Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_wc / props.c
bloba993649f5080732678c1814e4ff6d804116f088e
1 /*
2 * props.c : routines dealing with properties in the working copy
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 * ====================================================================
21 #include <stdlib.h>
22 #include <string.h>
23 #include <assert.h>
24 #include <apr_pools.h>
25 #include <apr_hash.h>
26 #include <apr_tables.h>
27 #include <apr_file_io.h>
28 #include <apr_strings.h>
29 #include <apr_general.h>
30 #include "svn_types.h"
31 #include "svn_string.h"
32 #include "svn_pools.h"
33 #include "svn_path.h"
34 #include "svn_xml.h"
35 #include "svn_error.h"
36 #include "svn_props.h"
37 #include "svn_io.h"
38 #include "svn_hash.h"
39 #include "svn_mergeinfo.h"
40 #include "svn_wc.h"
41 #include "svn_utf.h"
42 #include "svn_diff.h"
44 #include "private/svn_wc_private.h"
45 #include "private/svn_mergeinfo_private.h"
47 #include "wc.h"
48 #include "log.h"
49 #include "adm_files.h"
50 #include "entries.h"
51 #include "props.h"
52 #include "translate.h"
53 #include "questions.h"
54 #include "lock.h"
56 #include "svn_private_config.h"
58 /*---------------------------------------------------------------------*/
60 /*** Deducing local changes to properties ***/
62 /*---------------------------------------------------------------------*/
64 /*** Reading/writing property hashes from disk ***/
66 /* The real functionality here is part of libsvn_subr, in hashdump.c.
67 But these are convenience routines for use in libsvn_wc. */
69 static svn_error_t *
70 get_prop_path(const char **ppath,
71 const char *path,
72 svn_wc__props_kind_t props_kind,
73 svn_wc_adm_access_t *adm_access,
74 apr_pool_t *pool)
76 const svn_wc_entry_t *entry;
78 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, TRUE, pool));
79 SVN_ERR(svn_wc__prop_path(ppath, path, entry->kind,
80 props_kind, FALSE, pool));
82 return SVN_NO_ERROR;
85 /* If PROPFILE_PATH exists (and is a file), assume it's full of
86 properties and load this file into HASH. Otherwise, leave HASH
87 untouched. */
88 static svn_error_t *
89 load_prop_file(const char *propfile_path,
90 apr_hash_t *hash,
91 apr_pool_t *pool)
93 svn_error_t *err;
95 apr_file_t *propfile = NULL;
97 err = svn_io_file_open(&propfile, propfile_path,
98 APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
99 pool);
101 if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
102 || APR_STATUS_IS_ENOTDIR(err->apr_err)))
104 svn_error_clear(err);
105 return SVN_NO_ERROR;
108 SVN_ERR(err);
110 SVN_ERR_W(svn_hash_read(hash, propfile, pool),
111 apr_psprintf(pool, _("Can't parse '%s'"),
112 svn_path_local_style(propfile_path, pool)));
114 SVN_ERR(svn_io_file_close(propfile, pool));
116 return SVN_NO_ERROR;
121 /* Given a HASH full of property name/values, write them to a file
122 located at PROPFILE_PATH. If WRITE_EMPTY is TRUE then writing
123 an emtpy property hash will result in an actual empty property
124 file on disk, otherwise an empty hash will result in no file
125 being written at all. */
126 static svn_error_t *
127 save_prop_file(const char *propfile_path,
128 apr_hash_t *hash,
129 svn_boolean_t write_empty,
130 apr_pool_t *pool)
132 apr_file_t *prop_tmp;
134 SVN_ERR(svn_io_file_open(&prop_tmp, propfile_path,
135 (APR_WRITE | APR_CREATE | APR_TRUNCATE
136 | APR_BUFFERED),
137 APR_OS_DEFAULT, pool));
139 if (apr_hash_count(hash) != 0 || write_empty)
140 SVN_ERR_W(svn_hash_write(hash, prop_tmp, pool),
141 apr_psprintf(pool,
142 _("Can't write property hash to '%s'"),
143 svn_path_local_style(propfile_path, pool)));
145 SVN_ERR(svn_io_file_close(prop_tmp, pool));
147 return SVN_NO_ERROR;
151 /*---------------------------------------------------------------------*/
153 /*** Misc ***/
155 /* Opens reject temporary file for FULL_PATH. */
156 static svn_error_t *
157 open_reject_tmp_file(apr_file_t **fp, const char **reject_tmp_path,
158 const char *full_path,
159 svn_wc_adm_access_t *adm_access,
160 svn_boolean_t is_dir, apr_pool_t *pool)
162 const char *tmp_path;
164 /* Get path to /temporary/ local prop file */
165 SVN_ERR(svn_wc__prop_path(&tmp_path, full_path,
166 is_dir ? svn_node_dir : svn_node_file,
167 svn_wc__props_working, TRUE, pool));
169 /* Reserve a .prej file based on it. */
170 SVN_ERR(svn_io_open_unique_file2(fp, reject_tmp_path, tmp_path,
171 SVN_WC__PROP_REJ_EXT,
172 svn_io_file_del_none, pool));
174 return SVN_NO_ERROR;
178 /* Assuming FP is a filehandle already open for appending, write
179 CONFLICT_DESCRIPTION to file, plus a trailing EOL sequence. */
180 static svn_error_t *
181 append_prop_conflict(apr_file_t *fp,
182 const svn_string_t *conflict_description,
183 apr_pool_t *pool)
185 /* TODO: someday, perhaps prefix each conflict_description with a
186 timestamp or something? */
187 apr_size_t written;
188 const char *native_text =
189 svn_utf_cstring_from_utf8_fuzzy(conflict_description->data, pool);
190 SVN_ERR(svn_io_file_write_full(fp, native_text, strlen(native_text),
191 &written, pool));
193 native_text = svn_utf_cstring_from_utf8_fuzzy(APR_EOL_STR, pool);
194 SVN_ERR(svn_io_file_write_full(fp, native_text, strlen(native_text),
195 &written, pool));
197 return SVN_NO_ERROR;
201 /* Look up the entry NAME within ADM_ACCESS and see if it has a `current'
202 reject file describing a state of conflict. Set *REJECT_FILE to the
203 name of that file, or to NULL if no such file exists. */
204 static svn_error_t *
205 get_existing_prop_reject_file(const char **reject_file,
206 svn_wc_adm_access_t *adm_access,
207 const char *path,
208 apr_pool_t *pool)
210 const svn_wc_entry_t *entry;
212 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
214 *reject_file = entry->prejfile
215 ? apr_pstrcat(pool, svn_wc_adm_access_path(adm_access), entry->prejfile)
216 : NULL;
217 return SVN_NO_ERROR;
220 /*---------------------------------------------------------------------*/
223 /* Build a space separated list of properties that are contained in
224 the hash PROPS and which we want to cache.
225 The string is allocated in POOL. */
226 static const char *
227 build_present_props(apr_hash_t *props, apr_pool_t *pool)
229 apr_array_header_t *cachable;
230 svn_stringbuf_t *present_props = svn_stringbuf_create("", pool);
231 int i;
233 if (apr_hash_count(props) == 0)
234 return present_props->data;
236 cachable = svn_cstring_split(SVN_WC__CACHABLE_PROPS, " ", TRUE, pool);
237 for (i = 0; i < cachable->nelts; i++)
239 const char *proptolookfor = APR_ARRAY_IDX(cachable, i,
240 const char *);
242 if (apr_hash_get(props, proptolookfor, APR_HASH_KEY_STRING) != NULL)
244 svn_stringbuf_appendcstr(present_props, proptolookfor);
245 svn_stringbuf_appendcstr(present_props, " ");
249 /* Avoid returning a string with a trailing space. */
250 svn_stringbuf_chop(present_props, 1);
251 return present_props->data;
254 /*** Loading regular properties. ***/
255 svn_error_t *
256 svn_wc__load_props(apr_hash_t **base_props_p,
257 apr_hash_t **props_p,
258 apr_hash_t **revert_props_p,
259 svn_wc_adm_access_t *adm_access,
260 const char *path,
261 apr_pool_t *pool)
263 svn_node_kind_t kind;
264 svn_boolean_t has_propcaching =
265 svn_wc__adm_wc_format(adm_access) > SVN_WC__NO_PROPCACHING_VERSION;
266 const svn_wc_entry_t *entry;
267 apr_hash_t *base_props = NULL; /* Silence uninitialized warning. */
269 SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
270 /* If there is no entry, we just return empty hashes, since the
271 property merging can use this function when there is no entry. */
272 if (! entry)
274 if (base_props_p)
275 *base_props_p = apr_hash_make(pool);
276 if (props_p)
277 *props_p = apr_hash_make(pool);
278 if (revert_props_p)
279 *revert_props_p = apr_hash_make(pool);
280 return SVN_NO_ERROR;
283 kind = entry->kind;
284 /* We will need the base props if the user requested them, OR,
285 our WC has prop caching, the user requested working props and there are no
286 prop mods. */
287 if (base_props_p
288 || (has_propcaching && ! entry->has_prop_mods && entry->has_props))
290 const char *prop_base_path;
292 SVN_ERR(svn_wc__prop_path(&prop_base_path,
293 path, kind, svn_wc__props_base, FALSE, pool));
294 base_props = apr_hash_make(pool);
295 SVN_ERR(load_prop_file(prop_base_path, base_props, pool));
297 if (base_props_p)
298 *base_props_p = base_props;
301 if (props_p)
303 if (has_propcaching && ! entry->has_prop_mods && entry->has_props)
304 *props_p = apr_hash_copy(pool, base_props);
305 else if (! has_propcaching || entry->has_props)
307 const char *prop_path;
309 SVN_ERR(svn_wc__prop_path(&prop_path, path, kind,
310 svn_wc__props_working, FALSE, pool));
311 *props_p = apr_hash_make(pool);
312 SVN_ERR(load_prop_file(prop_path, *props_p, pool));
314 else
315 *props_p = apr_hash_make(pool);
318 if (revert_props_p)
320 *revert_props_p = apr_hash_make(pool);
322 if (entry->schedule == svn_wc_schedule_replace
323 && entry->copied)
325 const char *revert_prop_path;
327 SVN_ERR(svn_wc__prop_path(&revert_prop_path, path, kind,
328 svn_wc__props_revert, FALSE, pool));
329 SVN_ERR(load_prop_file(revert_prop_path, *revert_props_p, pool));
333 return SVN_NO_ERROR;
337 /*---------------------------------------------------------------------*/
339 /*** Installing new properties. ***/
340 svn_error_t *
341 svn_wc__install_props(svn_stringbuf_t **log_accum,
342 svn_wc_adm_access_t *adm_access,
343 const char *path,
344 apr_hash_t *base_props,
345 apr_hash_t *working_props,
346 svn_boolean_t write_base_props,
347 apr_pool_t *pool)
349 const char *working_propfile_path, *working_prop_tmp_path;
350 apr_array_header_t *prop_diffs;
351 const svn_wc_entry_t *entry;
352 svn_wc_entry_t tmp_entry;
353 svn_node_kind_t kind;
354 svn_boolean_t has_propcaching =
355 svn_wc__adm_wc_format(adm_access) > SVN_WC__NO_PROPCACHING_VERSION;
357 if (! svn_path_is_child(svn_wc_adm_access_path(adm_access), path, NULL))
358 kind = svn_node_dir;
359 else
360 kind = svn_node_file;
362 /* Check if the props are modified. */
363 SVN_ERR(svn_prop_diffs(&prop_diffs, working_props, base_props, pool));
364 tmp_entry.has_prop_mods = (prop_diffs->nelts > 0);
365 tmp_entry.has_props = (apr_hash_count(working_props) > 0);
366 tmp_entry.cachable_props = SVN_WC__CACHABLE_PROPS;
367 tmp_entry.present_props = build_present_props(working_props, pool);
369 SVN_ERR(svn_wc__loggy_entry_modify(log_accum, adm_access,
370 path, &tmp_entry,
371 SVN_WC__ENTRY_MODIFY_HAS_PROPS
372 | SVN_WC__ENTRY_MODIFY_HAS_PROP_MODS
373 | SVN_WC__ENTRY_MODIFY_CACHABLE_PROPS
374 | SVN_WC__ENTRY_MODIFY_PRESENT_PROPS,
375 pool));
377 if (has_propcaching)
378 SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
379 else
380 entry = NULL;
382 /* Write our property hashes into temporary files. Notice that the
383 paths computed are ABSOLUTE pathnames, which is what our disk
384 routines require. */
385 SVN_ERR(svn_wc__prop_path(&working_propfile_path, path,
386 kind, svn_wc__props_working, FALSE, pool));
387 if (tmp_entry.has_prop_mods)
389 SVN_ERR(svn_wc__prop_path(&working_prop_tmp_path, path,
390 kind, svn_wc__props_working, TRUE, pool));
392 /* Write the working prop hash to path/.svn/tmp/props/name or
393 path/.svn/tmp/dir-props */
394 SVN_ERR(save_prop_file(working_prop_tmp_path, working_props,
395 FALSE, pool));
397 /* Write log entry to move working tmp copy to real working area. */
398 SVN_ERR(svn_wc__loggy_move(log_accum, NULL,
399 adm_access,
400 working_prop_tmp_path,
401 working_propfile_path,
402 FALSE, pool));
404 /* Make props read-only */
405 SVN_ERR(svn_wc__loggy_set_readonly(log_accum, adm_access,
406 working_propfile_path, pool));
408 else
410 /* No property modifications, remove the file instead. */
411 if (! has_propcaching || (entry && entry->has_prop_mods))
412 SVN_ERR(svn_wc__loggy_remove(log_accum, adm_access,
413 working_propfile_path, pool));
416 /* Repeat the above steps for the base properties if required. */
417 if (write_base_props)
419 const char *base_propfile_path, *base_prop_tmp_path;
421 SVN_ERR(svn_wc__prop_path(&base_propfile_path, path,
422 kind, svn_wc__props_base, FALSE, pool));
424 if (apr_hash_count(base_props) > 0)
426 SVN_ERR(svn_wc__prop_path(&base_prop_tmp_path, path,
427 kind, svn_wc__props_base, TRUE, pool));
429 SVN_ERR(save_prop_file(base_prop_tmp_path, base_props, FALSE, pool));
431 SVN_ERR(svn_wc__loggy_move(log_accum, NULL, adm_access,
432 base_prop_tmp_path,
433 base_propfile_path,
434 FALSE, pool));
436 SVN_ERR(svn_wc__loggy_set_readonly(log_accum, adm_access,
437 base_propfile_path, pool));
439 else
441 if (! has_propcaching || (entry && entry->has_props))
442 SVN_ERR(svn_wc__loggy_remove(log_accum, adm_access,
443 base_propfile_path, pool));
447 return SVN_NO_ERROR;
452 svn_error_t *
453 svn_wc__working_props_committed(const char *path,
454 svn_wc_adm_access_t *adm_access,
455 svn_boolean_t sync_entries,
456 apr_pool_t *pool)
458 const char *working;
459 const char *base;
460 const svn_wc_entry_t *entry;
461 svn_wc_entry_t mod_entry;
462 svn_wc_adm_access_t *mod_access;
465 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, TRUE, pool));
467 SVN_ERR(svn_wc__prop_path(&working, path, entry->kind,
468 svn_wc__props_working, FALSE, pool));
469 SVN_ERR(svn_wc__prop_path(&base, path, entry->kind,
470 svn_wc__props_base, FALSE, pool));
472 /* svn_io_file_rename() retains a read-only bit, so there's no
473 need to explicitly set it. */
474 SVN_ERR(svn_io_file_rename(working, base, pool));
476 SVN_ERR(svn_wc_adm_probe_retrieve(&mod_access, adm_access, path, pool));
477 mod_entry.has_prop_mods = FALSE;
478 SVN_ERR(svn_wc__entry_modify(mod_access, entry->name, &mod_entry,
479 SVN_WC__ENTRY_MODIFY_HAS_PROP_MODS,
480 sync_entries, pool));
482 return SVN_NO_ERROR;
486 svn_error_t *
487 svn_wc__props_last_modified(apr_time_t *mod_time,
488 const char *path,
489 svn_wc__props_kind_t props_kind,
490 svn_wc_adm_access_t *adm_access,
491 apr_pool_t *pool)
493 svn_error_t *err;
494 const char *props_file;
496 SVN_ERR(get_prop_path(&props_file, path, props_kind, adm_access, pool));
498 err = svn_io_file_affected_time(mod_time, props_file, pool);
499 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
501 svn_error_clear(err);
502 *mod_time = 0;
504 else
505 SVN_ERR_W(err,
506 apr_psprintf(pool,
507 _("Error getting 'affected time' on '%s'"),
508 svn_path_local_style(props_file, pool)));
510 return SVN_NO_ERROR;
513 static svn_error_t *
514 remove_file_if_present(const char *file, apr_pool_t *pool)
516 svn_error_t *err;
518 /* Try to remove the file. */
519 err = svn_io_remove_file(file, pool);
521 /* Ignore file not found error. */
522 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
524 svn_error_clear(err);
525 err = SVN_NO_ERROR;
528 return err;
532 /* If wcprops are stored in a single file in this working copy, read that file
533 and store it in the cache of ADM_ACCESS. Use POOL for temporary
534 allocations. */
535 static svn_error_t *
536 read_wcprops(svn_wc_adm_access_t *adm_access, apr_pool_t *pool)
538 apr_file_t *file;
539 apr_pool_t *cache_pool = svn_wc_adm_access_pool(adm_access);
540 apr_hash_t *all_wcprops;
541 apr_hash_t *proplist;
542 svn_stream_t *stream;
543 svn_error_t *err;
545 /* If the WC format is too old, there is nothing to cache. */
546 if (svn_wc__adm_wc_format(adm_access) <= SVN_WC__WCPROPS_MANY_FILES_VERSION)
547 return SVN_NO_ERROR;
549 all_wcprops = apr_hash_make(cache_pool);
551 err = svn_wc__open_adm_file(&file, svn_wc_adm_access_path(adm_access),
552 SVN_WC__ADM_ALL_WCPROPS,
553 APR_READ | APR_BUFFERED, pool);
555 /* A non-existent file means there are no props. */
556 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
558 svn_error_clear(err);
559 svn_wc__adm_access_set_wcprops(adm_access, all_wcprops);
560 return SVN_NO_ERROR;
562 SVN_ERR(err);
564 stream = svn_stream_from_aprfile2(file, TRUE, pool);
566 /* Read the proplist for THIS_DIR. */
567 proplist = apr_hash_make(cache_pool);
568 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, cache_pool));
569 apr_hash_set(all_wcprops, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING,
570 proplist);
572 /* And now, the children. */
573 while (1729)
575 svn_stringbuf_t *line;
576 svn_boolean_t eof;
577 SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, cache_pool));
578 if (eof)
580 if (line->len > 0)
581 return svn_error_createf
582 (SVN_ERR_WC_CORRUPT, NULL,
583 _("Missing end of line in wcprops file for '%s'"),
584 svn_path_local_style(svn_wc_adm_access_path(adm_access), pool));
585 break;
587 proplist = apr_hash_make(cache_pool);
588 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR,
589 cache_pool));
590 apr_hash_set(all_wcprops, line->data, APR_HASH_KEY_STRING, proplist);
593 svn_wc__adm_access_set_wcprops(adm_access, all_wcprops);
595 SVN_ERR(svn_wc__close_adm_file(file, svn_wc_adm_access_path(adm_access),
596 SVN_WC__ADM_ALL_WCPROPS, FALSE, pool));
598 return SVN_NO_ERROR;
601 static svn_error_t *
602 write_wcprops(svn_wc_adm_access_t *adm_access, apr_pool_t *pool)
604 apr_hash_t *wcprops = svn_wc__adm_access_wcprops(adm_access);
605 apr_file_t *file;
606 svn_stream_t *stream;
607 apr_hash_t *proplist;
608 apr_hash_index_t *hi;
609 apr_pool_t *subpool = svn_pool_create(pool);
610 svn_boolean_t any_props = FALSE;
612 /* If there are no cached wcprops, there is nothing to do. */
613 if (! wcprops)
614 return SVN_NO_ERROR;
616 /* Check if there are any properties at all. */
617 for (hi = apr_hash_first(pool, wcprops); hi && ! any_props;
618 hi = apr_hash_next(hi))
620 void *val;
622 apr_hash_this(hi, NULL, NULL, &val);
623 proplist = val;
624 if (apr_hash_count(proplist) > 0)
625 any_props = TRUE;
628 /* If there are no props, remove the file. */
629 if (! any_props)
631 svn_error_t *err;
633 err = svn_wc__remove_adm_file(svn_wc_adm_access_path(adm_access), pool,
634 SVN_WC__ADM_ALL_WCPROPS, NULL);
635 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
637 svn_error_clear(err);
638 return SVN_NO_ERROR;
640 else
641 return err;
644 SVN_ERR(svn_wc__open_adm_file(&file, svn_wc_adm_access_path(adm_access),
645 SVN_WC__ADM_ALL_WCPROPS,
646 APR_WRITE | APR_BUFFERED, pool));
647 stream = svn_stream_from_aprfile2(file, TRUE, pool);
649 /* First, the props for this_dir. */
650 proplist = apr_hash_get(wcprops, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING);
651 if (! proplist)
652 proplist = apr_hash_make(subpool);
653 SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, subpool));
655 /* Write children. */
656 for (hi = apr_hash_first(pool, wcprops); hi; hi = apr_hash_next(hi))
658 const void *key;
659 void *val;
660 const char *name;
662 apr_hash_this(hi, &key, NULL, &val);
663 name = key;
664 proplist = val;
666 /* We already wrote this_dir, and writing empty hashes makes me
667 feel silly... */
668 if (strcmp(SVN_WC_ENTRY_THIS_DIR, name) == 0
669 || apr_hash_count(proplist) == 0)
670 continue;
672 svn_pool_clear(subpool);
674 SVN_ERR(svn_stream_printf(stream, subpool, "%s\n", name));
675 SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, subpool));
678 SVN_ERR(svn_wc__close_adm_file(file, svn_wc_adm_access_path(adm_access),
679 SVN_WC__ADM_ALL_WCPROPS, TRUE, pool));
681 return SVN_NO_ERROR;
685 svn_error_t *
686 svn_wc__props_flush(const char *path,
687 svn_wc__props_kind_t props_kind,
688 svn_wc_adm_access_t *adm_access,
689 apr_pool_t *pool)
691 if (props_kind != svn_wc__props_wcprop)
692 return SVN_NO_ERROR;
693 else
695 svn_wc_adm_access_t *prop_access;
697 SVN_ERR(svn_wc_adm_probe_retrieve(&prop_access, adm_access,
698 path, pool));;
699 SVN_ERR(write_wcprops(prop_access, pool));
702 return SVN_NO_ERROR;
706 static svn_error_t *
707 remove_wcprops(svn_wc_adm_access_t *adm_access,
708 const char *name,
709 apr_pool_t *pool)
711 apr_hash_t *all_wcprops = svn_wc__adm_access_wcprops(adm_access);
712 svn_boolean_t write_needed = FALSE;
714 if (! name)
716 /* There is no point in reading the props just to determine if we
717 need to rewrite them:-), so assume a write is needed if the props
718 aren't already cached. */
719 if (! all_wcprops || apr_hash_count(all_wcprops) > 0)
721 svn_wc__adm_access_set_wcprops
722 (adm_access, apr_hash_make(svn_wc_adm_access_pool(adm_access)));
723 write_needed = TRUE;
726 else
728 apr_hash_t *wcprops;
729 if (! all_wcprops)
731 SVN_ERR(read_wcprops(adm_access, pool));
732 all_wcprops = svn_wc__adm_access_wcprops(adm_access);
734 if (all_wcprops)
735 wcprops = apr_hash_get(all_wcprops, name, APR_HASH_KEY_STRING);
736 else
737 wcprops = NULL;
738 if (wcprops && apr_hash_count(wcprops) > 0)
740 apr_hash_set(all_wcprops, name, APR_HASH_KEY_STRING, NULL);
741 write_needed = TRUE;
744 if (write_needed)
745 SVN_ERR(write_wcprops(adm_access, pool));
747 return SVN_NO_ERROR;
750 svn_error_t *
751 svn_wc__loggy_props_delete(svn_stringbuf_t **log_accum,
752 const char *path,
753 svn_wc__props_kind_t props_kind,
754 svn_wc_adm_access_t *adm_access,
755 apr_pool_t *pool)
757 const char *props_file;
759 if (props_kind == svn_wc__props_wcprop)
761 /* We use 1 file for all wcprops in a directory,
762 use a helper to remove them from that file */
763 apr_hash_t *props;
764 apr_pool_t *iterpool = svn_pool_create(pool);
765 apr_hash_index_t *hi;
767 SVN_ERR(svn_wc__wcprop_list(&props, path, adm_access, pool));
768 /* ### TODO: There's no log command to delete all wcprops
769 from a file at once. Removing all props should do it though. */
771 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
773 const void *key;
774 const char *name;
776 svn_pool_clear(iterpool);
778 apr_hash_this(hi, &key, NULL, NULL);
779 name = key;
781 SVN_ERR(svn_wc__loggy_modify_wcprop(log_accum,
782 adm_access, path,
783 name, NULL, iterpool));
786 svn_pool_destroy(iterpool);
788 else
790 SVN_ERR(get_prop_path(&props_file, path, props_kind, adm_access, pool));
791 SVN_ERR(svn_wc__loggy_remove(log_accum, adm_access, props_file, pool));
794 return SVN_NO_ERROR;
798 svn_error_t *
799 svn_wc__props_delete(const char *path,
800 svn_wc__props_kind_t props_kind,
801 svn_wc_adm_access_t *adm_access,
802 apr_pool_t *pool)
804 const char *props_file;
806 if (props_kind == svn_wc__props_wcprop)
808 /* We use 1 file for all wcprops in a directory,
809 use a helper to remove them from that file */
811 svn_wc_adm_access_t *path_access;
813 SVN_ERR(svn_wc_adm_probe_retrieve(&path_access, adm_access,
814 path, pool));
815 SVN_ERR(remove_wcprops
816 (path_access,
817 svn_path_is_child(svn_wc_adm_access_path(path_access),
818 path, NULL), pool));
820 else
822 SVN_ERR(get_prop_path(&props_file, path, props_kind, adm_access, pool));
823 SVN_ERR(remove_file_if_present(props_file, pool));
826 return SVN_NO_ERROR;
829 svn_error_t *
830 svn_wc__loggy_revert_props_create(svn_stringbuf_t **log_accum,
831 const char *path,
832 svn_wc_adm_access_t *adm_access,
833 svn_boolean_t destroy_baseprops,
834 apr_pool_t *pool)
836 const svn_wc_entry_t *entry;
837 const char *dst_rprop;
838 const char *dst_bprop;
839 const char *tmp_rprop;
840 svn_node_kind_t kind;
842 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, TRUE, pool));
844 SVN_ERR(svn_wc__prop_path(&dst_rprop, path,
845 entry->kind, svn_wc__props_revert, FALSE, pool));
846 SVN_ERR(svn_wc__prop_path(&tmp_rprop, path,
847 entry->kind, svn_wc__props_revert, TRUE, pool));
848 SVN_ERR(svn_wc__prop_path(&dst_bprop, path,
849 entry->kind, svn_wc__props_base, FALSE, pool));
851 /* If prop base exist, copy it to revert base. */
852 SVN_ERR(svn_io_check_path(dst_bprop, &kind, pool));
853 if (kind == svn_node_file)
855 if (destroy_baseprops)
856 SVN_ERR(svn_wc__loggy_move(log_accum, NULL,
857 adm_access, dst_bprop, dst_rprop,
858 FALSE, pool));
859 else
861 SVN_ERR(svn_io_copy_file(dst_bprop, tmp_rprop, TRUE, pool));
862 SVN_ERR(svn_wc__loggy_move(log_accum, NULL, adm_access,
863 tmp_rprop, dst_rprop, FALSE, pool));
866 else if (kind == svn_node_none)
868 /* If there wasn't any prop base we still need an empty revert
869 propfile, otherwise a revert won't know that a change to the
870 props needs to be made (it'll just see no file, and do nothing).
871 So manufacture an empty propfile and force it to be written out. */
873 SVN_ERR(svn_wc__prop_path(&dst_bprop, path, entry->kind,
874 svn_wc__props_revert, TRUE, pool));
876 SVN_ERR(save_prop_file(dst_bprop, apr_hash_make(pool), TRUE, pool));
878 SVN_ERR(svn_wc__loggy_move(log_accum, NULL,
879 adm_access, dst_bprop, dst_rprop,
880 FALSE, pool));
883 return SVN_NO_ERROR;
886 #if 0
887 /*### Some day, when we get better log primitives,
888 we probably want to stat() less, which can be done coding
889 'calls' to functions like the one below into as a log command.*/
890 svn_error_t *
891 svn_wc__revert_props_create(const char *path,
892 svn_wc_adm_access_t *adm_access,
893 svn_boolean_t destroy_baseprops,
894 svn_boolean_t maybe_rerun,
895 apr_pool_t *pool)
897 const svn_wc_entry_t *entry;
898 const char *revert_file, *base_file;
899 const char *tmp_revert_file;
900 svn_error_t *err;
902 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, TRUE, pool));
904 SVN_ERR(svn_wc__prop_path(&base_file, path, entry->kind, svn_wc__props_base,
905 FALSE, pool));
906 SVN_ERR(svn_wc__prop_path(&revert_file, path, entry->kind,
907 svn_wc__props_revert, FALSE, pool));
908 SVN_ERR(svn_wc__prop_path(&tmp_revert_file, path, entry->kind,
909 svn_wc__props_revert, TRUE, pool));
912 if (destroy_baseprops)
913 err = svn_io_file_rename(base_file, revert_file, pool);
914 else
916 err = svn_io_copy_file(base_file, tmp_revert_file, TRUE, pool);
917 if (! err)
918 SVN_ERR(svn_io_file_rename(tmp_revert_file, revert_file, pool));
921 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
922 /* If there's no file to move or copy, create one. */
924 svn_node_kind_t kind = svn_node_none;
926 svn_error_clear(err);
928 if (maybe_rerun)
929 SVN_ERR(svn_io_check_path(revert_file, &kind, pool));
931 if (kind == svn_node_none)
933 SVN_ERR(save_prop_file(tmp_revert_file,
934 apr_hash_make(pool), TRUE, pool));
935 SVN_ERR(svn_io_file_rename(base_file, revert_file, pool));
939 return SVN_NO_ERROR;
941 #endif
943 svn_error_t *
944 svn_wc__loggy_revert_props_restore(svn_stringbuf_t **log_accum,
945 const char *path,
946 svn_wc_adm_access_t *adm_access,
947 apr_pool_t *pool)
949 const svn_wc_entry_t *entry;
950 const char *revert_file, *base_file;
952 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, TRUE, pool));
954 SVN_ERR(svn_wc__prop_path(&base_file, path, entry->kind, svn_wc__props_base,
955 FALSE, pool));
956 SVN_ERR(svn_wc__prop_path(&revert_file, path, entry->kind,
957 svn_wc__props_revert, FALSE, pool));
959 SVN_ERR(svn_wc__loggy_move(log_accum, NULL, adm_access,
960 revert_file, base_file, FALSE, pool));
961 return SVN_NO_ERROR;
965 #if 0
966 /*### Some day, when we get better log primitives,
967 we probably want to stat() less, which can be done coding
968 'calls' to functions like the one below into as a log command.*/
969 svn_error_t *
970 svn_wc__revert_props_restore(const char *path,
971 svn_wc_adm_access_t *adm_access,
972 apr_pool_t *pool)
974 const svn_wc_entry_t *entry;
975 const char *revert_file, *base_file;
977 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, TRUE, pool));
979 SVN_ERR(svn_wc__prop_path(&base_file, path, entry->kind, svn_wc__props_base,
980 FALSE, pool));
981 SVN_ERR(svn_wc__prop_path(&revert_file, path, entry->kind,
982 svn_wc__props_revert, FALSE, pool));
984 SVN_ERR(svn_io_file_rename(revert_file, base_file, pool));
985 return SVN_NO_ERROR;
987 #endif
989 /*---------------------------------------------------------------------*/
991 /*** Merging propchanges into the working copy ***/
994 /* Parse FROM_PROP_VAL and TO_PROP_VAL into mergeinfo hashes, and
995 calculate the deltas between them. */
996 static svn_error_t *
997 diff_mergeinfo_props(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
998 const svn_string_t *from_prop_val,
999 const svn_string_t *to_prop_val, apr_pool_t *pool)
1001 if (svn_string_compare(from_prop_val, to_prop_val))
1003 /* Don't bothering parsing identical mergeinfo. */
1004 *deleted = apr_hash_make(pool);
1005 *added = apr_hash_make(pool);
1007 else
1009 svn_mergeinfo_t from, to;
1010 SVN_ERR(svn_mergeinfo_parse(&from, from_prop_val->data, pool));
1011 SVN_ERR(svn_mergeinfo_parse(&to, to_prop_val->data, pool));
1012 SVN_ERR(svn_mergeinfo_diff(deleted, added, from, to,
1013 FALSE, pool));
1015 return SVN_NO_ERROR;
1018 /* Parse the mergeinfo from PROP_VAL1 and PROP_VAL2, combine it, then
1019 reconstitute it into *OUTPUT. Call when the WC's mergeinfo has
1020 been modified to combine it with incoming mergeinfo from the
1021 repos. */
1022 static svn_error_t *
1023 combine_mergeinfo_props(const svn_string_t **output,
1024 const svn_string_t *prop_val1,
1025 const svn_string_t *prop_val2,
1026 apr_pool_t *pool)
1028 svn_mergeinfo_t mergeinfo1, mergeinfo2;
1029 SVN_ERR(svn_mergeinfo_parse(&mergeinfo1, prop_val1->data, pool));
1030 SVN_ERR(svn_mergeinfo_parse(&mergeinfo2, prop_val2->data, pool));
1031 SVN_ERR(svn_mergeinfo_merge(mergeinfo1, mergeinfo2, pool));
1032 SVN_ERR(svn_mergeinfo_to_string((svn_string_t **)output, mergeinfo1, pool));
1033 return SVN_NO_ERROR;
1036 /* Perform a 3-way merge operation on mergeinfo. FROM_PROP_VAL is
1037 the "base" property value, WORKING_PROP_VAL is the current value,
1038 and TO_PROP_VAL is the new value. */
1039 static svn_error_t *
1040 combine_forked_mergeinfo_props(const svn_string_t **output,
1041 const svn_string_t *from_prop_val,
1042 const svn_string_t *working_prop_val,
1043 const svn_string_t *to_prop_val,
1044 apr_pool_t *pool)
1046 svn_mergeinfo_t from_mergeinfo, l_deleted, l_added, r_deleted, r_added;
1048 /* ### OPTIMIZE: Use from_mergeinfo when diff'ing. */
1049 SVN_ERR(diff_mergeinfo_props(&l_deleted, &l_added, from_prop_val,
1050 working_prop_val, pool));
1051 SVN_ERR(diff_mergeinfo_props(&r_deleted, &r_added, from_prop_val,
1052 to_prop_val, pool));
1053 SVN_ERR(svn_mergeinfo_merge(l_deleted, r_deleted, pool));
1054 SVN_ERR(svn_mergeinfo_merge(l_added, r_added, pool));
1056 /* Apply the combined deltas to the base. */
1057 SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo, from_prop_val->data, pool));
1058 SVN_ERR(svn_mergeinfo_merge(from_mergeinfo, l_added, pool));
1060 SVN_ERR(svn_mergeinfo_remove(&from_mergeinfo, l_deleted,
1061 from_mergeinfo, pool));
1063 return svn_mergeinfo_to_string((svn_string_t **)output, from_mergeinfo, pool);
1067 svn_error_t *
1068 svn_wc_merge_props(svn_wc_notify_state_t *state,
1069 const char *path,
1070 svn_wc_adm_access_t *adm_access,
1071 apr_hash_t *baseprops,
1072 const apr_array_header_t *propchanges,
1073 svn_boolean_t base_merge,
1074 svn_boolean_t dry_run,
1075 apr_pool_t *pool)
1077 return svn_wc_merge_props2(state, path, adm_access, baseprops, propchanges,
1078 base_merge, dry_run, NULL, NULL, pool);
1082 svn_error_t *
1083 svn_wc_merge_props2(svn_wc_notify_state_t *state,
1084 const char *path,
1085 svn_wc_adm_access_t *adm_access,
1086 apr_hash_t *baseprops,
1087 const apr_array_header_t *propchanges,
1088 svn_boolean_t base_merge,
1089 svn_boolean_t dry_run,
1090 svn_wc_conflict_resolver_func_t conflict_func,
1091 void *conflict_baton,
1092 apr_pool_t *pool)
1094 const svn_wc_entry_t *entry;
1095 svn_stringbuf_t *log_accum;
1097 /* IMPORTANT: svn_wc_merge_prop_diffs relies on the fact that baseprops
1098 may be NULL. */
1100 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
1102 /* Notice that we're not using svn_path_split_if_file(), because
1103 that looks at the actual working file. Its existence shouldn't
1104 matter, so we're looking at entry->kind instead. */
1105 switch (entry->kind)
1107 case svn_node_dir:
1108 case svn_node_file:
1109 break;
1110 default:
1111 return SVN_NO_ERROR; /* ### svn_node_none or svn_node_unknown */
1114 if (! dry_run)
1115 log_accum = svn_stringbuf_create("", pool);
1117 /* Note that while this routine does the "real" work, it's only
1118 prepping tempfiles and writing log commands. */
1119 SVN_ERR(svn_wc__merge_props(state, adm_access, path, baseprops, NULL, NULL,
1120 propchanges, base_merge, dry_run,
1121 conflict_func, conflict_baton, pool, &log_accum));
1123 if (! dry_run)
1125 SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool));
1126 SVN_ERR(svn_wc__run_log(adm_access, NULL, pool));
1129 return SVN_NO_ERROR;
1134 /* Set the value of *STATE to NEW_VALUE if STATE is not NULL
1135 * and NEW_VALUE is a higer order value than *STATE's current value
1136 * using this ordering (lower order first):
1138 * - unknown, unchanged, inapplicable
1139 * - changed
1140 * - merged
1141 * - missing
1142 * - obstructed
1143 * - conflicted
1146 static void
1147 set_prop_merge_state(svn_wc_notify_state_t *state,
1148 svn_wc_notify_state_t new_value)
1150 static char ordering[] =
1151 { svn_wc_notify_state_unknown,
1152 svn_wc_notify_state_unchanged,
1153 svn_wc_notify_state_inapplicable,
1154 svn_wc_notify_state_changed,
1155 svn_wc_notify_state_merged,
1156 svn_wc_notify_state_obstructed,
1157 svn_wc_notify_state_conflicted };
1158 int state_pos = 0, i;
1160 if (! state)
1161 return;
1163 /* Find *STATE in our ordering */
1164 for (i = 0; i < sizeof(ordering); i++)
1166 if (*state == ordering[i])
1168 state_pos = i;
1169 break;
1173 /* Find NEW_VALUE in our ordering
1174 * We don't need to look further than where we found *STATE though:
1175 * If we find our value, it's order is too low.
1176 * If we don't find it, we'll want to set it, no matter its order.
1179 for (i = 0; i <= state_pos; i++)
1181 if (new_value == ordering[i])
1182 return;
1185 *state = new_value;
1190 /* Helper function for the three apply_* functions below, used when
1191 * merging properties together.
1193 * Given property PROPNAME on PATH, and four possible property values,
1194 * generate four tmpfiles and pass them to CONFLICT_FUNC callback.
1195 * This gives the client an opportunity to interactively resolve the
1196 * property conflict. (ADM_ACCESS provides the ability to examine
1197 * PATH's entries.)
1199 * BASE_VAL/WORKING_VAL represent the current state of the working
1200 * copy, and OLD_VAL/NEW_VAL represents the incoming propchange. Any
1201 * of these values might be NULL, indicating either non-existence or
1202 * intent-to-delete.
1204 * If the callback isn't available, or if it responds with
1205 * 'choose_postpone', then set *CONFLICT_REMAINS to true and return.
1207 * If the callback responds with a choice of 'base', 'theirs', 'mine',
1208 * or 'merged', then install the proper value into WORKING_PROPS and
1209 * set *CONFLICT_REMAINS to false.
1212 static svn_error_t *
1213 maybe_generate_propconflict(svn_boolean_t *conflict_remains,
1214 const char *path,
1215 svn_wc_adm_access_t *adm_access,
1216 svn_boolean_t is_dir,
1217 const char *propname,
1218 apr_hash_t *working_props,
1219 const svn_string_t *old_val,
1220 const svn_string_t *new_val,
1221 const svn_string_t *base_val,
1222 const svn_string_t *working_val,
1223 svn_wc_conflict_resolver_func_t conflict_func,
1224 void *conflict_baton,
1225 apr_pool_t *pool)
1227 svn_wc_conflict_result_t *result = NULL;
1228 svn_string_t *mime_propval = NULL;
1229 apr_file_t *working_file, *base_file, *new_file, *merged_file;
1230 const char *working_path, *base_path, *new_path, *merged_path;
1231 apr_pool_t *filepool = svn_pool_create(pool);
1232 svn_wc_conflict_description_t *cdesc;
1234 if (! conflict_func)
1236 /* Just postpone the conflict. */
1237 *conflict_remains = TRUE;
1238 return SVN_NO_ERROR;
1241 cdesc = apr_pcalloc(pool, sizeof(*cdesc));
1243 /* Create a tmpfile for each of the string_t's we've got. */
1244 if (working_val)
1246 SVN_ERR(svn_io_open_unique_file2(&working_file, &working_path,
1247 path, ".tmp",
1248 svn_io_file_del_on_pool_cleanup,
1249 filepool));
1250 SVN_ERR(svn_io_file_write_full(working_file, working_val->data,
1251 working_val->len, NULL, filepool));
1252 SVN_ERR(svn_io_file_close(working_file, filepool));
1253 cdesc->my_file = working_path;
1256 if (new_val)
1258 SVN_ERR(svn_io_open_unique_file2(&new_file, &new_path,
1259 path, ".tmp",
1260 svn_io_file_del_on_pool_cleanup,
1261 filepool));
1262 SVN_ERR(svn_io_file_write_full(new_file, new_val->data,
1263 new_val->len, NULL, filepool));
1264 SVN_ERR(svn_io_file_close(new_file, filepool));
1265 cdesc->their_file = new_path;
1268 if (!base_val && !old_val)
1270 /* If base and old are both NULL, then that's fine, we just let
1271 base_file stay NULL as-is. Both agents are attempting to add a
1272 new property. */
1275 else if ((base_val && !old_val)
1276 || (!base_val && old_val))
1278 /* If only one of base and old are defined, then we've got a
1279 situation where one agent is attempting to add the property
1280 for the first time, and the other agent is changing a
1281 property it thinks already exists. In this case, we return
1282 whichever older-value happens to be defined, so that the
1283 conflict-callback can still attempt a 3-way merge. */
1285 const svn_string_t *the_val = base_val ? base_val : old_val;
1286 SVN_ERR(svn_io_open_unique_file2(&base_file, &base_path,
1287 path, ".tmp",
1288 svn_io_file_del_on_pool_cleanup,
1289 filepool));
1290 SVN_ERR(svn_io_file_write_full(base_file,
1291 the_val->data,
1292 the_val->len, NULL, filepool));
1293 SVN_ERR(svn_io_file_close(base_file, filepool));
1294 cdesc->base_file = base_path;
1297 else /* base and old are both non-NULL */
1299 const svn_string_t *the_val;
1301 if (! svn_string_compare(base_val, old_val))
1303 /* What happens if 'base' and 'old' don't match up? In an
1304 ideal situation, they would. But if they don't, this is
1305 a classic example of a patch 'hunk' failing to apply due
1306 to a lack of context. For example: imagine that the user
1307 is busy changing the property from a value of "cat" to
1308 "dog", but the incoming propchange wants to change the
1309 same property value from "red" to "green". Total context
1310 mismatch.
1312 HOWEVER: we can still pass one of the two base values as
1313 'base_file' to the callback anyway. It's still useful to
1314 present the working and new values to the user to
1315 compare. */
1317 if (working_val && svn_string_compare(base_val, working_val))
1318 the_val = old_val;
1319 else
1320 the_val = base_val;
1322 else
1324 the_val = base_val;
1327 SVN_ERR(svn_io_open_unique_file2(&base_file, &base_path,
1328 path, ".tmp",
1329 svn_io_file_del_on_pool_cleanup,
1330 filepool));
1331 SVN_ERR(svn_io_file_write_full(base_file, the_val->data,
1332 the_val->len, NULL, filepool));
1333 SVN_ERR(svn_io_file_close(base_file, filepool));
1334 cdesc->base_file = base_path;
1336 if (working_val && new_val)
1338 svn_stream_t *mergestream;
1339 svn_diff_t *diff;
1340 svn_diff_file_options_t *options =
1341 svn_diff_file_options_create(filepool);
1343 SVN_ERR(svn_io_open_unique_file2(&merged_file, &merged_path,
1344 path, ".tmp",
1345 svn_io_file_del_on_pool_cleanup,
1346 filepool));
1347 mergestream = svn_stream_from_aprfile2(merged_file, FALSE,
1348 filepool);
1349 SVN_ERR(svn_diff_mem_string_diff3(&diff, the_val, working_val,
1350 new_val, options, filepool));
1351 SVN_ERR(svn_diff_mem_string_output_merge
1352 (mergestream, diff, the_val, working_val, new_val,
1353 NULL, NULL, NULL, NULL, FALSE, FALSE, filepool));
1354 svn_stream_close(mergestream);
1356 cdesc->merged_file = merged_path;
1360 /* Build the rest of the description object: */
1361 cdesc->path = path;
1362 cdesc->node_kind = is_dir ? svn_node_dir : svn_node_file;
1363 cdesc->kind = svn_wc_conflict_kind_property;
1364 cdesc->property_name = propname;
1365 cdesc->access = adm_access;
1367 if (!is_dir && working_props)
1368 mime_propval = apr_hash_get(working_props, SVN_PROP_MIME_TYPE,
1369 APR_HASH_KEY_STRING);
1370 cdesc->mime_type = mime_propval ? mime_propval->data : NULL;
1371 cdesc->is_binary = mime_propval ?
1372 svn_mime_type_is_binary(mime_propval->data) : FALSE;
1374 if (!old_val && new_val)
1375 cdesc->action = svn_wc_conflict_action_add;
1376 else if (old_val && !new_val)
1377 cdesc->action = svn_wc_conflict_action_delete;
1378 else
1379 cdesc->action = svn_wc_conflict_action_edit;
1381 if (base_val && !working_val)
1382 cdesc->reason = svn_wc_conflict_reason_deleted;
1383 else if (!base_val && working_val)
1384 cdesc->reason = svn_wc_conflict_reason_obstructed;
1385 else
1386 cdesc->reason = svn_wc_conflict_reason_edited;
1388 /* Invoke the interactive conflict callback. */
1389 SVN_ERR(conflict_func(&result, cdesc, conflict_baton, pool));
1390 if (result == NULL)
1392 *conflict_remains = TRUE;
1393 return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
1394 NULL, _("Conflict callback violated API:"
1395 " returned no results."));
1398 switch (result->choice)
1400 default:
1401 case svn_wc_conflict_choose_postpone:
1403 *conflict_remains = TRUE;
1404 break;
1406 case svn_wc_conflict_choose_mine_full:
1408 /* No need to change working_props; it already contains working_val */
1409 *conflict_remains = FALSE;
1410 break;
1412 /* I think _mine_full and _theirs_full are appropriate for prop
1413 behavior as well as the text behavior. There should even be
1414 analogous behaviors for _mine and _theirs when those are
1415 ready, namely: fold in all non-conflicting prop changes, and
1416 then choose _mine side or _theirs side for conflicting ones. */
1417 case svn_wc_conflict_choose_theirs_full:
1419 apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, new_val);
1420 *conflict_remains = FALSE;
1421 break;
1423 case svn_wc_conflict_choose_base:
1425 apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, base_val);
1426 *conflict_remains = FALSE;
1427 break;
1429 case svn_wc_conflict_choose_merged:
1431 if (!cdesc->merged_file && !result->merged_file)
1432 return svn_error_create
1433 (SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
1434 NULL, _("Conflict callback violated API:"
1435 " returned no merged file."));
1436 else
1438 svn_stringbuf_t *merged_stringbuf;
1439 svn_string_t *merged_string;
1441 SVN_ERR(svn_stringbuf_from_file2(&merged_stringbuf,
1442 result->merged_file ?
1443 result->merged_file :
1444 cdesc->merged_file,
1445 pool));
1446 merged_string = svn_string_create_from_buf(merged_stringbuf, pool);
1447 apr_hash_set(working_props, propname,
1448 APR_HASH_KEY_STRING, merged_string);
1449 *conflict_remains = FALSE;
1451 break;
1455 /* Delete any tmpfiles we made. */
1456 svn_pool_destroy(filepool);
1458 return SVN_NO_ERROR;
1462 /* Add the property with name PROPNAME to the set of WORKING_PROPS on
1463 * PATH, setting *STATE or *CONFLICT according to merge outcomes.
1465 * *STATE is an input and output parameter, its value is to be
1466 * set using set_merge_prop_state().
1468 * BASE_VAL contains the working copy base property value
1470 * NEW_VAL contains the value to be set.
1472 * CONFLICT_FUNC/BATON is a callback to be called before declaring a
1473 * property conflict; it gives the client a chance to resolve the
1474 * conflict interactively. It uses ADM_ACCESS to possibly examine
1475 * PATH's entries.
1477 static svn_error_t *
1478 apply_single_prop_add(svn_wc_notify_state_t *state,
1479 const char *path,
1480 svn_boolean_t is_dir,
1481 apr_hash_t *working_props,
1482 svn_string_t **conflict,
1483 const char *propname,
1484 const svn_string_t *base_val,
1485 const svn_string_t *new_val,
1486 svn_wc_conflict_resolver_func_t conflict_func,
1487 void *conflict_baton,
1488 svn_wc_adm_access_t *adm_access,
1489 apr_pool_t *pool)
1492 svn_boolean_t got_conflict = FALSE;
1493 svn_string_t *working_val
1494 = apr_hash_get(working_props, propname, APR_HASH_KEY_STRING);
1496 if (working_val)
1498 /* the property already exists in working_props... */
1500 if (svn_string_compare(working_val, new_val))
1501 /* The value we want is already there, so it's a merge. */
1502 set_prop_merge_state(state, svn_wc_notify_state_merged);
1504 else
1506 /* The WC difference doesn't match the new value.
1507 We only merge mergeinfo; other props conflict */
1508 if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
1510 SVN_ERR(combine_mergeinfo_props(&new_val, working_val,
1511 new_val, pool));
1512 apr_hash_set(working_props, propname,
1513 APR_HASH_KEY_STRING, new_val);
1514 set_prop_merge_state(state, svn_wc_notify_state_merged);
1516 else
1518 SVN_ERR(maybe_generate_propconflict(&got_conflict, path,
1519 adm_access, is_dir,
1520 propname, working_props,
1521 NULL, new_val,
1522 base_val, working_val,
1523 conflict_func, conflict_baton,
1524 pool));
1525 if (got_conflict)
1526 *conflict = svn_string_createf
1527 (pool,
1528 _("Trying to add new property '%s' with value "
1529 "'%s',\nbut property already exists with value '%s'."),
1530 propname, new_val->data, working_val->data);
1534 else if (base_val)
1536 SVN_ERR(maybe_generate_propconflict(&got_conflict, path, adm_access,
1537 is_dir, propname,
1538 working_props, NULL, new_val,
1539 base_val, NULL,
1540 conflict_func, conflict_baton, pool));
1541 if (got_conflict)
1542 *conflict = svn_string_createf
1543 (pool, _("Trying to create property '%s' with value '%s',\n"
1544 "but it has been locally deleted."),
1545 propname, new_val->data);
1547 else /* property doesn't yet exist in working_props... */
1548 /* so just set it */
1549 apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, new_val);
1551 return SVN_NO_ERROR;
1555 /* Delete the property with name PROPNAME from the set of
1556 * WORKING_PROPS on PATH, setting *STATE or *CONFLICT according to
1557 * merge outcomes.
1559 * *STATE is an input and output parameter, its value is to be
1560 * set using set_merge_prop_state().
1562 * BASE_VAL contains the working copy base property value
1564 * OLD_VAL contains the value the of the property the server
1565 * thinks it's deleting.
1567 * CONFLICT_FUNC/BATON is a callback to be called before declaring a
1568 * property conflict; it gives the client a chance to resolve the
1569 * conflict interactively. It uses ADM_ACCESS to possibly examine
1570 * PATH's entries.
1572 static svn_error_t *
1573 apply_single_prop_delete(svn_wc_notify_state_t *state,
1574 const char *path,
1575 svn_boolean_t is_dir,
1576 apr_hash_t *working_props,
1577 svn_string_t **conflict,
1578 const char *propname,
1579 const svn_string_t *base_val,
1580 const svn_string_t *old_val,
1581 svn_wc_conflict_resolver_func_t conflict_func,
1582 void *conflict_baton,
1583 svn_wc_adm_access_t *adm_access,
1584 apr_pool_t *pool)
1586 svn_boolean_t got_conflict = FALSE;
1587 svn_string_t *working_val
1588 = apr_hash_get(working_props, propname, APR_HASH_KEY_STRING);
1590 if (! base_val)
1592 apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, NULL);
1593 if (old_val)
1594 /* This is a merge, merging a delete into non-existent */
1595 set_prop_merge_state(state, svn_wc_notify_state_merged);
1598 else if (svn_string_compare(base_val, old_val))
1600 if (working_val)
1602 if (svn_string_compare(working_val, old_val))
1603 /* they have the same values, so it's an update */
1604 apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, NULL);
1605 else
1607 SVN_ERR(maybe_generate_propconflict(&got_conflict, path,
1608 adm_access, is_dir,
1609 propname, working_props,
1610 old_val, NULL,
1611 base_val, working_val,
1612 conflict_func, conflict_baton,
1613 pool));
1614 if (got_conflict)
1615 *conflict = svn_string_createf
1616 (pool,
1617 _("Trying to delete property '%s' with value '%s'\n"
1618 "but it has been modified from '%s' to '%s'."),
1619 propname, old_val->data,
1620 base_val->data, working_val->data);
1623 else
1624 /* The property is locally deleted, so it's a merge */
1625 set_prop_merge_state(state, svn_wc_notify_state_merged);
1628 else
1630 SVN_ERR(maybe_generate_propconflict(&got_conflict, path, adm_access,
1631 is_dir, propname,
1632 working_props, old_val, NULL,
1633 base_val, working_val,
1634 conflict_func, conflict_baton, pool));
1635 if (got_conflict)
1636 *conflict = svn_string_createf
1637 (pool,
1638 _("Trying to delete property '%s' with value '%s'\n"
1639 "but the local value is '%s'."),
1640 propname, base_val->data, working_val->data);
1643 return SVN_NO_ERROR;
1647 /* Change the property with name PROPNAME in the set of WORKING_PROPS
1648 * on PATH, setting *STATE or *CONFLICT according to merge outcomes.
1650 * *STATE is an input and output parameter, its value is to be
1651 * set using set_merge_prop_state().
1653 * BASE_VAL contains the working copy base property value
1655 * OLD_VAL contains the value the of the property the server
1656 * thinks it's overwriting
1658 * NEW_VAL contains the value to be set.
1660 * CONFLICT_FUNC/BATON is a callback to be called before declaring a
1661 * property conflict; it gives the client a chance to resolve the
1662 * conflict interactively. It uses ADM_ACCESS to possibly examine the
1663 * path's entries.
1665 static svn_error_t *
1666 apply_single_prop_change(svn_wc_notify_state_t *state,
1667 const char *path,
1668 svn_boolean_t is_dir,
1669 apr_hash_t *working_props,
1670 svn_string_t **conflict,
1671 const char *propname,
1672 const svn_string_t *base_val,
1673 const svn_string_t *old_val,
1674 const svn_string_t *new_val,
1675 svn_wc_conflict_resolver_func_t conflict_func,
1676 void *conflict_baton,
1677 svn_wc_adm_access_t *adm_access,
1678 apr_pool_t *pool)
1680 svn_boolean_t got_conflict = FALSE;
1681 svn_string_t *working_val
1682 = apr_hash_get(working_props, propname, APR_HASH_KEY_STRING);
1684 if ((working_val && ! base_val)
1685 || (! working_val && base_val)
1686 || (working_val && base_val
1687 && !svn_string_compare(working_val, base_val)))
1689 /* Locally changed property */
1690 if (working_val)
1692 if (svn_string_compare(working_val, new_val))
1693 /* The new value equals the changed value: a merge */
1694 set_prop_merge_state(state, svn_wc_notify_state_merged);
1695 else
1697 if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
1699 /* We have base, WC, and new values. Discover
1700 deltas between base <-> WC, and base <->
1701 incoming. Combine those deltas, and apply
1702 them to base to get the new value. */
1703 SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
1704 working_val,
1705 new_val, pool));
1706 apr_hash_set(working_props, propname,
1707 APR_HASH_KEY_STRING, new_val);
1708 set_prop_merge_state(state, svn_wc_notify_state_merged);
1710 else
1712 SVN_ERR(maybe_generate_propconflict(&got_conflict,
1713 path, adm_access, is_dir,
1714 propname, working_props,
1715 old_val, new_val,
1716 base_val, working_val,
1717 conflict_func,
1718 conflict_baton,
1719 pool));
1720 if (got_conflict)
1722 if (base_val)
1723 *conflict = svn_string_createf
1724 (pool,
1725 _("Trying to change property '%s' from '%s' to '%s',\n"
1726 "but property has been locally changed "
1727 "from '%s' to '%s'."),
1728 propname, old_val->data, new_val->data,
1729 base_val->data, working_val->data);
1730 else
1731 *conflict = svn_string_createf
1732 (pool,
1733 _("Trying to change property '%s' from '%s' to '%s',\n"
1734 "but property has been locally added with "
1735 "value '%s'"),
1736 propname, old_val->data, new_val->data,
1737 working_val->data);
1743 else
1745 SVN_ERR(maybe_generate_propconflict(&got_conflict, path, adm_access,
1746 is_dir, propname, working_props,
1747 old_val, new_val,
1748 base_val, working_val,
1749 conflict_func, conflict_baton,
1750 pool));
1751 if (got_conflict)
1752 *conflict = svn_string_createf
1753 (pool,
1754 _("Trying to change property '%s' from '%s' to '%s',\n"
1755 "but it has been locally deleted."),
1756 propname, old_val->data, new_val->data);
1760 else if (! working_val) /* means !working_val && !base_val due
1761 to conditions above: no prop at all */
1763 if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
1765 /* Discover any mergeinfo additions in the
1766 incoming value relative to the base, and
1767 "combine" those with the empty WC value. */
1768 svn_mergeinfo_t deleted_mergeinfo, added_mergeinfo;
1769 SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo,
1770 &added_mergeinfo,
1771 old_val, new_val, pool));
1772 SVN_ERR(svn_mergeinfo_to_string((svn_string_t **)&new_val,
1773 added_mergeinfo, pool));
1774 apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, new_val);
1776 else
1778 SVN_ERR(maybe_generate_propconflict(&got_conflict, path, adm_access,
1779 is_dir, propname, working_props,
1780 old_val, new_val,
1781 base_val, working_val,
1782 conflict_func, conflict_baton,
1783 pool));
1784 if (got_conflict)
1785 *conflict = svn_string_createf
1786 (pool,
1787 _("Trying to change property '%s' from '%s' to '%s',\n"
1788 "but the property does not exist."),
1789 propname, old_val->data, new_val->data);
1793 else /* means working && base && svn_string_compare(working, base) */
1795 if (svn_string_compare(old_val, base_val))
1796 apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, new_val);
1798 else
1800 if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
1802 /* We have base, WC, and new values. Discover
1803 deltas between base <-> WC, and base <->
1804 incoming. Combine those deltas, and apply
1805 them to base to get the new value. */
1806 SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
1807 working_val,
1808 new_val, pool));
1809 apr_hash_set(working_props, propname,
1810 APR_HASH_KEY_STRING, new_val);
1811 set_prop_merge_state(state, svn_wc_notify_state_merged);
1813 else
1815 SVN_ERR(maybe_generate_propconflict(&got_conflict, path,
1816 adm_access, is_dir,
1817 propname, working_props,
1818 old_val, new_val,
1819 base_val, working_val,
1820 conflict_func, conflict_baton,
1821 pool));
1822 if (got_conflict)
1823 *conflict = svn_string_createf
1824 (pool,
1825 _("Trying to change property '%s' from '%s' to '%s',\n"
1826 "but property already exists with value '%s'."),
1827 propname, old_val->data, new_val->data,
1828 working_val->data);
1833 return SVN_NO_ERROR;
1837 svn_error_t *
1838 svn_wc__merge_props(svn_wc_notify_state_t *state,
1839 svn_wc_adm_access_t *adm_access,
1840 const char *path,
1841 apr_hash_t *server_baseprops,
1842 apr_hash_t *base_props,
1843 apr_hash_t *working_props,
1844 const apr_array_header_t *propchanges,
1845 svn_boolean_t base_merge,
1846 svn_boolean_t dry_run,
1847 svn_wc_conflict_resolver_func_t conflict_func,
1848 void *conflict_baton,
1849 apr_pool_t *pool,
1850 svn_stringbuf_t **entry_accum)
1852 int i;
1853 svn_boolean_t is_dir;
1855 const char *reject_path = NULL;
1856 apr_file_t *reject_tmp_fp = NULL; /* the temporary conflicts file */
1857 const char *reject_tmp_path = NULL;
1859 if (! svn_path_is_child(svn_wc_adm_access_path(adm_access), path, NULL))
1860 is_dir = TRUE;
1861 else
1862 is_dir = FALSE;
1864 /* If not provided, load the base & working property files into hashes */
1865 if (! base_props || ! working_props)
1866 SVN_ERR(svn_wc__load_props(base_props ? NULL : &base_props,
1867 working_props ? NULL : &working_props,
1868 NULL, adm_access, path, pool));
1869 if (!server_baseprops)
1870 server_baseprops = base_props;
1872 if (state)
1874 /* Start out assuming no changes or conflicts. Don't bother to
1875 examine propchanges->nelts yet; even if we knew there were
1876 propchanges, we wouldn't yet know if they are "normal" props,
1877 as opposed wc or entry props. */
1878 *state = svn_wc_notify_state_unchanged;
1881 /* Looping over the array of incoming propchanges we want to apply: */
1882 for (i = 0; i < propchanges->nelts; i++)
1884 const char *propname;
1885 svn_string_t *conflict = NULL;
1886 const svn_prop_t *incoming_change;
1887 const svn_string_t *from_val, *to_val, *working_val, *base_val;
1888 svn_boolean_t is_normal;
1890 /* For the incoming propchange, figure out the TO and FROM values. */
1891 incoming_change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
1892 propname = incoming_change->name;
1893 is_normal = svn_wc_is_normal_prop(propname);
1894 to_val = incoming_change->value
1895 ? svn_string_dup(incoming_change->value, pool) : NULL;
1896 from_val = apr_hash_get(server_baseprops, propname, APR_HASH_KEY_STRING);
1898 working_val = apr_hash_get(working_props, propname, APR_HASH_KEY_STRING);
1899 base_val = apr_hash_get(base_props, propname, APR_HASH_KEY_STRING);
1901 if (base_merge)
1902 apr_hash_set(base_props, propname, APR_HASH_KEY_STRING, to_val);
1904 /* We already know that state is at least `changed', so mark
1905 that, but remember that we may later upgrade to `merged' or
1906 even `conflicted'. */
1907 if (is_normal)
1908 set_prop_merge_state(state, svn_wc_notify_state_changed);
1910 if (! from_val) /* adding a new property */
1911 SVN_ERR(apply_single_prop_add(is_normal ? state : NULL, path, is_dir,
1912 working_props, &conflict,
1913 propname, base_val, to_val,
1914 conflict_func, conflict_baton,
1915 adm_access, pool));
1917 else if (! to_val) /* delete an existing property */
1918 SVN_ERR(apply_single_prop_delete(is_normal ? state : NULL, path, is_dir,
1919 working_props, &conflict,
1920 propname, base_val, from_val,
1921 conflict_func, conflict_baton,
1922 adm_access, pool));
1924 else /* changing an existing property */
1925 SVN_ERR(apply_single_prop_change(is_normal ? state : NULL, path, is_dir,
1926 working_props, &conflict,
1927 propname, base_val, from_val, to_val,
1928 conflict_func, conflict_baton,
1929 adm_access, pool));
1932 /* merging logic complete, now we need to possibly log conflict
1933 data to tmpfiles. */
1935 if (conflict)
1937 if (is_normal)
1938 set_prop_merge_state(state, svn_wc_notify_state_conflicted);
1940 if (dry_run)
1941 continue; /* skip to next incoming change */
1943 if (! reject_tmp_fp)
1944 /* This is the very first prop conflict found on this item. */
1945 SVN_ERR(open_reject_tmp_file(&reject_tmp_fp, &reject_tmp_path,
1946 path, adm_access, is_dir,
1947 pool));
1949 /* Append the conflict to the open tmp/PROPS/---.prej file */
1950 SVN_ERR(append_prop_conflict(reject_tmp_fp, conflict, pool));
1953 } /* foreach propchange ... */
1955 /* Finished applying all incoming propchanges to our hashes! */
1957 if (dry_run)
1958 return SVN_NO_ERROR;
1960 SVN_ERR(svn_wc__install_props(entry_accum, adm_access, path,
1961 base_props, working_props, base_merge,
1962 pool));
1964 if (reject_tmp_fp)
1966 /* There's a .prej file sitting in .svn/tmp/ somewhere. Deal
1967 with the conflicts. */
1969 /* First, _close_ this temporary conflicts file. We've been
1970 appending to it all along. */
1971 SVN_ERR(svn_io_file_close(reject_tmp_fp, pool));
1973 /* Now try to get the name of a pre-existing .prej file from the
1974 entries file */
1975 SVN_ERR(get_existing_prop_reject_file(&reject_path,
1976 adm_access, path, pool));
1978 if (! reject_path)
1980 /* Reserve a new .prej file *above* the .svn/ directory by
1981 opening and closing it. */
1982 const char *full_reject_path;
1984 full_reject_path = (!is_dir) ? path :
1985 svn_path_join(path, SVN_WC__THIS_DIR_PREJ, pool);
1987 SVN_ERR(svn_io_open_unique_file2(NULL, &reject_path,
1988 full_reject_path,
1989 SVN_WC__PROP_REJ_EXT,
1990 svn_io_file_del_none, pool));
1992 /* This file will be overwritten when the log is run; that's
1993 ok, because at least now we have a reservation on
1994 disk. */
1997 /* We've now guaranteed that some kind of .prej file exists
1998 above the .svn/ dir. We write log entries to append our
1999 conflicts to it. */
2000 SVN_ERR(svn_wc__loggy_append(entry_accum, adm_access,
2001 reject_tmp_path, reject_path, pool));
2003 /* And of course, delete the temporary reject file. */
2004 SVN_ERR(svn_wc__loggy_remove(entry_accum, adm_access,
2005 reject_tmp_path, pool));
2007 /* Mark entry as "conflicted" with a particular .prej file. */
2009 svn_wc_entry_t entry;
2011 entry.prejfile = svn_path_is_child(svn_wc_adm_access_path(adm_access),
2012 reject_path, NULL);
2013 SVN_ERR(svn_wc__loggy_entry_modify(entry_accum,
2014 adm_access,
2015 path,
2016 &entry,
2017 SVN_WC__ENTRY_MODIFY_PREJFILE,
2018 pool));
2021 } /* if (reject_tmp_fp) */
2023 return SVN_NO_ERROR;
2027 /* This is DEPRECATED, use svn_wc_merge_props() instead. */
2028 svn_error_t *
2029 svn_wc_merge_prop_diffs(svn_wc_notify_state_t *state,
2030 const char *path,
2031 svn_wc_adm_access_t *adm_access,
2032 const apr_array_header_t *propchanges,
2033 svn_boolean_t base_merge,
2034 svn_boolean_t dry_run,
2035 apr_pool_t *pool)
2037 /* NOTE: Here, we use implementation knowledge. The public
2038 svn_wc_merge_props doesn't allow NULL as baseprops argument, but we know
2039 that it works. */
2040 return svn_wc_merge_props(state, path, adm_access, NULL, propchanges,
2041 base_merge, dry_run, pool);
2046 /*------------------------------------------------------------------*/
2048 /*** Private 'wc prop' functions ***/
2051 svn_error_t *
2052 svn_wc__wcprop_list(apr_hash_t **wcprops,
2053 const char *entryname,
2054 svn_wc_adm_access_t *adm_access,
2055 apr_pool_t *pool)
2057 const char *prop_path;
2058 const svn_wc_entry_t *entry;
2059 apr_hash_t *all_wcprops;
2060 apr_pool_t *cache_pool = svn_wc_adm_access_pool(adm_access);
2061 const char *path = svn_path_join(svn_wc_adm_access_path(adm_access),
2062 entryname, pool);
2064 SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
2065 if (! entry)
2067 /* No entry exists, therefore no wcprop-file can exist */
2068 *wcprops = apr_hash_make(pool);
2069 return SVN_NO_ERROR;
2072 /* Try the cache first. */
2073 all_wcprops = svn_wc__adm_access_wcprops(adm_access);
2074 if (! all_wcprops)
2076 SVN_ERR(read_wcprops(adm_access, pool));
2077 all_wcprops = svn_wc__adm_access_wcprops(adm_access);
2079 if (all_wcprops)
2081 *wcprops = apr_hash_get(all_wcprops, entryname, APR_HASH_KEY_STRING);
2082 /* The cache contains no hash tables for empty proplist, so we just
2083 create one here if that's the case. */
2084 if (! *wcprops)
2086 *wcprops = apr_hash_make(cache_pool);
2087 entryname = apr_pstrdup(cache_pool, entryname);
2088 apr_hash_set(all_wcprops, entryname, APR_HASH_KEY_STRING, *wcprops);
2090 return SVN_NO_ERROR;
2093 /* Fall back on individual files for backwards compatibility. */
2095 /* Construct a path to the relevant property file */
2096 SVN_ERR(svn_wc__prop_path(&prop_path, path, entry->kind,
2097 svn_wc__props_wcprop, FALSE, pool));
2098 *wcprops = apr_hash_make(pool);
2099 SVN_ERR(load_prop_file(prop_path, *wcprops, pool));
2101 return SVN_NO_ERROR;
2105 /* Get a single 'wcprop' NAME for versioned object PATH, return in
2106 *VALUE. ADM_ACCESS is an access baton set that contains PATH. */
2107 static svn_error_t *
2108 wcprop_get(const svn_string_t **value,
2109 const char *name,
2110 const char *path,
2111 svn_wc_adm_access_t *adm_access,
2112 apr_pool_t *pool)
2114 apr_hash_t *prophash;
2115 const svn_wc_entry_t *entry;
2117 SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
2118 if (! entry)
2120 *value = NULL;
2121 return SVN_NO_ERROR;
2123 if (entry->kind == svn_node_dir)
2124 SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, path, pool));
2125 else
2126 SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access,
2127 svn_path_dirname(path, pool), pool));
2129 SVN_ERR_W(svn_wc__wcprop_list(&prophash, entry->name, adm_access, pool),
2130 _("Failed to load properties from disk"));
2132 *value = apr_hash_get(prophash, name, APR_HASH_KEY_STRING);
2133 return SVN_NO_ERROR;
2137 svn_error_t *
2138 svn_wc__wcprop_set(const char *name,
2139 const svn_string_t *value,
2140 const char *path,
2141 svn_wc_adm_access_t *adm_access,
2142 svn_boolean_t force_write,
2143 apr_pool_t *pool)
2145 apr_hash_t *prophash;
2146 apr_file_t *fp = NULL;
2147 apr_pool_t *cache_pool = svn_wc_adm_access_pool(adm_access);
2148 const svn_wc_entry_t *entry;
2150 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
2152 if (entry->kind == svn_node_dir)
2153 SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, path, pool));
2154 else
2155 SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access,
2156 svn_path_dirname(path, pool), pool));
2157 SVN_ERR_W(svn_wc__wcprop_list(&prophash, entry->name, adm_access, pool),
2158 _("Failed to load properties from disk"));
2160 /* Now we have all the properties in our hash. Simply merge the new
2161 property into it. */
2162 name = apr_pstrdup(cache_pool, name);
2163 if (value)
2164 value = svn_string_dup(value, cache_pool);
2165 apr_hash_set(prophash, name, APR_HASH_KEY_STRING, value);
2167 if (svn_wc__adm_wc_format(adm_access) > SVN_WC__WCPROPS_MANY_FILES_VERSION)
2169 if (force_write)
2170 SVN_ERR(write_wcprops(adm_access, pool));
2172 else
2174 /* For backwards compatibility. We don't use the cache in this case,
2175 so write to disk regardless of force_write. */
2176 /* Open the propfile for writing. */
2177 SVN_ERR(svn_wc__open_props(&fp,
2178 path, /* open in PATH */ entry->kind,
2179 (APR_WRITE | APR_CREATE | APR_BUFFERED),
2180 0, /* not base props */
2181 1, /* we DO want wcprops */
2182 pool));
2183 /* Write. */
2184 SVN_ERR_W(svn_hash_write(prophash, fp, pool),
2185 apr_psprintf(pool,
2186 _("Cannot write property hash for '%s'"),
2187 svn_path_local_style(path, pool)));
2189 /* Close file, doing an atomic "move". */
2190 SVN_ERR(svn_wc__close_props(fp, path, entry->kind, 0, 1,
2191 1, /* sync! */
2192 pool));
2195 return SVN_NO_ERROR;
2198 /*------------------------------------------------------------------*/
2201 /*** Public Functions ***/
2204 svn_error_t *
2205 svn_wc_prop_list(apr_hash_t **props,
2206 const char *path,
2207 svn_wc_adm_access_t *adm_access,
2208 apr_pool_t *pool)
2210 const svn_wc_entry_t *entry;
2212 SVN_ERR(svn_wc_entry(&entry, path, adm_access, TRUE, pool));
2214 /* if there is no entry, 'path' is not under version control and
2215 therefore has no props */
2216 if (! entry)
2218 *props = apr_hash_make(pool);
2219 return SVN_NO_ERROR;
2222 if (entry->kind == svn_node_dir)
2223 SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, path, pool));
2224 else
2225 SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access,
2226 svn_path_dirname(path, pool), pool));
2228 return svn_wc__load_props(NULL, props, NULL, adm_access, path, pool);
2231 /* Determine if PROPNAME is contained in the list of space separated
2232 values STRING. */
2234 static svn_boolean_t
2235 string_contains_prop(const char *string, const char *propname)
2237 const char *place = strstr(string, propname);
2238 int proplen = strlen(propname);
2240 if (!place)
2241 return FALSE;
2243 while (place)
2245 if (place[proplen] == ' ' || place[proplen] == 0)
2246 return TRUE;
2247 place = strstr(place + 1, propname);
2249 return FALSE;
2252 svn_error_t *
2253 svn_wc_prop_get(const svn_string_t **value,
2254 const char *name,
2255 const char *path,
2256 svn_wc_adm_access_t *adm_access,
2257 apr_pool_t *pool)
2259 apr_hash_t *prophash;
2260 enum svn_prop_kind kind = svn_property_kind(NULL, name);
2261 const svn_wc_entry_t *entry;
2263 SVN_ERR(svn_wc_entry(&entry, path, adm_access, TRUE, pool));
2265 if (entry == NULL)
2267 *value = NULL;
2268 return SVN_NO_ERROR;
2271 if (entry->cachable_props
2272 && string_contains_prop(entry->cachable_props, name))
2274 /* We separate these two cases so that we can return the correct
2275 value for booleans if they exist in the string. */
2276 if (!entry->present_props
2277 || !string_contains_prop(entry->present_props, name))
2279 *value = NULL;
2280 return SVN_NO_ERROR;
2282 if (svn_prop_is_boolean(name))
2284 *value = svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool);
2285 assert(*value != NULL);
2286 return SVN_NO_ERROR;
2290 if (kind == svn_prop_wc_kind)
2292 return wcprop_get(value, name, path, adm_access, pool);
2294 if (kind == svn_prop_entry_kind)
2296 return svn_error_createf /* we don't do entry properties here */
2297 (SVN_ERR_BAD_PROP_KIND, NULL,
2298 _("Property '%s' is an entry property"), name);
2300 else /* regular prop */
2302 SVN_ERR_W(svn_wc_prop_list(&prophash, path, adm_access, pool),
2303 _("Failed to load properties from disk"));
2305 *value = apr_hash_get(prophash, name, APR_HASH_KEY_STRING);
2307 return SVN_NO_ERROR;
2312 /* The special Subversion properties are not valid for all node kinds.
2313 Return an error if NAME is an invalid Subversion property for PATH which
2314 is of kind NODE_KIND. */
2315 static svn_error_t *
2316 validate_prop_against_node_kind(const char *name,
2317 const char *path,
2318 svn_node_kind_t node_kind,
2319 apr_pool_t *pool)
2322 const char *file_prohibit[] = { SVN_PROP_IGNORE,
2323 SVN_PROP_EXTERNALS,
2324 NULL };
2325 const char *dir_prohibit[] = { SVN_PROP_EXECUTABLE,
2326 SVN_PROP_KEYWORDS,
2327 SVN_PROP_EOL_STYLE,
2328 SVN_PROP_MIME_TYPE,
2329 SVN_PROP_NEEDS_LOCK,
2330 NULL };
2331 const char **node_kind_prohibit;
2332 const char *path_display
2333 = svn_path_is_url(path) ? path : svn_path_local_style(path, pool);
2335 switch (node_kind)
2337 case svn_node_dir:
2338 node_kind_prohibit = dir_prohibit;
2339 while (*node_kind_prohibit)
2340 if (strcmp(name, *node_kind_prohibit++) == 0)
2341 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
2342 _("Cannot set '%s' on a directory ('%s')"),
2343 name, path_display);
2344 break;
2345 case svn_node_file:
2346 node_kind_prohibit = file_prohibit;
2347 while (*node_kind_prohibit)
2348 if (strcmp(name, *node_kind_prohibit++) == 0)
2349 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
2350 _("Cannot set '%s' on a file ('%s')"),
2351 name,
2352 path_display);
2353 break;
2354 default:
2355 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2356 _("'%s' is not a file or directory"),
2357 path_display);
2360 return SVN_NO_ERROR;
2364 struct getter_baton {
2365 const char *path;
2366 svn_wc_adm_access_t *adm_access;
2370 static svn_error_t *
2371 get_file_for_validation(const svn_string_t **mime_type,
2372 svn_stream_t *stream,
2373 void *baton,
2374 apr_pool_t *pool)
2376 struct getter_baton *gb = baton;
2378 if (mime_type)
2379 SVN_ERR(svn_wc_prop_get(mime_type, SVN_PROP_MIME_TYPE,
2380 gb->path, gb->adm_access, pool));
2382 if (stream) {
2383 apr_file_t *fp;
2384 svn_stream_t *read_stream;
2386 /* Open PATH. */
2387 SVN_ERR(svn_io_file_open(&fp, gb->path,
2388 (APR_READ | APR_BINARY | APR_BUFFERED),
2389 0, pool));
2391 /* Get a READ_STREAM from the file we just opened. */
2392 read_stream = svn_stream_from_aprfile(fp, pool);
2394 /* Copy from the file into the translating stream. */
2395 SVN_ERR(svn_stream_copy(read_stream, stream, pool));
2397 SVN_ERR(svn_stream_close(read_stream));
2398 SVN_ERR(svn_io_file_close(fp, pool));
2401 return SVN_NO_ERROR;
2405 static svn_error_t *
2406 validate_eol_prop_against_file(const char *path,
2407 svn_wc_canonicalize_svn_prop_get_file_t getter,
2408 void *getter_baton,
2409 apr_pool_t *pool)
2411 svn_stream_t *translating_stream;
2412 svn_error_t *err;
2413 const svn_string_t *mime_type;
2414 const char *path_display
2415 = svn_path_is_url(path) ? path : svn_path_local_style(path, pool);
2417 /* First just ask the "getter" for the MIME type. */
2418 SVN_ERR(getter(&mime_type, NULL, getter_baton, pool));
2420 /* See if this file has been determined to be binary. */
2421 if (mime_type && svn_mime_type_is_binary(mime_type->data))
2422 return svn_error_createf
2423 (SVN_ERR_ILLEGAL_TARGET, NULL,
2424 _("File '%s' has binary mime type property"),
2425 path_display);
2427 /* Now ask the getter for the contents of the file; this will do a
2428 newline translation. All we really care about here is whether or
2429 not the function fails on inconsistent line endings. The
2430 function is "translating" to an empty stream. This is
2431 sneeeeeeeeeeeaky. */
2432 translating_stream = svn_subst_stream_translated(svn_stream_empty(pool),
2433 "", FALSE, NULL, FALSE,
2434 pool);
2436 err = getter(NULL, translating_stream, getter_baton, pool);
2438 if (!err)
2439 err = svn_stream_close(translating_stream);
2441 if (err && err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
2442 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, err,
2443 _("File '%s' has inconsistent newlines"),
2444 path_display);
2445 else if (err)
2446 return err;
2448 return SVN_NO_ERROR;
2452 svn_error_t *
2453 svn_wc_prop_set2(const char *name,
2454 const svn_string_t *value,
2455 const char *path,
2456 svn_wc_adm_access_t *adm_access,
2457 svn_boolean_t skip_checks,
2458 apr_pool_t *pool)
2460 apr_hash_t *prophash, *base_prophash;
2461 enum svn_prop_kind prop_kind = svn_property_kind(NULL, name);
2462 svn_stringbuf_t *log_accum = svn_stringbuf_create("", pool);
2463 const svn_wc_entry_t *entry;
2465 if (prop_kind == svn_prop_wc_kind)
2466 return svn_wc__wcprop_set(name, value, path, adm_access, TRUE, pool);
2467 else if (prop_kind == svn_prop_entry_kind)
2468 return svn_error_createf /* we don't do entry properties here */
2469 (SVN_ERR_BAD_PROP_KIND, NULL,
2470 _("Property '%s' is an entry property"), name);
2472 /* Else, handle a regular property: */
2474 /* Get the entry and name for this path. */
2475 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
2477 /* Get the access baton for the entry's directory. */
2478 if (entry->kind == svn_node_dir)
2479 SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, path, pool));
2480 else
2481 SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access,
2482 svn_path_dirname(path, pool), pool));
2484 /* Setting an inappropriate property is not allowed (unless
2485 overridden by 'skip_checks', in some circumstances). Deleting an
2486 inappropriate property is allowed, however, since older clients
2487 allowed (and other clients possibly still allow) setting it in
2488 the first place. */
2489 if (value && svn_prop_is_svn_prop(name))
2491 const svn_string_t *new_value;
2492 struct getter_baton *gb = apr_pcalloc(pool, sizeof(*gb));
2494 gb->path = path;
2495 gb->adm_access = adm_access;
2497 SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, name, value, path,
2498 entry->kind, skip_checks,
2499 get_file_for_validation, gb, pool));
2500 value = new_value;
2503 if (entry->kind == svn_node_file && strcmp(name, SVN_PROP_EXECUTABLE) == 0)
2505 /* If the svn:executable property was set, then chmod +x.
2506 If the svn:executable property was deleted (NULL value passed
2507 in), then chmod -x. */
2508 if (value == NULL)
2509 SVN_ERR(svn_io_set_file_executable(path, FALSE, TRUE, pool));
2510 else
2511 SVN_ERR(svn_io_set_file_executable(path, TRUE, TRUE, pool));
2514 if (entry->kind == svn_node_file && strcmp(name, SVN_PROP_NEEDS_LOCK) == 0)
2516 /* If the svn:needs-lock property was set to NULL, set the file
2517 to read-write */
2518 if (value == NULL)
2519 SVN_ERR(svn_io_set_file_read_write(path, FALSE, pool));
2521 /* If not, we'll set the file to read-only at commit time. */
2524 SVN_ERR_W(svn_wc__load_props(&base_prophash, &prophash, NULL,
2525 adm_access, path, pool),
2526 _("Failed to load properties from disk"));
2528 /* If we're changing this file's list of expanded keywords, then
2529 * we'll need to invalidate its text timestamp, since keyword
2530 * expansion affects the comparison of working file to text base.
2532 * Here we retrieve the old list of expanded keywords; after the
2533 * property is set, we'll grab the new list and see if it differs
2534 * from the old one.
2536 if (entry->kind == svn_node_file && strcmp(name, SVN_PROP_KEYWORDS) == 0)
2538 svn_string_t *old_value = apr_hash_get(prophash, SVN_PROP_KEYWORDS,
2539 APR_HASH_KEY_STRING);
2540 apr_hash_t *old_keywords, *new_keywords;
2542 SVN_ERR(svn_wc__get_keywords(&old_keywords, path, adm_access,
2543 old_value ? old_value->data : "", pool));
2544 SVN_ERR(svn_wc__get_keywords(&new_keywords, path, adm_access,
2545 value ? value->data : "", pool));
2547 if (svn_subst_keywords_differ2(old_keywords, new_keywords, FALSE, pool))
2549 svn_wc_entry_t tmp_entry;
2551 /* If we changed the keywords or newlines, void the entry
2552 timestamp for this file, so svn_wc_text_modified_p() does
2553 a real (albeit slow) check later on. */
2554 tmp_entry.kind = svn_node_file;
2555 tmp_entry.text_time = 0;
2556 SVN_ERR(svn_wc__loggy_entry_modify(&log_accum, adm_access,
2557 path, &tmp_entry,
2558 SVN_WC__ENTRY_MODIFY_TEXT_TIME,
2559 pool));
2563 /* Now we have all the properties in our hash. Simply merge the new
2564 property into it. */
2565 apr_hash_set(prophash, name, APR_HASH_KEY_STRING, value);
2567 SVN_ERR(svn_wc__install_props(&log_accum, adm_access, path,
2568 base_prophash, prophash, FALSE, pool));
2569 SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool));
2570 SVN_ERR(svn_wc__run_log(adm_access, NULL, pool));
2572 return SVN_NO_ERROR;
2576 svn_error_t *
2577 svn_wc_prop_set(const char *name,
2578 const svn_string_t *value,
2579 const char *path,
2580 svn_wc_adm_access_t *adm_access,
2581 apr_pool_t *pool)
2583 return svn_wc_prop_set2(name, value, path, adm_access, FALSE, pool);
2587 svn_error_t *
2588 svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p,
2589 const char *propname,
2590 const svn_string_t *propval,
2591 const char *path,
2592 svn_node_kind_t kind,
2593 svn_boolean_t skip_some_checks,
2594 svn_wc_canonicalize_svn_prop_get_file_t getter,
2595 void *getter_baton,
2596 apr_pool_t *pool)
2598 svn_stringbuf_t *new_value = NULL;
2600 /* Keep this static, it may get stored (for read-only purposes) in a
2601 hash that outlives this function. */
2602 static const svn_string_t boolean_value =
2604 SVN_PROP_BOOLEAN_TRUE,
2605 sizeof(SVN_PROP_BOOLEAN_TRUE) - 1
2608 SVN_ERR(validate_prop_against_node_kind(propname, path, kind, pool));
2610 if (!skip_some_checks && (strcmp(propname, SVN_PROP_EOL_STYLE) == 0))
2612 svn_subst_eol_style_t eol_style;
2613 const char *ignored_eol;
2614 new_value = svn_stringbuf_create_from_string(propval, pool);
2615 svn_stringbuf_strip_whitespace(new_value);
2616 svn_subst_eol_style_from_value(&eol_style, &ignored_eol, new_value->data);
2617 if (eol_style == svn_subst_eol_style_unknown)
2618 return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
2619 _("Unrecognized line ending style for '%s'"),
2620 svn_path_local_style(path, pool));
2621 SVN_ERR(validate_eol_prop_against_file(path, getter, getter_baton,
2622 pool));
2624 else if (!skip_some_checks && (strcmp(propname, SVN_PROP_MIME_TYPE) == 0))
2626 new_value = svn_stringbuf_create_from_string(propval, pool);
2627 svn_stringbuf_strip_whitespace(new_value);
2628 SVN_ERR(svn_mime_type_validate(new_value->data, pool));
2630 else if (strcmp(propname, SVN_PROP_IGNORE) == 0
2631 || strcmp(propname, SVN_PROP_EXTERNALS) == 0)
2633 /* Make sure that the last line ends in a newline */
2634 if (propval->data[propval->len - 1] != '\n')
2636 new_value = svn_stringbuf_create_from_string(propval, pool);
2637 svn_stringbuf_appendbytes(new_value, "\n", 1);
2640 /* Make sure this is a valid externals property. Do not
2641 allow 'skip_some_checks' to override, as there is no circumstance in
2642 which this is proper (because there is no circumstance in
2643 which Subversion can handle it). */
2644 if (strcmp(propname, SVN_PROP_EXTERNALS) == 0)
2646 /* We don't allow "." nor ".." as target directories in
2647 an svn:externals line. As it happens, our parse code
2648 checks for this, so all we have to is invoke it --
2649 we're not interested in the parsed result, only in
2650 whether or the parsing errored. */
2651 SVN_ERR(svn_wc_parse_externals_description3
2652 (NULL, path, propval->data, FALSE, pool));
2655 else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0)
2657 new_value = svn_stringbuf_create_from_string(propval, pool);
2658 svn_stringbuf_strip_whitespace(new_value);
2660 else if (strcmp(propname, SVN_PROP_EXECUTABLE) == 0
2661 || strcmp(propname, SVN_PROP_NEEDS_LOCK) == 0)
2663 new_value = svn_stringbuf_create_from_string(&boolean_value, pool);
2665 else if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
2667 apr_hash_t *mergeinfo;
2668 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, propval->data, pool));
2671 if (new_value)
2672 *propval_p = svn_string_create_from_buf(new_value, pool);
2673 else
2674 *propval_p = propval;
2676 return SVN_NO_ERROR;
2680 svn_boolean_t
2681 svn_wc_is_normal_prop(const char *name)
2683 enum svn_prop_kind kind = svn_property_kind(NULL, name);
2684 return (kind == svn_prop_regular_kind);
2688 svn_boolean_t
2689 svn_wc_is_wc_prop(const char *name)
2691 enum svn_prop_kind kind = svn_property_kind(NULL, name);
2692 return (kind == svn_prop_wc_kind);
2696 svn_boolean_t
2697 svn_wc_is_entry_prop(const char *name)
2699 enum svn_prop_kind kind = svn_property_kind(NULL, name);
2700 return (kind == svn_prop_entry_kind);
2704 /* Helper to optimize svn_wc_props_modified_p().
2706 If PATH_TO_PROP_FILE is nonexistent, is empty, or is of size 4 bytes
2707 ("END\n"), then set EMPTY_P to true. Otherwise set EMPTY_P to false,
2708 which means that the file must contain real properties. */
2709 static svn_error_t *
2710 empty_props_p(svn_boolean_t *empty_p,
2711 const char *path_to_prop_file,
2712 apr_pool_t *pool)
2714 svn_error_t *err;
2715 apr_finfo_t finfo;
2717 err = svn_io_stat(&finfo, path_to_prop_file, APR_FINFO_MIN | APR_FINFO_TYPE,
2718 pool);
2719 if (err)
2721 if (! APR_STATUS_IS_ENOENT(err->apr_err)
2722 && ! APR_STATUS_IS_ENOTDIR(err->apr_err))
2723 return err;
2725 /* nonexistent */
2726 svn_error_clear(err);
2727 *empty_p = TRUE;
2729 else
2733 /* If we remove props from a propfile, eventually the file will
2734 be empty, or, for working copies written by pre-1.3 libraries, will
2735 contain nothing but "END\n" */
2736 if (finfo.filetype == APR_REG && (finfo.size == 4 || finfo.size == 0))
2737 *empty_p = TRUE;
2738 else
2739 *empty_p = FALSE;
2741 /* If the size is between 1 and 4, then something is corrupt.
2742 If the size is between 4 and 16, then something is corrupt,
2743 because 16 is the -smallest- the file can possibly be if it
2744 contained only one property. So long as we say it is "not
2745 empty", we will discover such corruption later when we try
2746 to read the properties from the file. */
2749 return SVN_NO_ERROR;
2753 /* Simple wrapper around empty_props_p, and inversed. */
2754 svn_error_t *
2755 svn_wc__has_props(svn_boolean_t *has_props,
2756 const char *path,
2757 svn_wc_adm_access_t *adm_access,
2758 apr_pool_t *pool)
2760 svn_boolean_t is_empty;
2761 const char *prop_path;
2762 const svn_wc_entry_t *entry;
2763 svn_boolean_t has_propcaching =
2764 svn_wc__adm_wc_format(adm_access) > SVN_WC__NO_PROPCACHING_VERSION;
2766 SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
2768 /*### Maybe assert (entry); calling svn_wc__has_props
2769 for an unversioned path is bogus */
2770 if (! entry)
2772 *has_props = FALSE;
2773 return SVN_NO_ERROR;
2776 /* Use the flag in the entry if the WC is recent enough. */
2777 if (has_propcaching)
2779 *has_props = entry->has_props;
2780 return SVN_NO_ERROR;
2783 /* The rest is for compatibility with WCs that don't have propcaching. */
2785 SVN_ERR(svn_wc__prop_path(&prop_path, path, entry->kind,
2786 svn_wc__props_working, FALSE, pool));
2787 SVN_ERR(empty_props_p(&is_empty, prop_path, pool));
2789 if (is_empty)
2790 *has_props = FALSE;
2791 else
2792 *has_props = TRUE;
2794 return SVN_NO_ERROR;
2798 /* Common implementation for svn_wc_props_modified_p()
2799 and svn_wc__props_modified().
2801 Set *MODIFIED_P to true if PATH's properties are modified
2802 with regard to the base revision, else set MODIFIED_P to false.
2804 If WHICH_PROPS is non-null and there are prop mods then set
2805 *WHICH_PROPS to a (const char *propname) ->
2806 (const svn_string_t *propvalue) key:value mapping of only
2807 the modified properties. */
2808 static svn_error_t *
2809 modified_props(svn_boolean_t *modified_p,
2810 const char *path,
2811 apr_hash_t **which_props,
2812 svn_wc_adm_access_t *adm_access,
2813 apr_pool_t *pool)
2815 const char *prop_path;
2816 const char *prop_base_path;
2817 const svn_wc_entry_t *entry;
2818 apr_pool_t *subpool = svn_pool_create(pool);
2819 int wc_format = svn_wc__adm_wc_format(adm_access);
2820 svn_boolean_t want_props = which_props ? TRUE : FALSE;
2822 if (want_props)
2823 *which_props = apr_hash_make(pool);
2825 SVN_ERR(svn_wc_entry(&entry, path, adm_access, TRUE, subpool));
2827 /* If we have no entry, we can't have any prop mods. */
2828 if (! entry)
2830 *modified_p = FALSE;
2831 goto cleanup;
2834 /* For newer WCs, if there is an entry for the path, we have a fast
2835 * and nice way to retrieve the information from the entry. */
2836 if (wc_format > SVN_WC__NO_PROPCACHING_VERSION)
2838 /* Only continue if there are prop mods
2839 and we want to know the details. */
2840 *modified_p = entry->has_prop_mods;
2841 if (!*modified_p || !want_props)
2842 goto cleanup;
2845 /* So, we have a WC in an older format or we have propcaching
2846 but need to find the specific prop changes. Either way we
2847 have some work to do... */
2849 /* First, get the paths of the working and 'base' prop files. */
2850 SVN_ERR(svn_wc__prop_path(&prop_path, path, entry->kind,
2851 svn_wc__props_working, FALSE, subpool));
2852 SVN_ERR(svn_wc__prop_path(&prop_base_path, path, entry->kind,
2853 svn_wc__props_base, FALSE, subpool));
2855 /* Check for numerous easy outs on older WC formats before we
2856 resort to svn_prop_diffs(). */
2857 if (wc_format <= SVN_WC__NO_PROPCACHING_VERSION)
2859 svn_boolean_t bempty, wempty;
2860 /* Decide if either path is "empty" of properties. */
2861 SVN_ERR(empty_props_p(&wempty, prop_path, subpool));
2862 SVN_ERR(empty_props_p(&bempty, prop_base_path, subpool));
2864 /* If something is scheduled for replacement, we do *not* want to
2865 pay attention to any base-props; they might be residual from the
2866 old deleted file. */
2867 if (entry->schedule == svn_wc_schedule_replace)
2869 *modified_p = wempty ? FALSE : TRUE;
2871 /* Only continue if there are prop mods
2872 and we want to know the details. */
2873 if (!*modified_p || !want_props)
2874 goto cleanup;
2877 /* Easy out: if the base file is empty, we know the answer
2878 immediately. */
2879 if (bempty)
2881 if (! wempty)
2883 /* base is empty, but working is not */
2884 *modified_p = TRUE;
2886 /* Only continue if we want to know the details. */
2887 if (!want_props)
2888 goto cleanup;
2890 else
2892 /* base and working are both empty */
2893 *modified_p = FALSE;
2894 goto cleanup;
2897 /* OK, so the base file is non-empty. One more easy out: */
2898 else if (wempty)
2900 /* base exists, working is empty */
2901 *modified_p = TRUE;
2903 /* Only continue if we want to know the details. */
2904 if (!want_props)
2905 goto cleanup;
2907 else
2909 svn_boolean_t different_filesizes;
2911 /* At this point, we know both files exists. Therefore we have no
2912 choice but to start checking their contents. */
2914 /* There are at least three tests we can try in succession. */
2916 /* Easy-answer attempt #1: (### this stat's the files again) */
2918 /* Check if the local and prop-base file have *definitely*
2919 different filesizes. */
2920 SVN_ERR(svn_io_filesizes_different_p(&different_filesizes,
2921 prop_path,
2922 prop_base_path,
2923 subpool));
2924 if (different_filesizes)
2926 *modified_p = TRUE;
2928 /* Only continue if we want to know the details. */
2929 if (!want_props)
2930 goto cleanup;
2932 else
2934 svn_boolean_t equal_timestamps;
2936 /* Easy-answer attempt #2: (### this stat's the files again) */
2938 /* See if the local file's prop timestamp is the same as the
2939 one recorded in the administrative directory. */
2940 SVN_ERR(svn_wc__timestamps_equal_p(&equal_timestamps, path,
2941 adm_access,
2942 svn_wc__prop_time,
2943 subpool));
2944 if (equal_timestamps)
2946 *modified_p = FALSE;
2947 goto cleanup;
2951 } /* wc_format <= SVN_WC__NO_PROPCACHING_VERSION */
2953 /* If we get here, then we either known we have prop changes and want
2954 the specific changed props or we have a pre-propcaching WC version
2955 and still haven't figured out if we even have changes. Regardless,
2956 our approach is the same in both cases.
2958 In the pre-propcaching case:
2960 We know that the filesizes are the same,
2961 but the timestamps are different. That's still not enough
2962 evidence to make a correct decision; we need to look at the
2963 files' contents directly.
2965 However, doing a byte-for-byte comparison won't work. The two
2966 properties files may have the *exact* same name/value pairs, but
2967 arranged in a different order. (Our hashdump format makes no
2968 guarantees about ordering.)
2970 Therefore, rather than use contents_identical_p(), we use
2971 svn_prop_diffs(). */
2973 apr_array_header_t *local_propchanges;
2974 apr_hash_t *localprops = apr_hash_make(subpool);
2975 apr_hash_t *baseprops = apr_hash_make(subpool);
2977 /* ### Amazingly, this stats the files again! */
2978 SVN_ERR(load_prop_file(prop_path, localprops, subpool));
2979 SVN_ERR(load_prop_file(prop_base_path, baseprops, subpool));
2981 /* Don't use the subpool is we are hanging on to the changed props. */
2982 SVN_ERR(svn_prop_diffs(&local_propchanges, localprops,
2983 baseprops,
2984 want_props ? pool : subpool));
2986 if (local_propchanges->nelts == 0)
2988 *modified_p = FALSE;
2990 else
2992 *modified_p = TRUE;
2994 /* Record the changed props if that's what we want. */
2995 if (want_props)
2997 int i;
2998 for (i = 0; i < local_propchanges->nelts; i++)
3000 svn_prop_t *propt = &APR_ARRAY_IDX(local_propchanges, i,
3001 svn_prop_t);
3002 apr_hash_set(*which_props, propt->name,
3003 APR_HASH_KEY_STRING, propt->value);
3009 cleanup:
3010 svn_pool_destroy(subpool);
3012 return SVN_NO_ERROR;
3016 svn_error_t *
3017 svn_wc__props_modified(const char *path,
3018 apr_hash_t **which_props,
3019 svn_wc_adm_access_t *adm_access,
3020 apr_pool_t *pool)
3022 svn_boolean_t modified_p;
3023 return modified_props(&modified_p, path, which_props, adm_access, pool);
3027 svn_error_t *
3028 svn_wc_props_modified_p(svn_boolean_t *modified_p,
3029 const char *path,
3030 svn_wc_adm_access_t *adm_access,
3031 apr_pool_t *pool)
3033 return modified_props(modified_p, path, NULL, adm_access, pool);
3037 svn_error_t *
3038 svn_wc__has_prop_mods(svn_boolean_t *prop_mods,
3039 const char *path,
3040 svn_wc_adm_access_t *adm_access,
3041 apr_pool_t *pool)
3044 /* For an enough recent WC, we can have a really easy out. */
3045 if (svn_wc__adm_wc_format(adm_access) > SVN_WC__NO_PROPCACHING_VERSION)
3047 const svn_wc_entry_t *entry;
3048 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, TRUE, pool));
3049 *prop_mods = entry->has_prop_mods;
3051 else
3053 apr_array_header_t *propmods;
3054 apr_hash_t *localprops = apr_hash_make(pool);
3055 apr_hash_t *baseprops = apr_hash_make(pool);
3057 /* Load all properties into hashes */
3058 SVN_ERR(svn_wc__load_props(&baseprops, &localprops, NULL,
3059 adm_access, path, pool));
3061 /* Get an array of local changes by comparing the hashes. */
3062 SVN_ERR(svn_prop_diffs(&propmods, localprops, baseprops, pool));
3064 *prop_mods = propmods->nelts > 0;
3067 return SVN_NO_ERROR;
3071 svn_error_t *
3072 svn_wc_get_prop_diffs(apr_array_header_t **propchanges,
3073 apr_hash_t **original_props,
3074 const char *path,
3075 svn_wc_adm_access_t *adm_access,
3076 apr_pool_t *pool)
3078 const svn_wc_entry_t *entry;
3079 apr_hash_t *baseprops, *props;
3080 const char *entryname;
3082 /*### Maybe assert (entry); calling svn_wc_get_prop_diffs
3083 for an unversioned path is bogus */
3084 SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
3086 if (! entry)
3088 if (original_props)
3089 *original_props = apr_hash_make(pool);
3091 if (propchanges)
3092 *propchanges = apr_array_make(pool, 0, sizeof(svn_prop_t));
3094 return SVN_NO_ERROR;
3097 if (entry->kind == svn_node_dir)
3099 SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, path, pool));
3100 entryname = SVN_WC_ENTRY_THIS_DIR;
3102 else
3104 const char *dirname;
3105 svn_path_split(path, &dirname, &entryname, pool);
3106 SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, dirname, pool));
3109 SVN_ERR(svn_wc__load_props(&baseprops, propchanges ? &props : NULL, NULL,
3110 adm_access, path, pool));
3112 if (original_props != NULL)
3113 *original_props = baseprops;
3115 if (propchanges != NULL)
3116 SVN_ERR(svn_prop_diffs(propchanges, props, baseprops, pool));
3118 return SVN_NO_ERROR;
3123 /** Externals **/
3126 * Look for either
3128 * -r N
3129 * -rN
3131 * in the LINE_PARTS array and update the revision field in ITEM with
3132 * the revision if the revision is found. Set REV_IDX to the index in
3133 * LINE_PARTS where the revision specification starts. Remove from
3134 * LINE_PARTS the element(s) that specify the revision.
3135 * PARENT_DIRECTORY_DISPLAY and LINE are given to return a nice error
3136 * string.
3138 * If this function returns successfully, then LINE_PARTS will have
3139 * only two elements in it.
3141 static svn_error_t *
3142 find_and_remove_externals_revision(int *rev_idx,
3143 apr_array_header_t *line_parts,
3144 svn_wc_external_item2_t *item,
3145 const char *parent_directory_display,
3146 const char *line)
3148 int i;
3150 for (i = 0; i < 2; ++i)
3152 const char *token = APR_ARRAY_IDX(line_parts, i, const char *);
3154 if (token[0] == '-' && token[1] == 'r')
3156 const char *digits_ptr;
3157 const char *end_ptr;
3158 int shift_count;
3159 int j;
3161 *rev_idx = i;
3163 if (token[2] == '\0')
3165 /* There must be a total of four elements in the line if
3166 -r N is used. */
3167 if (line_parts->nelts != 4)
3168 goto parse_error;
3170 shift_count = 2;
3171 digits_ptr = APR_ARRAY_IDX(line_parts, i+1, const char *);
3173 else
3175 /* There must be a total of three elements in the line
3176 if -rN is used. */
3177 if (line_parts->nelts != 3)
3178 goto parse_error;
3180 shift_count = 1;
3181 digits_ptr = token+2;
3184 item->revision.kind = svn_opt_revision_number;
3185 SVN_ERR(svn_revnum_parse(&item->revision.value.number,
3186 digits_ptr,
3187 &end_ptr));
3189 /* If there's trailing garbage after the digits, then treat
3190 the revision as invalid. */
3191 if (*end_ptr != '\0')
3192 goto parse_error;
3194 /* Shift any line elements past the revision specification
3195 down over the revision specification. */
3196 for (j = i; j < line_parts->nelts-shift_count; ++j)
3197 APR_ARRAY_IDX(line_parts, j, const char *) =
3198 APR_ARRAY_IDX(line_parts, j+shift_count, const char *);
3199 for (j = 0; j < shift_count; ++j)
3200 apr_array_pop(line_parts);
3202 /* Found the revision, so leave the function immediately, do
3203 * not continue looking for additional revisions. */
3204 return SVN_NO_ERROR;
3208 /* No revision was found, so there must be exactly two items in the
3209 line array. */
3210 if (line_parts->nelts == 2)
3211 return SVN_NO_ERROR;
3213 parse_error:
3214 return svn_error_createf
3215 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
3216 _("Error parsing %s property on '%s': '%s'"),
3217 SVN_PROP_EXTERNALS,
3218 parent_directory_display,
3219 line);
3222 svn_error_t *
3223 svn_wc_parse_externals_description3(apr_array_header_t **externals_p,
3224 const char *parent_directory,
3225 const char *desc,
3226 svn_boolean_t canonicalize_url,
3227 apr_pool_t *pool)
3229 apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool);
3230 int i;
3231 const char *parent_directory_display = svn_path_is_url(parent_directory) ?
3232 parent_directory : svn_path_local_style(parent_directory, pool);
3234 if (externals_p)
3235 *externals_p = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *));
3237 for (i = 0; i < lines->nelts; i++)
3239 const char *line = APR_ARRAY_IDX(lines, i, const char *);
3240 apr_array_header_t *line_parts;
3241 svn_wc_external_item2_t *item;
3242 const char *token0;
3243 const char *token1;
3244 svn_boolean_t token0_is_url;
3245 svn_boolean_t token1_is_url;
3247 /* Index into line_parts where the revision specification
3248 started. */
3249 int rev_idx = -1;
3251 if ((! line) || (line[0] == '#'))
3252 continue;
3254 /* else proceed */
3256 line_parts = svn_cstring_split(line, " \t", TRUE, pool);
3258 SVN_ERR(svn_wc_external_item_create
3259 ((const svn_wc_external_item2_t **) &item, pool));
3260 item->revision.kind = svn_opt_revision_unspecified;
3261 item->peg_revision.kind = svn_opt_revision_unspecified;
3264 * There are six different formats of externals:
3266 * 1) DIR URL
3267 * 2) DIR -r N URL
3268 * 3) DIR -rN URL
3269 * 4) URL DIR
3270 * 5) -r N URL DIR
3271 * 6) -rN URL DIR
3273 * The last three allow peg revisions in the URL.
3275 * With relative URLs and no '-rN' or '-r N', there is no way to
3276 * distinguish between 'DIR URL' and 'URL DIR' when URL is a
3277 * relative URL like /svn/repos/trunk, so this case is taken as
3278 * case 4).
3280 if (line_parts->nelts < 2 || line_parts->nelts > 4)
3281 return svn_error_createf
3282 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
3283 _("Error parsing %s property on '%s': '%s'"),
3284 SVN_PROP_EXTERNALS,
3285 parent_directory_display,
3286 line);
3288 /* To make it easy to check for the forms, find and remove -r N
3289 or -rN from the line item array. If it is found, rev_idx
3290 contains the index into line_parts where '-r' was found and
3291 set item->revision to the parsed revision. */
3292 SVN_ERR(find_and_remove_externals_revision(&rev_idx, line_parts, item,
3293 parent_directory_display,
3294 line));
3296 token0 = APR_ARRAY_IDX(line_parts, 0, const char *);
3297 token1 = APR_ARRAY_IDX(line_parts, 1, const char *);
3299 token0_is_url = svn_path_is_url(token0);
3300 token1_is_url = svn_path_is_url(token1);
3302 /* If -r is at the beginning of the line or the first token is
3303 an absolute URL or if the second token is not an absolute
3304 URL, then the URL supports peg revisions. */
3305 if (0 == rev_idx || token0_is_url || ! token1_is_url)
3307 /* The URL is passed to svn_opt_parse_path in
3308 uncanonicalized form so that the scheme relative URL
3309 //hostname/foo is not collapsed to a server root relative
3310 URL /hostname/foo. */
3311 SVN_ERR(svn_opt_parse_path(&item->peg_revision, &item->url,
3312 token0, pool));
3313 item->target_dir = token1;
3315 else
3317 item->target_dir = token0;
3318 item->url = token1;
3319 item->peg_revision = item->revision;
3322 SVN_ERR(svn_opt_resolve_revisions(&item->peg_revision,
3323 &item->revision, TRUE, FALSE,
3324 pool));
3326 item->target_dir = svn_path_canonicalize
3327 (svn_path_internal_style(item->target_dir, pool), pool);
3329 if (item->target_dir[0] == '\0' || item->target_dir[0] == '/'
3330 || svn_path_is_backpath_present(item->target_dir))
3331 return svn_error_createf
3332 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
3333 _("Invalid %s property on '%s': "
3334 "target '%s' is an absolute path or involves '..'"),
3335 SVN_PROP_EXTERNALS,
3336 parent_directory_display,
3337 item->target_dir);
3339 if (canonicalize_url)
3340 item->url = svn_path_canonicalize(item->url, pool);
3342 if (externals_p)
3343 APR_ARRAY_PUSH(*externals_p, svn_wc_external_item2_t *) = item;
3346 return SVN_NO_ERROR;
3349 svn_error_t *
3350 svn_wc_parse_externals_description2(apr_array_header_t **externals_p,
3351 const char *parent_directory,
3352 const char *desc,
3353 apr_pool_t *pool)
3355 apr_array_header_t *list;
3356 apr_pool_t *subpool = svn_pool_create(pool);
3357 int i;
3359 SVN_ERR(svn_wc_parse_externals_description3(externals_p ? &list : NULL,
3360 parent_directory, desc,
3361 TRUE, subpool));
3363 if (externals_p)
3365 *externals_p = apr_array_make(pool, list->nelts,
3366 sizeof(svn_wc_external_item_t *));
3367 for (i = 0; i < list->nelts; i++)
3369 svn_wc_external_item2_t *item2 = APR_ARRAY_IDX(list, i,
3370 svn_wc_external_item2_t *);
3371 svn_wc_external_item_t *item = apr_palloc(pool, sizeof (*item));
3373 if (item2->target_dir)
3374 item->target_dir = apr_pstrdup(pool, item2->target_dir);
3375 if (item2->url)
3376 item->url = apr_pstrdup(pool, item2->url);
3377 item->revision = item2->revision;
3379 APR_ARRAY_PUSH(*externals_p, svn_wc_external_item_t *) = item;
3383 svn_pool_destroy(subpool);
3385 return SVN_NO_ERROR;
3389 svn_error_t *
3390 svn_wc_parse_externals_description(apr_hash_t **externals_p,
3391 const char *parent_directory,
3392 const char *desc,
3393 apr_pool_t *pool)
3395 apr_array_header_t *list;
3396 int i;
3398 SVN_ERR(svn_wc_parse_externals_description2(externals_p ? &list : NULL,
3399 parent_directory, desc, pool));
3401 /* Store all of the items into the hash if that was requested. */
3402 if (externals_p)
3404 *externals_p = apr_hash_make(pool);
3405 for (i = 0; i < list->nelts; i++)
3407 svn_wc_external_item_t *item;
3408 item = APR_ARRAY_IDX(list, i, svn_wc_external_item_t *);
3410 apr_hash_set(*externals_p, item->target_dir,
3411 APR_HASH_KEY_STRING, item);
3414 return SVN_NO_ERROR;
3417 svn_boolean_t
3418 svn_wc__has_special_property(apr_hash_t *props)
3420 return apr_hash_get(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING) != NULL;
3423 svn_boolean_t
3424 svn_wc__has_magic_property(const apr_array_header_t *properties)
3426 int i;
3428 for (i = 0; i < properties->nelts; i++)
3430 const svn_prop_t *property = &APR_ARRAY_IDX(properties, i, svn_prop_t);
3432 if (strcmp(property->name, SVN_PROP_EXECUTABLE) == 0
3433 || strcmp(property->name, SVN_PROP_KEYWORDS) == 0
3434 || strcmp(property->name, SVN_PROP_EOL_STYLE) == 0
3435 || strcmp(property->name, SVN_PROP_SPECIAL) == 0
3436 || strcmp(property->name, SVN_PROP_NEEDS_LOCK) == 0)
3437 return TRUE;
3439 return FALSE;