2 * repos_diff.c -- The diff editor for comparing two repository versions
4 * ====================================================================
5 * Copyright (c) 2000-2006 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 * ====================================================================
19 /* This code uses an editor driven by a tree delta between two
20 * repository revisions (REV1 and REV2). For each file encountered in
21 * the delta the editor constructs two temporary files, one for each
22 * revision. This necessitates a separate request for the REV1 version
23 * of the file when the delta shows the file being modified or
24 * deleted. Files that are added by the delta do not require a
25 * separate request, the REV1 version is empty and the delta is
26 * sufficient to construct the REV2 version. When both versions of
27 * each file have been created the diff callback is invoked to display
28 * the difference between the two files. */
32 #include "svn_pools.h"
35 #include "svn_props.h"
39 /* Overall crawler editor baton. */
41 /* TARGET is a working-copy directory which corresponds to the base
42 URL open in RA_SESSION below. */
45 /* ADM_ACCESS is an access baton that includes the TARGET directory */
46 svn_wc_adm_access_t
*adm_access
;
48 /* The callback and calback argument that implement the file comparison
50 const svn_wc_diff_callbacks2_t
*diff_callbacks
;
53 /* DRY_RUN is TRUE if this is a dry-run diff, false otherwise. */
54 svn_boolean_t dry_run
;
56 /* RA_SESSION is the open session for making requests to the RA layer */
57 svn_ra_session_t
*ra_session
;
59 /* The rev1 from the '-r Rev1:Rev2' command line option */
60 svn_revnum_t revision
;
62 /* The rev2 from the '-r Rev1:Rev2' option, specifically set by
63 set_target_revision(). */
64 svn_revnum_t target_revision
;
66 /* The path to a temporary empty file used for add/delete
67 differences. The path is cached here so that it can be reused,
68 since all empty files are the same. */
69 const char *empty_file
;
71 /* Empty hash used for adds. */
72 apr_hash_t
*empty_hash
;
74 /* Hash used to check replaced paths. Key is path relative CWD,
75 * Value is *kind_action_state_t.
76 * All allocations are from edit_baton's pool. */
77 apr_hash_t
*deleted_paths
;
79 /* If the func is non-null, send notifications of actions. */
80 svn_wc_notify_func2_t notify_func
;
86 typedef struct kind_action_state_t
89 svn_wc_notify_action_t action
;
90 svn_wc_notify_state_t state
;
91 } kind_action_state_t
;
93 /* Directory level baton.
96 /* Gets set if the directory is added rather than replaced/unchanged. */
99 /* The path of the directory within the repository */
102 /* The path of the directory in the wc, relative to cwd */
105 /* The baton for the parent directory, or null if this is the root of the
106 hierarchy to be compared. */
107 struct dir_baton
*dir_baton
;
109 /* The overall crawler editor baton. */
110 struct edit_baton
*edit_baton
;
112 /* A cache of any property changes (svn_prop_t) received for this dir. */
113 apr_array_header_t
*propchanges
;
115 /* The pristine-property list attached to this directory. */
116 apr_hash_t
*pristine_props
;
118 /* The pool passed in by add_dir, open_dir, or open_root.
119 Also, the pool this dir baton is allocated in. */
126 /* Gets set if the file is added rather than replaced. */
129 /* The path of the file within the repository */
132 /* The path of the file in the wc, relative to cwd */
135 /* The path and APR file handle to the temporary file that contains the
136 first repository version. Also, the pristine-property list of
138 const char *path_start_revision
;
139 apr_file_t
*file_start_revision
;
140 apr_hash_t
*pristine_props
;
142 /* The path and APR file handle to the temporary file that contains the
143 second repository version. These fields are set when processing
144 textdelta and file deletion, and will be NULL if there's no
145 textual difference between the two revisions. */
146 const char *path_end_revision
;
147 apr_file_t
*file_end_revision
;
149 /* APPLY_HANDLER/APPLY_BATON represent the delta application baton. */
150 svn_txdelta_window_handler_t apply_handler
;
153 /* The overall crawler editor baton. */
154 struct edit_baton
*edit_baton
;
156 /* A cache of any property changes (svn_prop_t) received for this file. */
157 apr_array_header_t
*propchanges
;
159 /* The pool passed in by add_file or open_file.
160 Also, the pool this file_baton is allocated in. */
165 /* Create a new directory baton for PATH in POOL. ADDED is set if
166 * this directory is being added rather than replaced. PARENT_BATON is
167 * the baton of the parent directory (or NULL if this is the root of
168 * the comparison hierarchy). The directory and its parent may or may
169 * not exist in the working copy. EDIT_BATON is the overall crawler
172 static struct dir_baton
*
173 make_dir_baton(const char *path
,
174 struct dir_baton
*parent_baton
,
175 struct edit_baton
*edit_baton
,
179 struct dir_baton
*dir_baton
= apr_pcalloc(pool
, sizeof(*dir_baton
));
181 dir_baton
->dir_baton
= parent_baton
;
182 dir_baton
->edit_baton
= edit_baton
;
183 dir_baton
->added
= added
;
184 dir_baton
->pool
= pool
;
185 dir_baton
->path
= apr_pstrdup(pool
, path
);
186 dir_baton
->wcpath
= svn_path_join(edit_baton
->target
, path
, pool
);
187 dir_baton
->propchanges
= apr_array_make(pool
, 1, sizeof(svn_prop_t
));
192 /* Create a new file baton for PATH in POOL, which is a child of
193 * directory PARENT_PATH. ADDED is set if this file is being added
194 * rather than replaced. EDIT_BATON is a pointer to the global edit
197 static struct file_baton
*
198 make_file_baton(const char *path
,
203 struct file_baton
*file_baton
= apr_pcalloc(pool
, sizeof(*file_baton
));
204 struct edit_baton
*eb
= edit_baton
;
206 file_baton
->edit_baton
= edit_baton
;
207 file_baton
->added
= added
;
208 file_baton
->pool
= pool
;
209 file_baton
->path
= apr_pstrdup(pool
, path
);
210 file_baton
->wcpath
= svn_path_join(eb
->target
, path
, pool
);
211 file_baton
->propchanges
= apr_array_make(pool
, 1, sizeof(svn_prop_t
));
217 /* Helper function: return up to two svn:mime-type values buried
218 * within a file baton. Set *MIMETYPE1 to the value within the file's
219 * pristine properties, or NULL if not available. Set *MIMETYPE2 to
220 * the value within the "new" file's propchanges, or NULL if not
224 get_file_mime_types(const char **mimetype1
,
225 const char **mimetype2
,
226 struct file_baton
*b
)
232 if (b
->pristine_props
)
234 svn_string_t
*pristine_val
;
235 pristine_val
= apr_hash_get(b
->pristine_props
, SVN_PROP_MIME_TYPE
,
236 strlen(SVN_PROP_MIME_TYPE
));
238 *mimetype1
= pristine_val
->data
;
244 svn_prop_t
*propchange
;
246 for (i
= 0; i
< b
->propchanges
->nelts
; i
++)
248 propchange
= &APR_ARRAY_IDX(b
->propchanges
, i
, svn_prop_t
);
249 if (strcmp(propchange
->name
, SVN_PROP_MIME_TYPE
) == 0)
251 if (propchange
->value
)
252 *mimetype2
= propchange
->value
->data
;
260 /* Get the repository version of a file. This makes an RA request to
261 * retrieve the file contents. A pool cleanup handler is installed to
265 get_file_from_ra(struct file_baton
*b
, svn_revnum_t revision
)
268 svn_stream_t
*fstream
;
269 const char *temp_dir
;
271 SVN_ERR(svn_io_temp_dir(&temp_dir
, b
->pool
));
272 SVN_ERR(svn_io_open_unique_file2(&file
, &(b
->path_start_revision
),
273 svn_path_join(temp_dir
, "tmp", b
->pool
),
274 "", svn_io_file_del_on_pool_cleanup
,
277 fstream
= svn_stream_from_aprfile(file
, b
->pool
);
278 SVN_ERR(svn_ra_get_file(b
->edit_baton
->ra_session
,
282 &(b
->pristine_props
),
284 SVN_ERR(svn_io_file_close(file
, b
->pool
));
289 /* Get the props attached to a directory in the repository at BASE_REVISION. */
291 get_dirprops_from_ra(struct dir_baton
*b
, svn_revnum_t base_revision
)
293 SVN_ERR(svn_ra_get_dir2(b
->edit_baton
->ra_session
,
294 NULL
, NULL
, &(b
->pristine_props
),
304 /* Create an empty file, the path to the file is returned in
305 EMPTY_FILE_PATH. If ADM_ACCESS is not NULL and a lock is held,
306 create the file in the adm tmp/ area, otherwise use a system temp
309 If FILE is non-NULL, an open file is returned in *FILE. */
311 create_empty_file(apr_file_t
**file
,
312 const char **empty_file_path
,
313 svn_wc_adm_access_t
*adm_access
,
314 svn_io_file_del_t delete_when
,
317 if (adm_access
&& svn_wc_adm_locked(adm_access
))
318 SVN_ERR(svn_wc_create_tmp_file2(file
, empty_file_path
,
319 svn_wc_adm_access_path(adm_access
),
323 const char *temp_dir
;
325 SVN_ERR(svn_io_temp_dir(&temp_dir
, pool
));
326 SVN_ERR(svn_io_open_unique_file2(file
, empty_file_path
,
327 svn_path_join(temp_dir
, "tmp", pool
),
328 "", delete_when
, pool
));
334 /* Return in *PATH_ACCESS the access baton for the directory PATH by
335 searching the access baton set of ADM_ACCESS. If ADM_ACCESS is NULL
336 then *PATH_ACCESS will be NULL. If LENIENT is TRUE then failure to find
337 an access baton will not return an error but will set *PATH_ACCESS to
340 get_path_access(svn_wc_adm_access_t
**path_access
,
341 svn_wc_adm_access_t
*adm_access
,
343 svn_boolean_t lenient
,
350 svn_error_t
*err
= svn_wc_adm_retrieve(path_access
, adm_access
, path
,
356 svn_error_clear(err
);
364 /* Like get_path_access except the returned access baton, in
365 *PARENT_ACCESS, is for the parent of PATH rather than for PATH
368 get_parent_access(svn_wc_adm_access_t
**parent_access
,
369 svn_wc_adm_access_t
*adm_access
,
371 svn_boolean_t lenient
,
375 *parent_access
= NULL
; /* Avoid messing around with paths */
378 const char *parent_path
= svn_path_dirname(path
, pool
);
379 SVN_ERR(get_path_access(parent_access
, adm_access
, parent_path
,
385 /* Get the empty file associated with the edit baton. This is cached so
386 * that it can be reused, all empty files are the same.
389 get_empty_file(struct edit_baton
*eb
,
390 const char **empty_file_path
)
392 /* Create the file if it does not exist */
393 /* Note that we tried to use /dev/null in r17220, but
394 that won't work on Windows: it's impossible to stat NUL */
396 SVN_ERR(create_empty_file(NULL
, &(eb
->empty_file
), eb
->adm_access
,
397 svn_io_file_del_on_pool_cleanup
, eb
->pool
));
400 *empty_file_path
= eb
->empty_file
;
405 /* An editor function. The root of the comparison hierarchy */
407 set_target_revision(void *edit_baton
,
408 svn_revnum_t target_revision
,
411 struct edit_baton
*eb
= edit_baton
;
413 eb
->target_revision
= target_revision
;
417 /* An editor function. The root of the comparison hierarchy */
419 open_root(void *edit_baton
,
420 svn_revnum_t base_revision
,
424 struct edit_baton
*eb
= edit_baton
;
425 struct dir_baton
*b
= make_dir_baton("", NULL
, eb
, FALSE
, pool
);
427 /* Override the wcpath in our baton. */
428 b
->wcpath
= apr_pstrdup(pool
, eb
->target
);
430 SVN_ERR(get_dirprops_from_ra(b
, base_revision
));
436 /* An editor function. */
438 delete_entry(const char *path
,
439 svn_revnum_t base_revision
,
443 struct dir_baton
*pb
= parent_baton
;
444 struct edit_baton
*eb
= pb
->edit_baton
;
445 svn_node_kind_t kind
;
446 svn_wc_adm_access_t
*adm_access
;
447 svn_wc_notify_state_t state
= svn_wc_notify_state_inapplicable
;
448 svn_wc_notify_action_t action
= svn_wc_notify_skip
;
450 /* We need to know if this is a directory or a file */
451 SVN_ERR(svn_ra_check_path(eb
->ra_session
, path
, eb
->revision
, &kind
, pool
));
452 SVN_ERR(get_path_access(&adm_access
, eb
->adm_access
, pb
->wcpath
,
454 if ((! eb
->adm_access
) || adm_access
)
460 const char *mimetype1
, *mimetype2
;
461 struct file_baton
*b
;
463 /* Compare a file being deleted against an empty file */
464 b
= make_file_baton(path
, FALSE
, eb
, pool
);
465 SVN_ERR(get_file_from_ra(b
, eb
->revision
));
466 SVN_ERR(get_empty_file(b
->edit_baton
, &(b
->path_end_revision
)));
468 get_file_mime_types(&mimetype1
, &mimetype2
, b
);
470 SVN_ERR(eb
->diff_callbacks
->file_deleted
471 (adm_access
, &state
, b
->wcpath
,
472 b
->path_start_revision
,
473 b
->path_end_revision
,
474 mimetype1
, mimetype2
,
476 b
->edit_baton
->diff_cmd_baton
));
482 SVN_ERR(eb
->diff_callbacks
->dir_deleted
484 svn_path_join(eb
->target
, path
, pool
),
485 eb
->diff_cmd_baton
));
492 if ((state
!= svn_wc_notify_state_missing
)
493 && (state
!= svn_wc_notify_state_obstructed
))
495 action
= svn_wc_notify_update_delete
;
498 /* Remember what we _would've_ deleted (issue #2584). */
499 const char *wcpath
= svn_path_join(eb
->target
, path
, pb
->pool
);
500 apr_hash_set(svn_client__dry_run_deletions(eb
->diff_cmd_baton
),
501 wcpath
, APR_HASH_KEY_STRING
, wcpath
);
503 /* ### TODO: if (kind == svn_node_dir), record all
504 ### children as deleted to avoid collisions from
505 ### subsequent edits. */
512 const char* deleted_path
;
513 kind_action_state_t
*kas
= apr_palloc(eb
->pool
, sizeof(*kas
));
514 deleted_path
= svn_path_join(eb
->target
, path
, eb
->pool
);
516 kas
->action
= action
;
518 apr_hash_set(eb
->deleted_paths
, deleted_path
, APR_HASH_KEY_STRING
, kas
);
523 /* An editor function. */
525 add_directory(const char *path
,
527 const char *copyfrom_path
,
528 svn_revnum_t copyfrom_revision
,
532 struct dir_baton
*pb
= parent_baton
;
533 struct edit_baton
*eb
= pb
->edit_baton
;
535 svn_wc_adm_access_t
*adm_access
;
536 svn_wc_notify_state_t state
;
537 svn_wc_notify_action_t action
;
539 /* ### TODO: support copyfrom? */
541 b
= make_dir_baton(path
, pb
, eb
, TRUE
, pool
);
542 b
->pristine_props
= eb
->empty_hash
;
545 SVN_ERR(get_path_access(&adm_access
, eb
->adm_access
, pb
->wcpath
, TRUE
,
548 SVN_ERR(eb
->diff_callbacks
->dir_added
549 (adm_access
, &state
, b
->wcpath
, eb
->target_revision
,
550 eb
->diff_cmd_baton
));
552 if ((state
== svn_wc_notify_state_missing
)
553 || (state
== svn_wc_notify_state_obstructed
))
554 action
= svn_wc_notify_skip
;
556 action
= svn_wc_notify_update_add
;
560 svn_wc_notify_t
*notify
;
561 svn_boolean_t is_replace
= FALSE
;
562 kind_action_state_t
*kas
= apr_hash_get(eb
->deleted_paths
, b
->wcpath
,
563 APR_HASH_KEY_STRING
);
566 svn_wc_notify_action_t new_action
;
567 if (kas
->action
== svn_wc_notify_update_delete
568 && action
== svn_wc_notify_update_add
)
571 new_action
= svn_wc_notify_update_replace
;
574 new_action
= kas
->action
;
575 notify
= svn_wc_create_notify(b
->wcpath
, new_action
, pool
);
576 notify
->kind
= kas
->kind
;
577 notify
->content_state
= notify
->prop_state
= kas
->state
;
578 notify
->lock_state
= svn_wc_notify_lock_state_inapplicable
;
579 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
580 apr_hash_set(eb
->deleted_paths
, b
->wcpath
,
581 APR_HASH_KEY_STRING
, NULL
);
586 notify
= svn_wc_create_notify(b
->wcpath
, action
, pool
);
587 notify
->kind
= svn_node_dir
;
588 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
595 /* An editor function. */
597 open_directory(const char *path
,
599 svn_revnum_t base_revision
,
603 struct dir_baton
*pb
= parent_baton
;
606 b
= make_dir_baton(path
, pb
, pb
->edit_baton
, FALSE
, pool
);
609 SVN_ERR(get_dirprops_from_ra(b
, base_revision
));
615 /* An editor function. */
617 add_file(const char *path
,
619 const char *copyfrom_path
,
620 svn_revnum_t copyfrom_revision
,
624 struct dir_baton
*pb
= parent_baton
;
625 struct file_baton
*b
;
627 /* ### TODO: support copyfrom? */
629 b
= make_file_baton(path
, TRUE
, pb
->edit_baton
, pool
);
632 SVN_ERR(get_empty_file(b
->edit_baton
, &(b
->path_start_revision
)));
633 b
->pristine_props
= pb
->edit_baton
->empty_hash
;
638 /* An editor function. */
640 open_file(const char *path
,
642 svn_revnum_t base_revision
,
646 struct dir_baton
*pb
= parent_baton
;
647 struct file_baton
*b
;
649 b
= make_file_baton(path
, FALSE
, pb
->edit_baton
, pool
);
652 SVN_ERR(get_file_from_ra(b
, base_revision
));
657 /* Do the work of applying the text delta. */
659 window_handler(svn_txdelta_window_t
*window
,
662 struct file_baton
*b
= window_baton
;
664 SVN_ERR(b
->apply_handler(window
, b
->apply_baton
));
668 SVN_ERR(svn_io_file_close(b
->file_start_revision
, b
->pool
));
669 SVN_ERR(svn_io_file_close(b
->file_end_revision
, b
->pool
));
675 /* An editor function. */
677 apply_textdelta(void *file_baton
,
678 const char *base_checksum
,
680 svn_txdelta_window_handler_t
*handler
,
681 void **handler_baton
)
683 struct file_baton
*b
= file_baton
;
684 svn_wc_adm_access_t
*adm_access
;
686 /* Open the file to be used as the base for second revision */
687 SVN_ERR(svn_io_file_open(&(b
->file_start_revision
),
688 b
->path_start_revision
,
689 APR_READ
, APR_OS_DEFAULT
, b
->pool
));
691 /* Open the file that will become the second revision after applying the
692 text delta, it starts empty */
693 if (b
->edit_baton
->adm_access
)
697 err
= svn_wc_adm_probe_retrieve(&adm_access
, b
->edit_baton
->adm_access
,
701 svn_error_clear(err
);
707 SVN_ERR(create_empty_file(&(b
->file_end_revision
),
708 &(b
->path_end_revision
), adm_access
,
709 svn_io_file_del_on_pool_cleanup
, b
->pool
));
711 svn_txdelta_apply(svn_stream_from_aprfile(b
->file_start_revision
, b
->pool
),
712 svn_stream_from_aprfile(b
->file_end_revision
, b
->pool
),
716 &(b
->apply_handler
), &(b
->apply_baton
));
718 *handler
= window_handler
;
719 *handler_baton
= file_baton
;
724 /* An editor function. When the file is closed we have a temporary
725 * file containing a pristine version of the repository file. This can
726 * be compared against the working copy.
728 * ### Ignore TEXT_CHECKSUM for now. Someday we can use it to verify
729 * ### the integrity of the file being diffed. Done efficiently, this
730 * ### would probably involve calculating the checksum as the data is
731 * ### received, storing the final checksum in the file_baton, and
732 * ### comparing against it here.
735 close_file(void *file_baton
,
736 const char *text_checksum
,
739 struct file_baton
*b
= file_baton
;
740 struct edit_baton
*eb
= b
->edit_baton
;
741 svn_wc_adm_access_t
*adm_access
;
743 svn_wc_notify_action_t action
;
744 svn_wc_notify_state_t
745 content_state
= svn_wc_notify_state_unknown
,
746 prop_state
= svn_wc_notify_state_unknown
;
748 err
= get_parent_access(&adm_access
, eb
->adm_access
,
749 b
->wcpath
, eb
->dry_run
, b
->pool
);
751 if (err
&& err
->apr_err
== SVN_ERR_WC_NOT_LOCKED
)
753 /* ### maybe try to stat the local b->wcpath? */
754 /* If the file path doesn't exist, then send a 'skipped' notification. */
757 svn_wc_notify_t
*notify
= svn_wc_create_notify(b
->wcpath
,
760 notify
->kind
= svn_node_file
;
761 notify
->content_state
= svn_wc_notify_state_missing
;
762 notify
->prop_state
= prop_state
;
763 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
766 svn_error_clear(err
);
772 if (b
->path_end_revision
|| b
->propchanges
->nelts
> 0)
774 const char *mimetype1
, *mimetype2
;
775 get_file_mime_types(&mimetype1
, &mimetype2
, b
);
778 SVN_ERR(eb
->diff_callbacks
->file_added
779 (adm_access
, &content_state
, &prop_state
,
781 b
->path_end_revision
? b
->path_start_revision
: NULL
,
782 b
->path_end_revision
,
784 b
->edit_baton
->target_revision
,
785 mimetype1
, mimetype2
,
786 b
->propchanges
, b
->pristine_props
,
787 b
->edit_baton
->diff_cmd_baton
));
789 SVN_ERR(eb
->diff_callbacks
->file_changed
790 (adm_access
, &content_state
, &prop_state
,
792 b
->path_end_revision
? b
->path_start_revision
: NULL
,
793 b
->path_end_revision
,
794 b
->edit_baton
->revision
,
795 b
->edit_baton
->target_revision
,
796 mimetype1
, mimetype2
,
797 b
->propchanges
, b
->pristine_props
,
798 b
->edit_baton
->diff_cmd_baton
));
802 if ((content_state
== svn_wc_notify_state_missing
)
803 || (content_state
== svn_wc_notify_state_obstructed
))
804 action
= svn_wc_notify_skip
;
806 action
= svn_wc_notify_update_add
;
808 action
= svn_wc_notify_update_update
;
812 svn_wc_notify_t
*notify
;
813 svn_boolean_t is_replace
= FALSE
;
814 kind_action_state_t
*kas
= apr_hash_get(eb
->deleted_paths
, b
->wcpath
,
815 APR_HASH_KEY_STRING
);
818 svn_wc_notify_action_t new_action
;
819 if (kas
->action
== svn_wc_notify_update_delete
820 && action
== svn_wc_notify_update_add
)
823 new_action
= svn_wc_notify_update_replace
;
826 new_action
= kas
->action
;
827 notify
= svn_wc_create_notify(b
->wcpath
, new_action
, pool
);
828 notify
->kind
= kas
->kind
;
829 notify
->content_state
= notify
->prop_state
= kas
->state
;
830 notify
->lock_state
= svn_wc_notify_lock_state_inapplicable
;
831 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
832 apr_hash_set(eb
->deleted_paths
, b
->wcpath
,
833 APR_HASH_KEY_STRING
, NULL
);
838 notify
= svn_wc_create_notify(b
->wcpath
, action
, pool
);
839 notify
->kind
= svn_node_file
;
840 notify
->content_state
= content_state
;
841 notify
->prop_state
= prop_state
;
842 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
849 /* An editor function. */
851 close_directory(void *dir_baton
,
854 struct dir_baton
*b
= dir_baton
;
855 struct edit_baton
*eb
= b
->edit_baton
;
856 svn_wc_notify_state_t prop_state
= svn_wc_notify_state_unknown
;
860 svn_hash__clear(svn_client__dry_run_deletions(eb
->diff_cmd_baton
));
862 if (b
->propchanges
->nelts
> 0)
864 svn_wc_adm_access_t
*adm_access
;
865 err
= get_path_access(&adm_access
, eb
->adm_access
, b
->wcpath
,
866 eb
->dry_run
, b
->pool
);
868 if (err
&& err
->apr_err
== SVN_ERR_WC_NOT_LOCKED
)
870 /* ### maybe try to stat the local b->wcpath? */
871 /* If the path doesn't exist, then send a 'skipped' notification. */
874 svn_wc_notify_t
*notify
875 = svn_wc_create_notify(b
->wcpath
, svn_wc_notify_skip
, pool
);
876 notify
->kind
= svn_node_dir
;
877 notify
->content_state
= notify
->prop_state
878 = svn_wc_notify_state_missing
;
879 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
881 svn_error_clear(err
);
887 /* Don't do the props_changed stuff if this is a dry_run and we don't
888 have an access baton, since in that case the directory will already
889 have been recognised as added, in which case they cannot conflict. */
890 if (! eb
->dry_run
|| adm_access
)
891 SVN_ERR(eb
->diff_callbacks
->dir_props_changed
892 (adm_access
, &prop_state
,
894 b
->propchanges
, b
->pristine_props
,
895 b
->edit_baton
->diff_cmd_baton
));
898 /* ### Don't notify added directories as they triggered notification
899 in add_directory. Does this mean that directory notification
900 isn't getting all the information? */
901 if (!b
->added
&& eb
->notify_func
)
903 svn_wc_notify_t
*notify
;
904 apr_hash_index_t
*hi
;
906 for (hi
= apr_hash_first(NULL
, eb
->deleted_paths
); hi
;
907 hi
= apr_hash_next(hi
))
909 const void *deleted_path
;
910 kind_action_state_t
*kas
;
911 apr_hash_this(hi
, &deleted_path
, NULL
, (void *)&kas
);
912 notify
= svn_wc_create_notify(deleted_path
, kas
->action
, pool
);
913 notify
->kind
= kas
->kind
;
914 notify
->content_state
= notify
->prop_state
= kas
->state
;
915 notify
->lock_state
= svn_wc_notify_lock_state_inapplicable
;
916 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
917 apr_hash_set(eb
->deleted_paths
, deleted_path
,
918 APR_HASH_KEY_STRING
, NULL
);
921 notify
= svn_wc_create_notify(b
->wcpath
,
922 svn_wc_notify_update_update
, pool
);
923 notify
->kind
= svn_node_dir
;
924 notify
->content_state
= svn_wc_notify_state_inapplicable
;
925 notify
->prop_state
= prop_state
;
926 notify
->lock_state
= svn_wc_notify_lock_state_inapplicable
;
927 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
934 /* An editor function. */
936 change_file_prop(void *file_baton
,
938 const svn_string_t
*value
,
941 struct file_baton
*b
= file_baton
;
942 svn_prop_t
*propchange
;
944 propchange
= apr_array_push(b
->propchanges
);
945 propchange
->name
= apr_pstrdup(b
->pool
, name
);
946 propchange
->value
= value
? svn_string_dup(value
, b
->pool
) : NULL
;
951 /* An editor function. */
953 change_dir_prop(void *dir_baton
,
955 const svn_string_t
*value
,
958 struct dir_baton
*db
= dir_baton
;
959 svn_prop_t
*propchange
;
961 propchange
= apr_array_push(db
->propchanges
);
962 propchange
->name
= apr_pstrdup(db
->pool
, name
);
963 propchange
->value
= value
? svn_string_dup(value
, db
->pool
) : NULL
;
969 /* An editor function. */
971 close_edit(void *edit_baton
,
974 struct edit_baton
*eb
= edit_baton
;
976 svn_pool_destroy(eb
->pool
);
981 /* An editor function. */
983 absent_directory(const char *path
,
987 struct dir_baton
*pb
= parent_baton
;
988 struct edit_baton
*eb
= pb
->edit_baton
;
992 svn_wc_notify_t
*notify
993 = svn_wc_create_notify(svn_path_join(pb
->wcpath
,
994 svn_path_basename(path
, pool
),
996 svn_wc_notify_skip
, pool
);
997 notify
->kind
= svn_node_dir
;
998 notify
->content_state
= notify
->prop_state
999 = svn_wc_notify_state_missing
;
1000 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
1003 return SVN_NO_ERROR
;
1007 /* An editor function. */
1008 static svn_error_t
*
1009 absent_file(const char *path
,
1013 struct dir_baton
*pb
= parent_baton
;
1014 struct edit_baton
*eb
= pb
->edit_baton
;
1016 if (eb
->notify_func
)
1018 svn_wc_notify_t
*notify
1019 = svn_wc_create_notify(svn_path_join(pb
->wcpath
,
1020 svn_path_basename(path
, pool
),
1022 svn_wc_notify_skip
, pool
);
1023 notify
->kind
= svn_node_file
;
1024 notify
->content_state
= notify
->prop_state
1025 = svn_wc_notify_state_missing
;
1026 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
1029 return SVN_NO_ERROR
;
1032 /* Create a repository diff editor and baton. */
1034 svn_client__get_diff_editor(const char *target
,
1035 svn_wc_adm_access_t
*adm_access
,
1036 const svn_wc_diff_callbacks2_t
*diff_callbacks
,
1037 void *diff_cmd_baton
,
1039 svn_boolean_t dry_run
,
1040 svn_ra_session_t
*ra_session
,
1041 svn_revnum_t revision
,
1042 svn_wc_notify_func2_t notify_func
,
1044 svn_cancel_func_t cancel_func
,
1046 const svn_delta_editor_t
**editor
,
1050 apr_pool_t
*subpool
= svn_pool_create(pool
);
1051 svn_delta_editor_t
*tree_editor
= svn_delta_default_editor(subpool
);
1052 struct edit_baton
*eb
= apr_palloc(subpool
, sizeof(*eb
));
1054 eb
->target
= target
;
1055 eb
->adm_access
= adm_access
;
1056 eb
->diff_callbacks
= diff_callbacks
;
1057 eb
->diff_cmd_baton
= diff_cmd_baton
;
1058 eb
->dry_run
= dry_run
;
1059 eb
->ra_session
= ra_session
;
1060 eb
->revision
= revision
;
1061 eb
->empty_file
= NULL
;
1062 eb
->empty_hash
= apr_hash_make(subpool
);
1063 eb
->deleted_paths
= apr_hash_make(subpool
);
1065 eb
->notify_func
= notify_func
;
1066 eb
->notify_baton
= notify_baton
;
1068 tree_editor
->set_target_revision
= set_target_revision
;
1069 tree_editor
->open_root
= open_root
;
1070 tree_editor
->delete_entry
= delete_entry
;
1071 tree_editor
->add_directory
= add_directory
;
1072 tree_editor
->open_directory
= open_directory
;
1073 tree_editor
->add_file
= add_file
;
1074 tree_editor
->open_file
= open_file
;
1075 tree_editor
->apply_textdelta
= apply_textdelta
;
1076 tree_editor
->close_file
= close_file
;
1077 tree_editor
->close_directory
= close_directory
;
1078 tree_editor
->change_file_prop
= change_file_prop
;
1079 tree_editor
->change_dir_prop
= change_dir_prop
;
1080 tree_editor
->close_edit
= close_edit
;
1081 tree_editor
->absent_directory
= absent_directory
;
1082 tree_editor
->absent_file
= absent_file
;
1084 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func
,
1092 /* We don't destroy subpool, as it's managed by the edit baton. */
1093 return SVN_NO_ERROR
;