Modified: subversion/branches/shelve-checkpoint/subversion/libsvn_client/conflicts.c URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint/subversion/libsvn_client/conflicts.c?rev=1813860&r1=1813859&r2=1813860&view=diff ============================================================================== --- subversion/branches/shelve-checkpoint/subversion/libsvn_client/conflicts.c (original) +++ subversion/branches/shelve-checkpoint/subversion/libsvn_client/conflicts.c Tue Oct 31 09:39:59 2017 @@ -267,6 +267,9 @@ struct repos_move_info { /* The copyfrom revision of the moved-to path. */ svn_revnum_t copyfrom_rev; + /* The node kind of the item being moved. */ + svn_node_kind_t node_kind; + /* Prev pointer. NULL if no prior move exists in the chain. */ struct repos_move_info *prev; @@ -366,6 +369,7 @@ struct copy_info { const char *copyto_path; const char *copyfrom_path; svn_revnum_t copyfrom_rev; + svn_node_kind_t node_kind; }; /* Allocate and return a NEW_MOVE, and update MOVED_PATHS with this new move. */ @@ -374,6 +378,7 @@ add_new_move(struct repos_move_info **ne const char *deleted_repos_relpath, const char *copyto_path, svn_revnum_t copyfrom_rev, + svn_node_kind_t node_kind, svn_revnum_t revision, const char *author, apr_hash_t *moved_paths, @@ -392,6 +397,7 @@ add_new_move(struct repos_move_info **ne move->rev = revision; move->rev_author = apr_pstrdup(result_pool, author); move->copyfrom_rev = copyfrom_rev; + move->node_kind = node_kind; /* Link together multiple moves of the same node. * Note that we're traversing history backwards, so moves already @@ -430,6 +436,269 @@ add_new_move(struct repos_move_info **ne return SVN_NO_ERROR; } +/* Push a MOVE into the MOVES_TABLE. */ +static void +push_move(struct repos_move_info *move, apr_hash_t *moves_table, + apr_pool_t *result_pool) +{ + apr_array_header_t *moves; + + /* Add this move to the list of moves in the revision. */ + moves = apr_hash_get(moves_table, &move->rev, sizeof(svn_revnum_t)); + if (moves == NULL) + { + /* It is the first move in this revision. Create the list. */ + moves = apr_array_make(result_pool, 1, sizeof(struct repos_move_info *)); + apr_hash_set(moves_table, &move->rev, sizeof(svn_revnum_t), moves); + } + APR_ARRAY_PUSH(moves, struct repos_move_info *) = move; +} + +/* Find the youngest common ancestor of REPOS_RELPATH1@PEG_REV1 and + * REPOS_RELPATH2@PEG_REV2. Return the result in *YCA_LOC. + * Set *YCA_LOC to NULL if no common ancestor exists. */ +static svn_error_t * +find_yca(svn_client__pathrev_t **yca_loc, + const char *repos_relpath1, + svn_revnum_t peg_rev1, + const char *repos_relpath2, + svn_revnum_t peg_rev2, + const char *repos_root_url, + const char *repos_uuid, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_client__pathrev_t *loc1; + svn_client__pathrev_t *loc2; + + *yca_loc = NULL; + + loc1 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid, + peg_rev1, repos_relpath1, + scratch_pool); + loc2 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid, + peg_rev2, repos_relpath2, + scratch_pool); + SVN_ERR(svn_client__get_youngest_common_ancestor(yca_loc, loc1, loc2, + ra_session, ctx, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Like find_yca, expect that a YCA could also be found via a brute-force + * search of parents of REPOS_RELPATH1 and REPOS_RELPATH2, if no "direct" + * YCA exists. An implicit assumption is that some parent of REPOS_RELPATH1 + * is a branch of some parent of REPOS_RELPATH2. + * + * This function can guess a "good enough" YCA for 'missing nodes' which do + * not exist in the working copy, e.g. when a file edit is merged to a path + * which does not exist in the working copy. + */ +static svn_error_t * +find_nearest_yca(svn_client__pathrev_t **yca_locp, + const char *repos_relpath1, + svn_revnum_t peg_rev1, + const char *repos_relpath2, + svn_revnum_t peg_rev2, + const char *repos_root_url, + const char *repos_uuid, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_client__pathrev_t *yca_loc; + svn_error_t *err; + apr_pool_t *iterpool; + const char *p1, *p2; + apr_size_t c1, c2; + + *yca_locp = NULL; + + iterpool = svn_pool_create(scratch_pool); + + p1 = repos_relpath1; + c1 = svn_path_component_count(repos_relpath1); + while (c1--) + { + svn_pool_clear(iterpool); + + p2 = repos_relpath2; + c2 = svn_path_component_count(repos_relpath2); + while (c2--) + { + err = find_yca(&yca_loc, p1, peg_rev1, p2, peg_rev2, + repos_root_url, repos_uuid, ra_session, ctx, + result_pool, iterpool); + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + yca_loc = NULL; + } + else + return svn_error_trace(err); + } + + if (yca_loc) + { + *yca_locp = yca_loc; + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; + } + + p2 = svn_relpath_dirname(p2, scratch_pool); + } + + p1 = svn_relpath_dirname(p1, scratch_pool); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Check if the copied node described by COPY and the DELETED_PATH@DELETED_REV + * share a common ancestor. If so, return new repos_move_info in *MOVE which + * describes a move from the deleted path to that copy's destination. */ +static svn_error_t * +find_related_move(struct repos_move_info **move, + struct copy_info *copy, + const char *deleted_repos_relpath, + svn_revnum_t deleted_rev, + const char *author, + apr_hash_t *moved_paths, + const char *repos_root_url, + const char *repos_uuid, + svn_client_ctx_t *ctx, + svn_ra_session_t *ra_session, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_client__pathrev_t *yca_loc; + svn_error_t *err; + + *move = NULL; + err = find_yca(&yca_loc, copy->copyfrom_path, copy->copyfrom_rev, + deleted_repos_relpath, rev_below(deleted_rev), + repos_root_url, repos_uuid, ra_session, ctx, + scratch_pool, scratch_pool); + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + yca_loc = NULL; + } + else + return svn_error_trace(err); + } + + if (yca_loc) + SVN_ERR(add_new_move(move, deleted_repos_relpath, + copy->copyto_path, copy->copyfrom_rev, + copy->node_kind, deleted_rev, author, + moved_paths, ra_session, repos_root_url, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Detect moves by matching DELETED_REPOS_RELPATH@DELETED_REV to the copies + * in COPIES. Add any moves found to MOVES_TABLE and update MOVED_PATHS. */ +static svn_error_t * +match_copies_to_deletion(const char *deleted_repos_relpath, + svn_revnum_t deleted_rev, + const char *author, + apr_hash_t *copies, + apr_hash_t *moves_table, + apr_hash_t *moved_paths, + const char *repos_root_url, + const char *repos_uuid, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + iterpool = svn_pool_create(scratch_pool); + for (hi = apr_hash_first(scratch_pool, copies); + hi != NULL; + hi = apr_hash_next(hi)) + { + const char *copyfrom_path = apr_hash_this_key(hi); + apr_array_header_t *copies_with_same_source_path; + int i; + + svn_pool_clear(iterpool); + + copies_with_same_source_path = apr_hash_this_val(hi); + + if (strcmp(copyfrom_path, deleted_repos_relpath) == 0) + { + /* We found a copyfrom path which matches a deleted node. + * Check if the deleted node is an ancestor of the copied node. */ + for (i = 0; i < copies_with_same_source_path->nelts; i++) + { + struct copy_info *copy; + svn_boolean_t related; + struct repos_move_info *move; + + copy = APR_ARRAY_IDX(copies_with_same_source_path, i, + struct copy_info *); + SVN_ERR(check_move_ancestry(&related, + ra_session, repos_root_url, + deleted_repos_relpath, + deleted_rev, + copy->copyfrom_path, + copy->copyfrom_rev, + TRUE, iterpool)); + if (!related) + continue; + + /* Remember details of this move. */ + SVN_ERR(add_new_move(&move, deleted_repos_relpath, + copy->copyto_path, copy->copyfrom_rev, + copy->node_kind, deleted_rev, author, + moved_paths, ra_session, repos_root_url, + result_pool, iterpool)); + push_move(move, moves_table, result_pool); + } + } + else + { + /* Check if this deleted node is related to any copies in this + * revision. These could be moves of the deleted node which + * were merged here from other lines of history. */ + for (i = 0; i < copies_with_same_source_path->nelts; i++) + { + struct copy_info *copy; + struct repos_move_info *move = NULL; + + copy = APR_ARRAY_IDX(copies_with_same_source_path, i, + struct copy_info *); + SVN_ERR(find_related_move(&move, copy, deleted_repos_relpath, + deleted_rev, author, + moved_paths, + repos_root_url, repos_uuid, + ctx, ra_session, + result_pool, iterpool)); + if (move) + push_move(move, moves_table, result_pool); + } + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + /* Update MOVES_TABLE and MOVED_PATHS based on information from * revision data in LOG_ENTRY, COPIES, and DELETED_PATHS. * Use RA_SESSION to perform the necessary requests. */ @@ -441,73 +710,31 @@ find_moves_in_revision(svn_ra_session_t apr_hash_t *copies, apr_array_header_t *deleted_paths, const char *repos_root_url, + const char *repos_uuid, + svn_client_ctx_t *ctx, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { apr_pool_t *iterpool; - svn_boolean_t related; int i; + const svn_string_t *author; + author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR); iterpool = svn_pool_create(scratch_pool); for (i = 0; i < deleted_paths->nelts; i++) { const char *deleted_repos_relpath; - struct repos_move_info *move; - apr_array_header_t *moves; - apr_array_header_t *copies_with_same_source_path; - int j; svn_pool_clear(iterpool); deleted_repos_relpath = APR_ARRAY_IDX(deleted_paths, i, const char *); - - copies_with_same_source_path = svn_hash_gets(copies, - deleted_repos_relpath); - if (copies_with_same_source_path == NULL) - continue; /* Not a move, or a nested move we handle later on. */ - - for (j = 0; j < copies_with_same_source_path->nelts; j++) - { - struct copy_info *copy; - - /* We found a copy with a copyfrom path which matches a deleted node. - * Verify that the deleted node is an ancestor of the copied node. */ - copy = APR_ARRAY_IDX(copies_with_same_source_path, j, - struct copy_info *); - SVN_ERR(check_move_ancestry(&related, ra_session, repos_root_url, - deleted_repos_relpath, - log_entry->revision, - copy->copyfrom_path, - copy->copyfrom_rev, - TRUE, iterpool)); - if (related) - { - const svn_string_t *author; - - author = svn_hash_gets(log_entry->revprops, - SVN_PROP_REVISION_AUTHOR); - /* Remember details of this move. */ - SVN_ERR(add_new_move(&move, deleted_repos_relpath, - copy->copyto_path, copy->copyfrom_rev, - log_entry->revision, - author ? author->data : _("unknown author"), - moved_paths, ra_session, repos_root_url, - result_pool, iterpool)); - - /* Add this move to the list of moves in this revision. */ - moves = apr_hash_get(moves_table, &move->rev, - sizeof(svn_revnum_t)); - if (moves == NULL) - { - /* It is the first move in this revision. Create the list. */ - moves = apr_array_make(result_pool, 1, - sizeof(struct repos_move_info *)); - apr_hash_set(moves_table, &move->rev, sizeof(svn_revnum_t), - moves); - } - APR_ARRAY_PUSH(moves, struct repos_move_info *) = move; - } - } + SVN_ERR(match_copies_to_deletion(deleted_repos_relpath, + log_entry->revision, + author ? author->data + : _("unknown author"), + copies, moves_table, moved_paths, + repos_root_url, repos_uuid, ra_session, + ctx, result_pool, iterpool)); } svn_pool_destroy(iterpool); @@ -520,7 +747,7 @@ struct find_deleted_rev_baton * svn_ra_get_log2(). */ const char *deleted_repos_relpath; const char *related_repos_relpath; - svn_revnum_t related_repos_peg_rev; + svn_revnum_t related_peg_rev; const char *repos_root_url; const char *repos_uuid; svn_client_ctx_t *ctx; @@ -539,40 +766,6 @@ struct find_deleted_rev_baton svn_ra_session_t *extra_ra_session; }; -/* Find the youngest common ancestor of REPOS_RELPATH1@PEG_REV1 and - * REPOS_RELPATH2@PEG_REV2. Return the result in *YCA_LOC. - * Set *YCA_LOC to NULL if no common ancestor exists. */ -static svn_error_t * -find_yca(svn_client__pathrev_t **yca_loc, - const char *repos_relpath1, - svn_revnum_t peg_rev1, - const char *repos_relpath2, - svn_revnum_t peg_rev2, - const char *repos_root_url, - const char *repos_uuid, - svn_ra_session_t *ra_session, - svn_client_ctx_t *ctx, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_client__pathrev_t *loc1; - svn_client__pathrev_t *loc2; - - *yca_loc = NULL; - - loc1 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid, - peg_rev1, repos_relpath1, - scratch_pool); - loc2 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid, - peg_rev2, repos_relpath2, - scratch_pool); - SVN_ERR(svn_client__get_youngest_common_ancestor(yca_loc, loc1, loc2, - ra_session, ctx, - result_pool, scratch_pool)); - - return SVN_NO_ERROR; -} - /* If DELETED_RELPATH matches the moved-from path of a move in MOVES, * or if DELETED_RELPATH is a child of a moved-to path in MOVES, return * a struct move_info for the corresponding move. Else, return NULL. */ @@ -708,6 +901,7 @@ find_nested_moves(apr_array_header_t *mo /* Remember details of this move. */ SVN_ERR(add_new_move(&nested_move, moved_along_repos_relpath, copy->copyto_path, copy->copyfrom_rev, + copy->node_kind, revision, author, moved_paths, ra_session, repos_root_url, result_pool, iterpool)); @@ -726,6 +920,34 @@ find_nested_moves(apr_array_header_t *mo return SVN_NO_ERROR; } +/* Make a shallow copy of the copied LOG_ITEM in COPIES. */ +static void +cache_copied_item(apr_hash_t *copies, const char *changed_path, + svn_log_changed_path2_t *log_item) +{ + apr_pool_t *result_pool = apr_hash_pool_get(copies); + struct copy_info *copy = apr_palloc(result_pool, sizeof(*copy)); + apr_array_header_t *copies_with_same_source_path; + + copy->copyfrom_path = log_item->copyfrom_path; + if (log_item->copyfrom_path[0] == '/') + copy->copyfrom_path++; + copy->copyto_path = changed_path; + copy->copyfrom_rev = log_item->copyfrom_rev; + copy->node_kind = log_item->node_kind; + + copies_with_same_source_path = apr_hash_get(copies, copy->copyfrom_path, + APR_HASH_KEY_STRING); + if (copies_with_same_source_path == NULL) + { + copies_with_same_source_path = apr_array_make(result_pool, 1, + sizeof(struct copy_info *)); + apr_hash_set(copies, copy->copyfrom_path, APR_HASH_KEY_STRING, + copies_with_same_source_path); + } + APR_ARRAY_PUSH(copies_with_same_source_path, struct copy_info *) = copy; +} + /* Implements svn_log_entry_receiver_t. * * Find the revision in which a node, optionally ancestrally related to the @@ -796,7 +1018,7 @@ find_deleted_rev(void *baton, deleted_node_found = TRUE; if (b->related_repos_relpath != NULL && - b->related_repos_peg_rev != SVN_INVALID_REVNUM) + b->related_peg_rev != SVN_INVALID_REVNUM) { svn_client__pathrev_t *yca_loc; svn_error_t *err; @@ -807,7 +1029,7 @@ find_deleted_rev(void *baton, * "related node" specified in our baton. */ err = find_yca(&yca_loc, b->related_repos_relpath, - b->related_repos_peg_rev, + b->related_peg_rev, b->deleted_repos_relpath, rev_below(log_entry->revision), b->repos_root_url, b->repos_uuid, @@ -1438,31 +1660,7 @@ find_moves(void *baton, svn_log_entry_t /* For move detection, scan for copied nodes in this revision. */ if (log_item->action == 'A' && log_item->copyfrom_path) - { - struct copy_info *copy; - apr_array_header_t *copies_with_same_source_path; - - if (log_item->copyfrom_path[0] == '/') - log_item->copyfrom_path++; - - copy = apr_palloc(scratch_pool, sizeof(*copy)); - copy->copyto_path = changed_path; - copy->copyfrom_path = log_item->copyfrom_path; - copy->copyfrom_rev = log_item->copyfrom_rev; - copies_with_same_source_path = apr_hash_get(copies, - log_item->copyfrom_path, - APR_HASH_KEY_STRING); - if (copies_with_same_source_path == NULL) - { - copies_with_same_source_path = apr_array_make( - scratch_pool, 1, - sizeof(struct copy_info *)); - apr_hash_set(copies, copy->copyfrom_path, APR_HASH_KEY_STRING, - copies_with_same_source_path); - } - APR_ARRAY_PUSH(copies_with_same_source_path, - struct copy_info *) = copy; - } + cache_copied_item(copies, changed_path, log_item); /* For move detection, store all deleted_paths. */ if (log_item->action == 'D' || log_item->action == 'R') @@ -1475,8 +1673,8 @@ find_moves(void *baton, svn_log_entry_t SVN_ERR(find_moves_in_revision(b->extra_ra_session, b->moves_table, b->moved_paths, log_entry, copies, deleted_paths, - b->repos_root_url, - b->result_pool, scratch_pool)); + b->repos_root_url, b->repos_uuid, + b->ctx, b->result_pool, scratch_pool)); moves = apr_hash_get(b->moves_table, &log_entry->revision, sizeof(svn_revnum_t)); @@ -1503,7 +1701,9 @@ find_moves(void *baton, svn_log_entry_t static svn_error_t * find_moves_in_revision_range(struct apr_hash_t **moves_table, const char *repos_relpath, - svn_client_conflict_t *conflict, + const char *repos_root_url, + const char *repos_uuid, + const char *victim_abspath, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_client_ctx_t *ctx, @@ -1515,15 +1715,10 @@ find_moves_in_revision_range(struct apr_ const char *corrected_url; apr_array_header_t *paths; apr_array_header_t *revprops; - const char *repos_root_url; - const char *repos_uuid; struct find_moves_baton b = { 0 }; SVN_ERR_ASSERT(start_rev > end_rev); - SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid, - conflict, scratch_pool, - scratch_pool)); url = svn_path_url_add_component2(repos_root_url, repos_relpath, scratch_pool); SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, @@ -1540,7 +1735,7 @@ find_moves_in_revision_range(struct apr_ b.repos_root_url = repos_root_url; b.repos_uuid = repos_uuid; b.ctx = ctx; - b.victim_abspath = conflict->local_abspath; + b.victim_abspath = victim_abspath; b.moves_table = apr_hash_make(result_pool); b.moved_paths = apr_hash_make(scratch_pool); b.result_pool = result_pool; @@ -1562,12 +1757,14 @@ find_moves_in_revision_range(struct apr_ } /* Return new move information for a moved-along child MOVED_ALONG_RELPATH. + * Set MOVE->NODE_KIND to MOVED_ALONG_NODE_KIND. * Do not copy MOVE->NEXT and MOVE-PREV. * If MOVED_ALONG_RELPATH is empty, this effectively copies MOVE to * RESULT_POOL with NEXT and PREV pointers cleared. */ static struct repos_move_info * new_path_adjusted_move(struct repos_move_info *move, const char *moved_along_relpath, + svn_node_kind_t moved_along_node_kind, apr_pool_t *result_pool) { struct repos_move_info *new_move; @@ -1582,6 +1779,7 @@ new_path_adjusted_move(struct repos_move new_move->rev = move->rev; new_move->rev_author = apr_pstrdup(result_pool, move->rev_author); new_move->copyfrom_rev = move->copyfrom_rev; + new_move->node_kind = moved_along_node_kind; /* Ignore prev and next pointers. Caller will set them if needed. */ return new_move; @@ -1644,7 +1842,8 @@ find_next_moves_in_revision(apr_array_he struct repos_move_info *new_move; /* We have a winner. */ - new_move = new_path_adjusted_move(move, relpath, result_pool); + new_move = new_path_adjusted_move(move, relpath, prev_move->node_kind, + result_pool); if (*next_moves == NULL) *next_moves = apr_array_make(result_pool, 1, sizeof(struct repos_move_info *)); @@ -1743,6 +1942,188 @@ trace_moved_node(apr_hash_t *moves_table return SVN_NO_ERROR; } +/* Given a list of MOVES_IN_REVISION, figure out which of these moves + * move the node which was later on moved by NEXT_MOVE. */ +static svn_error_t * +find_prev_move_in_revision(struct repos_move_info **prev_move, + apr_array_header_t *moves_in_revision, + struct repos_move_info *next_move, + svn_ra_session_t *ra_session, + const char *repos_root_url, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i; + apr_pool_t *iterpool; + + *prev_move = NULL; + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < moves_in_revision->nelts; i++) + { + struct repos_move_info *move; + const char *relpath; + const char *deleted_repos_relpath; + svn_boolean_t related; + svn_error_t *err; + + svn_pool_clear(iterpool); + + /* Check if this move affects the current known path of our node. */ + move = APR_ARRAY_IDX(moves_in_revision, i, struct repos_move_info *); + relpath = svn_relpath_skip_ancestor(next_move->moved_from_repos_relpath, + move->moved_to_repos_relpath); + if (relpath == NULL) + continue; + + /* It does. So our node must have been deleted. */ + deleted_repos_relpath = svn_relpath_join( + next_move->moved_from_repos_relpath, + relpath, iterpool); + + /* Tracing back history of the delete-half of the next move to the + * copyfrom-revision of the prior move we must end up at the + * delete-half of the prior move. */ + err = check_move_ancestry(&related, ra_session, repos_root_url, + deleted_repos_relpath, next_move->rev, + move->moved_from_repos_relpath, + move->copyfrom_rev, + FALSE, scratch_pool); + if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + continue; + } + else + SVN_ERR(err); + + if (related) + { + /* We have a winner. */ + *prev_move = new_path_adjusted_move(move, relpath, + next_move->node_kind, + result_pool); + break; + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +static int +compare_items_as_revs_reverse(const svn_sort__item_t *a, + const svn_sort__item_t *b) +{ + int c = svn_sort_compare_revisions(a->key, b->key); + if (c < 0) + return 1; + if (c > 0) + return -1; + return c; +} + +/* Starting at MOVE->REV, loop over past revisions which contain moves, + * and look for a matching previous move in each. Once found, return + * it in *PREV_MOVE */ +static svn_error_t * +find_prev_move(struct repos_move_info **prev_move, + apr_hash_t *moves_table, + struct repos_move_info *move, + svn_ra_session_t *ra_session, + const char *repos_root_url, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *moves; + apr_array_header_t *revisions; + apr_pool_t *iterpool; + int i; + + *prev_move = NULL; + revisions = svn_sort__hash(moves_table, compare_items_as_revs_reverse, + scratch_pool); + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < revisions->nelts; i++) + { + svn_sort__item_t item = APR_ARRAY_IDX(revisions, i, svn_sort__item_t); + svn_revnum_t rev = *(svn_revnum_t *)item.key; + + svn_pool_clear(iterpool); + + if (rev >= move->rev) + continue; + + moves = apr_hash_get(moves_table, &rev, sizeof(rev)); + SVN_ERR(find_prev_move_in_revision(prev_move, moves, move, + ra_session, repos_root_url, + result_pool, iterpool)); + if (*prev_move) + break; + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Trace all past moves of the node moved by MOVE. + * Update MOVE->PREV and MOVE->NEXT accordingly. */ +static svn_error_t * +trace_moved_node_backwards(apr_hash_t *moves_table, + struct repos_move_info *move, + svn_ra_session_t *ra_session, + const char *repos_root_url, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct repos_move_info *prev_move; + + SVN_ERR(find_prev_move(&prev_move, moves_table, move, + ra_session, repos_root_url, + result_pool, scratch_pool)); + if (prev_move) + { + move->prev = prev_move; + prev_move->next = apr_array_make(result_pool, 1, + sizeof(struct repos_move_info *)); + APR_ARRAY_PUSH(prev_move->next, struct repos_move_info *) = move; + + SVN_ERR(trace_moved_node_backwards(moves_table, prev_move, + ra_session, repos_root_url, + result_pool, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +reparent_session_and_fetch_node_kind(svn_node_kind_t *node_kind, + svn_ra_session_t *ra_session, + const char *url, + svn_revnum_t peg_rev, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + + err = svn_ra_reparent(ra_session, url, scratch_pool); + if (err) + { + if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL) + { + svn_error_clear(err); + *node_kind = svn_node_unknown; + return SVN_NO_ERROR; + } + + return svn_error_trace(err); + } + + SVN_ERR(svn_ra_check_path(ra_session, "", peg_rev, node_kind, scratch_pool)); + + return SVN_NO_ERROR; +} + /* Scan MOVES_TABLE for moves which affect a particular deleted node, and * build a set of new move information for this node. * Return heads of all possible move chains in *MOVES. @@ -1766,6 +2147,7 @@ find_operative_moves(apr_array_header_t apr_array_header_t *moves_in_deleted_rev; int i; apr_pool_t *iterpool; + const char *session_url, *url = NULL; moves_in_deleted_rev = apr_hash_get(moves_table, &deleted_rev, sizeof(deleted_rev)); @@ -1775,6 +2157,8 @@ find_operative_moves(apr_array_header_t return SVN_NO_ERROR; } + SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, scratch_pool)); + /* Look for operative moves in the revision where the node was deleted. */ *moves = apr_array_make(scratch_pool, 0, sizeof(struct repos_move_info *)); iterpool = svn_pool_create(scratch_pool); @@ -1788,15 +2172,25 @@ find_operative_moves(apr_array_header_t move = APR_ARRAY_IDX(moves_in_deleted_rev, i, struct repos_move_info *); relpath = svn_relpath_skip_ancestor(move->moved_from_repos_relpath, deleted_repos_relpath); - if (relpath) + if (relpath && relpath[0] != '\0') { - struct repos_move_info *new_move; + svn_node_kind_t node_kind; - new_move = new_path_adjusted_move(move, relpath, result_pool); - APR_ARRAY_PUSH(*moves, struct repos_move_info *) = new_move; + url = svn_path_url_add_component2(repos_root_url, + deleted_repos_relpath, + iterpool); + SVN_ERR(reparent_session_and_fetch_node_kind(&node_kind, + ra_session, url, + rev_below(deleted_rev), + iterpool)); + move = new_path_adjusted_move(move, relpath, node_kind, result_pool); } + APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move; } + if (url != NULL) + SVN_ERR(svn_ra_reparent(ra_session, session_url, scratch_pool)); + /* If we didn't find any applicable moves, return NULL. */ if ((*moves)->nelts == 0) { @@ -1862,13 +2256,16 @@ find_revision_for_suspected_deletion(svn SVN_ERR_ASSERT(start_rev > end_rev); - SVN_ERR(find_moves_in_revision_range(&moves_table, parent_repos_relpath, - conflict, start_rev, end_rev, - ctx, result_pool, scratch_pool)); - SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid, conflict, scratch_pool, scratch_pool)); + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + + SVN_ERR(find_moves_in_revision_range(&moves_table, parent_repos_relpath, + repos_root_url, repos_uuid, + victim_abspath, start_rev, end_rev, + ctx, result_pool, scratch_pool)); + url = svn_path_url_add_component2(repos_root_url, parent_repos_relpath, scratch_pool); SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, @@ -1882,12 +2279,11 @@ find_revision_for_suspected_deletion(svn revprops = apr_array_make(scratch_pool, 1, sizeof(const char *)); APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; - victim_abspath = svn_client_conflict_get_local_abspath(conflict); b.victim_abspath = victim_abspath; b.deleted_repos_relpath = svn_relpath_join(parent_repos_relpath, deleted_basename, scratch_pool); b.related_repos_relpath = related_repos_relpath; - b.related_repos_peg_rev = related_peg_rev; + b.related_peg_rev = related_peg_rev; b.deleted_rev = SVN_INVALID_REVNUM; b.replacing_node_kind = svn_node_unknown; b.repos_root_url = repos_root_url; @@ -1929,7 +2325,7 @@ find_revision_for_suspected_deletion(svn *deleted_rev_author = move->rev_author; *replacing_node_kind = b.replacing_node_kind; SVN_ERR(find_operative_moves(moves, moves_table, - move->moved_from_repos_relpath, + b.deleted_repos_relpath, move->rev, ra_session, repos_root_url, result_pool, scratch_pool)); @@ -1954,7 +2350,6 @@ find_revision_for_suspected_deletion(svn b.deleted_repos_relpath, b.deleted_rev, ra_session, repos_root_url, result_pool, scratch_pool)); - } return SVN_NO_ERROR; @@ -1972,11 +2367,20 @@ struct conflict_tree_local_missing_detai /* The path which was deleted relative to the repository root. */ const char *deleted_repos_relpath; - /* Move information. If not NULL, this is an array of repos_move_info * - * elements. Each element is the head of a move chain which starts in - * DELETED_REV. */ + /* Move information about the conflict victim. If not NULL, this is an + * array of repos_move_info elements. Each element is the head of a + * move chain which starts in DELETED_REV. */ apr_array_header_t *moves; + /* Move information about siblings. Siblings are nodes which share + * a youngest common ancestor with the conflict victim. E.g. in case + * of a merge operation they are part of the merge source branch. + * If not NULL, this is an array of repos_move_info elements. + * Each element is the head of a move chain, which starts at some + * point in history after siblings and conflict victim forked off + * their common ancestor. */ + apr_array_header_t *sibling_moves; + /* If not NULL, this is the move target abspath. */ const char *moved_to_abspath; }; @@ -2063,6 +2467,140 @@ find_related_node(const char **related_r return SVN_NO_ERROR; } +/* Determine if REPOS_RELPATH@PEG_REV was moved at some point in its history. + * History's range of interest ends at END_REV which must be older than PEG_REV. + * + * VICTIM_ABSPATH is the abspath of a conflict victim in the working copy and + * will be used in notifications. + * + * Return any applicable move chain heads in *MOVES. + * If no moves can be found, set *MOVES to NULL. */ +static svn_error_t * +find_moves_in_natural_history(apr_array_header_t **moves, + const char *repos_relpath, + svn_revnum_t peg_rev, + svn_node_kind_t node_kind, + svn_revnum_t end_rev, + const char *victim_abspath, + const char *repos_root_url, + const char *repos_uuid, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *moves_table; + apr_array_header_t *revs; + apr_array_header_t *most_recent_moves = NULL; + int i; + apr_pool_t *iterpool; + + *moves = NULL; + + SVN_ERR(find_moves_in_revision_range(&moves_table, repos_relpath, + repos_root_url, repos_uuid, + victim_abspath, peg_rev, end_rev, + ctx, scratch_pool, scratch_pool)); + + iterpool = svn_pool_create(scratch_pool); + + /* Scan the moves table for applicable moves. */ + revs = svn_sort__hash(moves_table, compare_items_as_revs, scratch_pool); + for (i = revs->nelts - 1; i >= 0; i--) + { + svn_sort__item_t item = APR_ARRAY_IDX(revs, i, svn_sort__item_t); + apr_array_header_t *moves_in_rev = apr_hash_get(moves_table, item.key, + sizeof(svn_revnum_t)); + int j; + + svn_pool_clear(iterpool); + + /* Was repos relpath moved to its location in this revision? */ + for (j = 0; j < moves_in_rev->nelts; j++) + { + struct repos_move_info *move; + const char *relpath; + + move = APR_ARRAY_IDX(moves_in_rev, j, struct repos_move_info *); + relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath, + repos_relpath); + if (relpath) + { + /* If the move did not happen in our peg revision, make + * sure this move happened on the same line of history. */ + if (move->rev != peg_rev) + { + svn_client__pathrev_t *yca_loc; + svn_error_t *err; + + err = find_yca(&yca_loc, repos_relpath, peg_rev, + repos_relpath, move->rev, + repos_root_url, repos_uuid, + NULL, ctx, iterpool, iterpool); + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + yca_loc = NULL; + } + else + return svn_error_trace(err); + } + + if (yca_loc == NULL || yca_loc->rev != move->rev) + continue; + } + + if (most_recent_moves == NULL) + most_recent_moves = + apr_array_make(result_pool, 1, + sizeof(struct repos_move_info *)); + + /* Copy the move to result pool (even if relpath is ""). */ + move = new_path_adjusted_move(move, relpath, node_kind, + result_pool); + APR_ARRAY_PUSH(most_recent_moves, + struct repos_move_info *) = move; + } + } + + /* If we found one move, or several ambiguous moves, we're done. */ + if (most_recent_moves) + break; + } + + if (most_recent_moves && most_recent_moves->nelts > 0) + { + *moves = apr_array_make(result_pool, 1, + sizeof(struct repos_move_info *)); + + /* Figure out what happened to the most recent moves in prior + * revisions and build move chains. */ + for (i = 0; i < most_recent_moves->nelts; i++) + { + struct repos_move_info *move; + + svn_pool_clear(iterpool); + + move = APR_ARRAY_IDX(most_recent_moves, i, struct repos_move_info *); + SVN_ERR(trace_moved_node_backwards(moves_table, move, + ra_session, repos_root_url, + result_pool, iterpool)); + /* Follow the move chain backwards. */ + while (move->prev) + move = move->prev; + + /* Return move heads. */ + APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move; + } + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + /* Implements tree_conflict_get_details_func_t. */ static svn_error_t * conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, @@ -2080,9 +2618,12 @@ conflict_tree_get_details_local_missing( svn_node_kind_t replacing_node_kind; const char *deleted_basename; struct conflict_tree_local_missing_details *details; - apr_array_header_t *moves; + apr_array_header_t *moves = NULL; + apr_array_header_t *sibling_moves = NULL; const char *related_repos_relpath; svn_revnum_t related_peg_rev; + const char *repos_root_url; + const char *repos_uuid; SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( &old_repos_relpath, &old_rev, NULL, conflict, @@ -2096,7 +2637,7 @@ conflict_tree_get_details_local_missing( deleted_basename = svn_dirent_basename(conflict->local_abspath, scratch_pool); SVN_ERR(svn_wc__node_get_repos_info(&parent_peg_rev, &parent_repos_relpath, - NULL, NULL, + &repos_root_url, &repos_uuid, ctx->wc_ctx, svn_dirent_dirname( conflict->local_abspath, @@ -2126,16 +2667,72 @@ conflict_tree_get_details_local_missing( parent_peg_rev, 0, related_repos_relpath, related_peg_rev, ctx, conflict->pool, scratch_pool)); + /* If the victim was not deleted then check if the related path was moved. */ if (deleted_rev == SVN_INVALID_REVNUM) - return SVN_NO_ERROR; + { + const char *victim_abspath; + svn_ra_session_t *ra_session; + const char *url, *corrected_url; + svn_client__pathrev_t *yca_loc; + svn_revnum_t end_rev; + svn_node_kind_t related_node_kind; + + /* ### The following describes all moves in terms of forward-merges, + * should do we something else for reverse-merges? */ + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + url = svn_path_url_add_component2(repos_root_url, related_repos_relpath, + scratch_pool); + SVN_ERR(svn_client__open_ra_session_internal(&ra_session, + &corrected_url, + url, NULL, NULL, + FALSE, + FALSE, + ctx, + scratch_pool, + scratch_pool)); + + /* Set END_REV to our best guess of the nearest YCA revision. */ + SVN_ERR(find_nearest_yca(&yca_loc, related_repos_relpath, related_peg_rev, + parent_repos_relpath, parent_peg_rev, + repos_root_url, repos_uuid, ra_session, ctx, + scratch_pool, scratch_pool)); + if (yca_loc == NULL) + return SVN_NO_ERROR; + end_rev = yca_loc->rev; + + /* END_REV must be smaller than RELATED_PEG_REV, else the call + to find_moves_in_natural_history() below will error out. */ + if (end_rev >= related_peg_rev) + end_rev = related_peg_rev > 0 ? related_peg_rev - 1 : 0; + + SVN_ERR(svn_ra_check_path(ra_session, "", related_peg_rev, + &related_node_kind, scratch_pool)); + SVN_ERR(find_moves_in_natural_history(&sibling_moves, + related_repos_relpath, + related_peg_rev, + related_node_kind, + end_rev, + victim_abspath, + repos_root_url, repos_uuid, + ra_session, ctx, + conflict->pool, scratch_pool)); + + if (sibling_moves == NULL) + return SVN_NO_ERROR; + + /* ## TODO: Find the missing node in the WC. */ + } details = apr_pcalloc(conflict->pool, sizeof(*details)); details->deleted_rev = deleted_rev; details->deleted_rev_author = deleted_rev_author; - details->deleted_repos_relpath = svn_relpath_join(parent_repos_relpath, - deleted_basename, - conflict->pool); + if (deleted_rev != SVN_INVALID_REVNUM) + details->deleted_repos_relpath = svn_relpath_join(parent_repos_relpath, + deleted_basename, + conflict->pool); details->moves = moves; + details->sibling_moves = sibling_moves; conflict->tree_conflict_local_details = details; @@ -2287,22 +2884,74 @@ conflict_tree_get_description_local_miss description, conflict, ctx, result_pool, scratch_pool)); - if (details->moves) + if (details->moves || details->sibling_moves) { struct repos_move_info *move; + + *description = _("No such file or directory was found in the " + "merge target working copy.\n"); - move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); - *description = apr_psprintf( - result_pool, - _("No such file or directory was found in the " - "merge target working copy.\nThe item was " - "moved away to '^/%s' in r%ld by %s."), - move->moved_to_repos_relpath, - move->rev, move->rev_author); - *description = append_moved_to_chain_description(*description, - move->next, - result_pool, - scratch_pool); + if (details->moves) + { + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + if (move->node_kind == svn_node_file) + *description = apr_psprintf( + result_pool, + _("%sThe file was moved to '^/%s' in r%ld by %s."), + *description, move->moved_to_repos_relpath, + move->rev, move->rev_author); + else if (move->node_kind == svn_node_dir) + *description = apr_psprintf( + result_pool, + _("%sThe directory was moved to '^/%s' in " + "r%ld by %s."), + *description, move->moved_to_repos_relpath, + move->rev, move->rev_author); + else + *description = apr_psprintf( + result_pool, + _("%sThe item was moved to '^/%s' in r%ld by %s."), + *description, move->moved_to_repos_relpath, + move->rev, move->rev_author); + *description = append_moved_to_chain_description(*description, + move->next, + result_pool, + scratch_pool); + } + + if (details->sibling_moves) + { + move = APR_ARRAY_IDX(details->sibling_moves, 0, + struct repos_move_info *); + if (move->node_kind == svn_node_file) + *description = apr_psprintf( + result_pool, + _("%sThe file '^/%s' was moved to '^/%s' " + "in r%ld by %s."), + *description, move->moved_from_repos_relpath, + move->moved_to_repos_relpath, + move->rev, move->rev_author); + else if (move->node_kind == svn_node_dir) + *description = apr_psprintf( + result_pool, + _("%sThe directory '^/%s' was moved to '^/%s' " + "in r%ld by %s."), + *description, move->moved_from_repos_relpath, + move->moved_to_repos_relpath, + move->rev, move->rev_author); + else + *description = apr_psprintf( + result_pool, + _("%sThe item '^/%s' was moved to '^/%s' " + "in r%ld by %s."), + *description, move->moved_from_repos_relpath, + move->moved_to_repos_relpath, + move->rev, move->rev_author); + *description = append_moved_to_chain_description(*description, + move->next, + result_pool, + scratch_pool); + } } else *description = apr_psprintf( @@ -3994,7 +4643,8 @@ get_incoming_delete_details_for_reverse_ } /* Follow each move chain starting a MOVE all the way to the end to find - * the possible working copy locations for VICTIM_ABSPATH at PEG_REVISION. + * the possible working copy locations for VICTIM_ABSPATH which corresponds + * to VICTIM_REPOS_REPLATH@VICTIM_REVISION. * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the * repos_relpath which is the corresponding move destination in the repository. * This function is recursive. */ @@ -4004,7 +4654,8 @@ follow_move_chains(apr_hash_t *wc_move_t svn_client_ctx_t *ctx, const char *victim_abspath, svn_node_kind_t victim_node_kind, - svn_revnum_t peg_revision, + const char *victim_repos_relpath, + svn_revnum_t victim_revision, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -4012,17 +4663,85 @@ follow_move_chains(apr_hash_t *wc_move_t * the working copy and add them to our collection if found. */ if (move->next == NULL) { - apr_array_header_t *moved_to_abspaths; + apr_array_header_t *candidate_abspaths; - /* Gather nodes which represent this moved_to_repos_relpath. */ + /* Gather candidate nodes which represent this moved_to_repos_relpath. */ SVN_ERR(svn_wc__guess_incoming_move_target_nodes( - &moved_to_abspaths, ctx->wc_ctx, + &candidate_abspaths, ctx->wc_ctx, victim_abspath, victim_node_kind, move->moved_to_repos_relpath, - peg_revision, result_pool, scratch_pool)); - if (moved_to_abspaths->nelts > 0) - svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath, - moved_to_abspaths); + scratch_pool, scratch_pool)); + if (candidate_abspaths->nelts > 0) + { + apr_array_header_t *moved_to_abspaths; + int i; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + moved_to_abspaths = apr_array_make(result_pool, 1, + sizeof (const char *)); + + for (i = 0; i < candidate_abspaths->nelts; i++) + { + const char *candidate_abspath; + const char *repos_root_url; + const char *repos_uuid; + const char *candidate_repos_relpath; + svn_revnum_t candidate_revision; + + svn_pool_clear(iterpool); + + candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i, + const char *); + SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision, + &candidate_repos_relpath, + &repos_root_url, + &repos_uuid, + NULL, NULL, + ctx->wc_ctx, + candidate_abspath, + FALSE, + iterpool, iterpool)); + + if (candidate_revision == SVN_INVALID_REVNUM) + continue; + + /* If the conflict victim and the move target candidate + * are not from the same revision we must ensure that + * they are related. */ + if (candidate_revision != victim_revision) + { + svn_client__pathrev_t *yca_loc; + svn_error_t *err; + + err = find_yca(&yca_loc, victim_repos_relpath, + victim_revision, + candidate_repos_relpath, + candidate_revision, + repos_root_url, repos_uuid, + NULL, ctx, iterpool, iterpool); + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + yca_loc = NULL; + } + else + return svn_error_trace(err); + } + + if (yca_loc == NULL) + continue; + } + + APR_ARRAY_PUSH(moved_to_abspaths, const char *) = + apr_pstrdup(result_pool, candidate_abspath); + } + svn_pool_destroy(iterpool); + + svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath, + moved_to_abspaths); + } } else { @@ -4040,7 +4759,8 @@ follow_move_chains(apr_hash_t *wc_move_t next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *); SVN_ERR(follow_move_chains(wc_move_targets, next_move, ctx, victim_abspath, victim_node_kind, - peg_revision, result_pool, iterpool)); + victim_repos_relpath, victim_revision, + result_pool, iterpool)); } svn_pool_destroy(iterpool); @@ -4058,14 +4778,17 @@ init_wc_move_targets(struct conflict_tre int i; const char *victim_abspath; svn_node_kind_t victim_node_kind; + const char *incoming_new_repos_relpath; svn_revnum_t incoming_new_pegrev; svn_wc_operation_t operation; victim_abspath = svn_client_conflict_get_local_abspath(conflict); victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); operation = svn_client_conflict_get_operation(conflict); + /* ### Should we get the old location in case of reverse-merges? */ SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( - NULL, &incoming_new_pegrev, NULL, conflict, + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, scratch_pool)); details->wc_move_targets = apr_hash_make(conflict->pool); for (i = 0; i < details->moves->nelts; i++) @@ -4076,6 +4799,7 @@ init_wc_move_targets(struct conflict_tre SVN_ERR(follow_move_chains(details->wc_move_targets, move, ctx, victim_abspath, victim_node_kind, + incoming_new_repos_relpath, incoming_new_pegrev, conflict->pool, scratch_pool)); } @@ -9264,6 +9988,7 @@ configure_option_local_move_file_merge(s SVN_ERR(follow_move_chains(wc_move_targets, move, ctx, conflict->local_abspath, svn_node_file, + incoming_new_repos_relpath, incoming_new_pegrev, scratch_pool, iterpool)); } @@ -9603,6 +10328,22 @@ svn_client_conflict_tree_get_resolution_ return SVN_NO_ERROR; } +/* Swallow authz failures and return SVN_NO_ERROR in that case. + * Otherwise, return ERR unchanged. */ +static svn_error_t * +ignore_authz_failures(svn_error_t *err) +{ + if (err && ( svn_error_find_cause(err, SVN_ERR_AUTHZ_UNREADABLE) + || svn_error_find_cause(err, SVN_ERR_RA_NOT_AUTHORIZED) + || svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN))) + { + svn_error_clear(err); + err = SVN_NO_ERROR; + } + + return err; +} + svn_error_t * svn_client_conflict_tree_get_details(svn_client_conflict_t *conflict, svn_client_ctx_t *ctx, @@ -9622,13 +10363,18 @@ svn_client_conflict_tree_get_details(svn scratch_pool); } + /* Collecting conflict details may fail due to insufficient access rights. + * This is not a failure but simply restricts our future options. */ if (conflict->tree_conflict_get_incoming_details_func) - SVN_ERR(conflict->tree_conflict_get_incoming_details_func(conflict, ctx, - scratch_pool)); + SVN_ERR(ignore_authz_failures( + conflict->tree_conflict_get_incoming_details_func(conflict, ctx, + scratch_pool))); + if (conflict->tree_conflict_get_local_details_func) - SVN_ERR(conflict->tree_conflict_get_local_details_func(conflict, ctx, - scratch_pool)); + SVN_ERR(ignore_authz_failures( + conflict->tree_conflict_get_local_details_func(conflict, ctx, + scratch_pool))); if (ctx->notify_func2) {
Modified: subversion/branches/shelve-checkpoint/subversion/libsvn_client/diff.c URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint/subversion/libsvn_client/diff.c?rev=1813860&r1=1813859&r2=1813860&view=diff ============================================================================== --- subversion/branches/shelve-checkpoint/subversion/libsvn_client/diff.c (original) +++ subversion/branches/shelve-checkpoint/subversion/libsvn_client/diff.c Tue Oct 31 09:39:59 2017 @@ -422,7 +422,8 @@ print_git_diff_header(svn_stream_t *os, scratch_pool)); *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1), rev1, scratch_pool); - *label2 = diff_label("/dev/null", rev2, scratch_pool); + *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), + rev2, scratch_pool); } else if (operation == svn_diff_op_copied) @@ -447,7 +448,8 @@ print_git_diff_header(svn_stream_t *os, repos_relpath1, repos_relpath2, exec_bit2, symlink_bit2, scratch_pool)); - *label1 = diff_label("/dev/null", rev1, scratch_pool); + *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1), + rev1, scratch_pool); *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), rev2, scratch_pool); } Modified: subversion/branches/shelve-checkpoint/subversion/libsvn_client/list.c URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint/subversion/libsvn_client/list.c?rev=1813860&r1=1813859&r2=1813860&view=diff ============================================================================== --- subversion/branches/shelve-checkpoint/subversion/libsvn_client/list.c (original) +++ subversion/branches/shelve-checkpoint/subversion/libsvn_client/list.c Tue Oct 31 09:39:59 2017 @@ -37,6 +37,7 @@ #include "private/svn_fspath.h" #include "private/svn_ra_private.h" #include "private/svn_sorts_private.h" +#include "private/svn_utf_private.h" #include "private/svn_wc_private.h" #include "svn_private_config.h" @@ -69,23 +70,16 @@ list_internal(const char *path_or_url, apr_pool_t *pool); /* Return TRUE if S matches any of the const char * in PATTERNS. - * Note that any S will match if PATTERNS is empty. */ + * Note that any S will match if PATTERNS is empty. + * Use SCRATCH_BUFFER for temporary string contents. */ static svn_boolean_t match_patterns(const char *s, - const apr_array_header_t *patterns) + const apr_array_header_t *patterns, + svn_membuf_t *scratch_buffer) { - int i; - if (!patterns) - return TRUE; - - for (i = 0; i < patterns->nelts; ++i) - { - const char *pattern = APR_ARRAY_IDX(patterns, i, const char *); - if (apr_fnmatch(pattern, s, APR_FNM_PERIOD) == APR_SUCCESS) - return TRUE; - } - - return FALSE; + return patterns + ? svn_utf__fuzzy_glob_match(s, patterns, scratch_buffer) + : TRUE; } /* Get the directory entries of DIR at REV (relative to the root of @@ -113,6 +107,8 @@ match_patterns(const char *s, EXTERNAL_PARENT_URL and EXTERNAL_TARGET are set when external items are listed, otherwise both are set to NULL by the caller. + + Use SCRATCH_BUFFER for temporary string contents. */ static svn_error_t * get_dir_contents(apr_uint32_t dirent_fields, @@ -129,6 +125,7 @@ get_dir_contents(apr_uint32_t dirent_fie const char *external_target, svn_client_list_func2_t list_func, void *baton, + svn_membuf_t *scratch_buffer, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -203,7 +200,7 @@ get_dir_contents(apr_uint32_t dirent_fie if (the_ent->kind == svn_node_file || depth == svn_depth_immediates || depth == svn_depth_infinity) - if (match_patterns(item->key, patterns)) + if (match_patterns(item->key, patterns, scratch_buffer)) SVN_ERR(list_func(baton, path, the_ent, lock, fs_path, external_parent_url, external_target, iterpool)); @@ -214,7 +211,7 @@ get_dir_contents(apr_uint32_t dirent_fie locks, fs_path, patterns, depth, ctx, externals, external_parent_url, external_target, list_func, baton, - result_pool, iterpool)); + scratch_buffer, result_pool, iterpool)); } svn_pool_destroy(iterpool); @@ -329,6 +326,7 @@ list_internal(const char *path_or_url, svn_error_t *err; apr_hash_t *locks; apr_hash_t *externals; + svn_membuf_t scratch_buffer; if (include_externals) externals = apr_hash_make(pool); @@ -391,8 +389,13 @@ list_internal(const char *path_or_url, _("URL '%s' non-existent in revision %ld"), loc->url, loc->rev); + /* We need a scratch buffer for temporary string data. + * Create one with a reasonable initial size. */ + svn_membuf__create(&scratch_buffer, 256, pool); + /* Report the dirent for the target. */ - if (match_patterns(svn_dirent_dirname(fs_path, pool), patterns)) + if (match_patterns(svn_dirent_dirname(fs_path, pool), patterns, + &scratch_buffer)) SVN_ERR(list_func(baton, "", dirent, locks ? (svn_hash_gets(locks, fs_path)) : NULL, fs_path, external_parent_url, @@ -405,7 +408,7 @@ list_internal(const char *path_or_url, SVN_ERR(get_dir_contents(dirent_fields, "", loc->rev, ra_session, locks, fs_path, patterns, depth, ctx, externals, external_parent_url, external_target, list_func, - baton, pool, pool)); + baton, &scratch_buffer, pool, pool)); /* We handle externals after listing entries under path_or_url, so that handling external items (and any errors therefrom) doesn't delay Modified: subversion/branches/shelve-checkpoint/subversion/libsvn_client/shelve.c URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint/subversion/libsvn_client/shelve.c?rev=1813860&r1=1813859&r2=1813860&view=diff ============================================================================== --- subversion/branches/shelve-checkpoint/subversion/libsvn_client/shelve.c (original) +++ subversion/branches/shelve-checkpoint/subversion/libsvn_client/shelve.c Tue Oct 31 09:39:59 2017 @@ -42,12 +42,12 @@ /* */ static svn_error_t * -validate_shelf_name(const char *shelf_name, - apr_pool_t *scratch_pool) +validate_name(const char *name, + apr_pool_t *scratch_pool) { - if (shelf_name[0] == '\0' || strchr(shelf_name, '/')) + if (name[0] == '\0' || strchr(name, '/')) return svn_error_createf(SVN_ERR_BAD_CHANGELIST_NAME, NULL, - _("Shelve: Bad name '%s'"), shelf_name); + _("Shelve: Bad name '%s'"), name); return SVN_NO_ERROR; } @@ -55,7 +55,7 @@ validate_shelf_name(const char *shelf_na /* */ static svn_error_t * get_patch_abspath(char **patch_abspath, - const char *shelf_name, + const char *name, const char *wc_root_abspath, svn_client_ctx_t *ctx, apr_pool_t *result_pool, @@ -66,13 +66,13 @@ get_patch_abspath(char **patch_abspath, SVN_ERR(svn_wc__get_shelves_dir(&dir, ctx->wc_ctx, wc_root_abspath, scratch_pool, scratch_pool)); - filename = apr_pstrcat(scratch_pool, shelf_name, ".patch", SVN_VA_NULL); + filename = apr_pstrcat(scratch_pool, name, ".patch", SVN_VA_NULL); *patch_abspath = svn_dirent_join(dir, filename, result_pool); return SVN_NO_ERROR; } svn_error_t * -svn_client_shelf_write_patch(const char *shelf_name, +svn_client_shelf_write_patch(const char *name, const char *message, const char *wc_root_abspath, svn_boolean_t overwrite_existing, @@ -93,7 +93,9 @@ svn_client_shelf_write_patch(const char svn_opt_revision_t start_revision = {svn_opt_revision_base, {0}}; svn_opt_revision_t end_revision = {svn_opt_revision_working, {0}}; - SVN_ERR(get_patch_abspath(&patch_abspath, shelf_name, wc_root_abspath, + printf("writing '%s.patch'\n", name); + + SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath, ctx, scratch_pool, scratch_pool)); /* Get streams for the output and any error output of the diff. */ @@ -156,7 +158,7 @@ svn_client_shelf_write_patch(const char } svn_error_t * -svn_client_shelf_apply_patch(const char *shelf_name, +svn_client_shelf_apply_patch(const char *name, const char *wc_root_abspath, svn_boolean_t reverse, svn_boolean_t dry_run, @@ -165,7 +167,7 @@ svn_client_shelf_apply_patch(const char { char *patch_abspath; - SVN_ERR(get_patch_abspath(&patch_abspath, shelf_name, wc_root_abspath, + SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath, ctx, scratch_pool, scratch_pool)); SVN_ERR(svn_client_patch(patch_abspath, wc_root_abspath, dry_run, 0 /*strip*/, @@ -178,25 +180,34 @@ svn_client_shelf_apply_patch(const char } svn_error_t * -svn_client_shelf_delete_patch(const char *shelf_name, +svn_client_shelf_delete_patch(const char *name, const char *wc_root_abspath, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { - char *patch_abspath; + char *patch_abspath, *to_abspath; - SVN_ERR(get_patch_abspath(&patch_abspath, shelf_name, wc_root_abspath, + SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath, ctx, scratch_pool, scratch_pool)); - SVN_ERR(svn_io_remove_file2(patch_abspath, FALSE /*ignore_enoent*/, + to_abspath = apr_pstrcat(scratch_pool, patch_abspath, ".bak", SVN_VA_NULL); + + /* remove any previous backup */ + SVN_ERR(svn_io_remove_file2(to_abspath, TRUE /*ignore_enoent*/, + scratch_pool)); + + /* move the patch to a backup file */ + printf("moving '%s.patch' to '%s.patch.bak'\n", name, name); + SVN_ERR(svn_io_file_rename2(patch_abspath, to_abspath, FALSE /*flush_to_disk*/, scratch_pool)); return SVN_NO_ERROR; } svn_error_t * -svn_client_shelve(const char *shelf_name, +svn_client_shelve(const char *name, const apr_array_header_t *paths, svn_depth_t depth, const apr_array_header_t *changelists, + svn_boolean_t keep_local, svn_boolean_t dry_run, svn_client_ctx_t *ctx, apr_pool_t *pool) @@ -206,7 +217,7 @@ svn_client_shelve(const char *shelf_name const char *message = ""; svn_error_t *err; - SVN_ERR(validate_shelf_name(shelf_name, pool)); + SVN_ERR(validate_name(name, pool)); /* ### TODO: check all paths are in same WC; for now use first path */ SVN_ERR(svn_dirent_get_absolute(&local_abspath, @@ -226,7 +237,7 @@ svn_client_shelve(const char *shelf_name return SVN_NO_ERROR; } - err = svn_client_shelf_write_patch(shelf_name, message, wc_root_abspath, + err = svn_client_shelf_write_patch(name, message, wc_root_abspath, FALSE /*overwrite_existing*/, paths, depth, changelists, ctx, pool); @@ -234,20 +245,23 @@ svn_client_shelve(const char *shelf_name { return svn_error_quick_wrapf(err, "Shelved change '%s' already exists", - shelf_name); + name); } else SVN_ERR(err); - /* Reverse-apply the patch. This should be a safer way to remove those - changes from the WC than running a 'revert' operation. */ - SVN_ERR(svn_client_shelf_apply_patch(shelf_name, wc_root_abspath, - TRUE /*reverse*/, dry_run, - ctx, pool)); + if (!keep_local) + { + /* Reverse-apply the patch. This should be a safer way to remove those + changes from the WC than running a 'revert' operation. */ + SVN_ERR(svn_client_shelf_apply_patch(name, wc_root_abspath, + TRUE /*reverse*/, dry_run, + ctx, pool)); + } if (dry_run) { - SVN_ERR(svn_client_shelf_delete_patch(shelf_name, wc_root_abspath, + SVN_ERR(svn_client_shelf_delete_patch(name, wc_root_abspath, ctx, pool)); } @@ -255,7 +269,7 @@ svn_client_shelve(const char *shelf_name } svn_error_t * -svn_client_unshelve(const char *shelf_name, +svn_client_unshelve(const char *name, const char *local_abspath, svn_boolean_t keep, svn_boolean_t dry_run, @@ -265,20 +279,20 @@ svn_client_unshelve(const char *shelf_na const char *wc_root_abspath; svn_error_t *err; - SVN_ERR(validate_shelf_name(shelf_name, pool)); + SVN_ERR(validate_name(name, pool)); SVN_ERR(svn_client_get_wc_root(&wc_root_abspath, local_abspath, ctx, pool, pool)); /* Apply the patch. */ - err = svn_client_shelf_apply_patch(shelf_name, wc_root_abspath, + err = svn_client_shelf_apply_patch(name, wc_root_abspath, FALSE /*reverse*/, dry_run, ctx, pool); if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) { return svn_error_quick_wrapf(err, "Shelved change '%s' not found", - shelf_name); + name); } else SVN_ERR(err); @@ -286,7 +300,7 @@ svn_client_unshelve(const char *shelf_na /* Remove the patch. */ if (! keep && ! dry_run) { - SVN_ERR(svn_client_shelf_delete_patch(shelf_name, wc_root_abspath, + SVN_ERR(svn_client_shelf_delete_patch(name, wc_root_abspath, ctx, pool)); } @@ -294,7 +308,7 @@ svn_client_unshelve(const char *shelf_na } svn_error_t * -svn_client_shelves_delete(const char *shelf_name, +svn_client_shelves_delete(const char *name, const char *local_abspath, svn_boolean_t dry_run, svn_client_ctx_t *ctx, @@ -302,7 +316,7 @@ svn_client_shelves_delete(const char *sh { const char *wc_root_abspath; - SVN_ERR(validate_shelf_name(shelf_name, pool)); + SVN_ERR(validate_name(name, pool)); SVN_ERR(svn_client_get_wc_root(&wc_root_abspath, local_abspath, ctx, pool, pool)); @@ -312,13 +326,13 @@ svn_client_shelves_delete(const char *sh { svn_error_t *err; - err = svn_client_shelf_delete_patch(shelf_name, wc_root_abspath, + err = svn_client_shelf_delete_patch(name, wc_root_abspath, ctx, pool); if (err && APR_STATUS_IS_ENOENT(err->apr_err)) { return svn_error_quick_wrapf(err, "Shelved change '%s' not found", - shelf_name); + name); } else SVN_ERR(err); @@ -327,32 +341,82 @@ svn_client_shelves_delete(const char *sh return SVN_NO_ERROR; } +/* ### Currently just reads the first line. + */ +static svn_error_t * +read_logmsg_from_patch(const char **logmsg, + const char *patch_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_file_t *file; + svn_stream_t *stream; + svn_boolean_t eof; + svn_stringbuf_t *line; + + SVN_ERR(svn_io_file_open(&file, patch_abspath, + APR_FOPEN_READ, APR_FPROT_OS_DEFAULT, scratch_pool)); + stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool); + SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool)); + SVN_ERR(svn_stream_close(stream)); + *logmsg = line->data; + return SVN_NO_ERROR; +} + svn_error_t * -svn_client_shelves_list(apr_hash_t **dirents, +svn_client_shelves_list(apr_hash_t **shelved_patch_infos, const char *local_abspath, svn_client_ctx_t *ctx, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { char *shelves_dir; + apr_hash_t *dirents; apr_hash_index_t *hi; SVN_ERR(svn_wc__get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath, scratch_pool, scratch_pool)); - SVN_ERR(svn_io_get_dirents3(dirents, shelves_dir, FALSE /*only_check_type*/, + SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/, result_pool, scratch_pool)); + *shelved_patch_infos = apr_hash_make(result_pool); + /* Remove non-shelves */ - for (hi = apr_hash_first(scratch_pool, *dirents); hi; hi = apr_hash_next(hi)) + for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi)) { - const char *name = apr_hash_this_key(hi); + const char *filename = apr_hash_this_key(hi); + int len = strlen(filename); - if (! strstr(name, ".patch")) + if (len > 6 && strcmp(filename + len - 6, ".patch") == 0) { - svn_hash_sets(*dirents, name, NULL); + const char *name = apr_pstrndup(result_pool, filename, len - 6); + svn_client_shelved_patch_info_t *info + = apr_palloc(result_pool, sizeof(*info)); + + info->dirent = apr_hash_this_val(hi); + info->mtime = info->dirent->mtime; + info->patch_path + = svn_dirent_join(shelves_dir, filename, result_pool); + SVN_ERR(read_logmsg_from_patch(&info->message, info->patch_path, + result_pool, scratch_pool)); + + svn_hash_sets(*shelved_patch_infos, name, info); } } return SVN_NO_ERROR; } +svn_error_t * +svn_client_shelves_any(svn_boolean_t *any_shelved, + const char *local_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + apr_hash_t *shelved_patch_infos; + + SVN_ERR(svn_client_shelves_list(&shelved_patch_infos, local_abspath, + ctx, scratch_pool, scratch_pool)); + *any_shelved = apr_hash_count(shelved_patch_infos) != 0; + return SVN_NO_ERROR; +} Modified: subversion/branches/shelve-checkpoint/subversion/libsvn_ra_serf/merge.c URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint/subversion/libsvn_ra_serf/merge.c?rev=1813860&r1=1813859&r2=1813860&view=diff ============================================================================== --- subversion/branches/shelve-checkpoint/subversion/libsvn_ra_serf/merge.c (original) +++ subversion/branches/shelve-checkpoint/subversion/libsvn_ra_serf/merge.c Tue Oct 31 09:39:59 2017 @@ -79,6 +79,7 @@ typedef struct merge_context_t apr_hash_t *lock_tokens; svn_boolean_t keep_locks; + svn_boolean_t disable_merge_response; const char *merge_resource_url; /* URL of resource to be merged. */ const char *merge_url; /* URL at which the MERGE request is aimed. */ @@ -275,12 +276,17 @@ setup_merge_headers(serf_bucket_t *heade apr_pool_t *scratch_pool) { merge_context_t *ctx = baton; + apr_array_header_t *vals = apr_array_make(scratch_pool, 2, + sizeof(const char *)); if (!ctx->keep_locks) - { - serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER, - SVN_DAV_OPTION_RELEASE_LOCKS); - } + APR_ARRAY_PUSH(vals, const char *) = SVN_DAV_OPTION_RELEASE_LOCKS; + if (ctx->disable_merge_response) + APR_ARRAY_PUSH(vals, const char *) = SVN_DAV_OPTION_NO_MERGE_RESPONSE; + + if (vals->nelts > 0) + serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER, + svn_cstring_join2(vals, " ", FALSE, scratch_pool)); return SVN_NO_ERROR; } @@ -412,6 +418,13 @@ svn_ra_serf__run_merge(const svn_commit_ merge_ctx->lock_tokens = lock_tokens; merge_ctx->keep_locks = keep_locks; + /* We don't need the full merge response when working over HTTPv2. + * Over HTTPv1, this response is only required with a non-null + * svn_ra_push_wc_prop_func_t callback. */ + merge_ctx->disable_merge_response = + SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session) || + session->wc_callbacks->push_wc_prop == NULL; + merge_ctx->commit_info = svn_create_commit_info(result_pool); merge_ctx->merge_url = session->session_url.path; Modified: subversion/branches/shelve-checkpoint/subversion/libsvn_repos/deprecated.c URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint/subversion/libsvn_repos/deprecated.c?rev=1813860&r1=1813859&r2=1813860&view=diff ============================================================================== --- subversion/branches/shelve-checkpoint/subversion/libsvn_repos/deprecated.c (original) +++ subversion/branches/shelve-checkpoint/subversion/libsvn_repos/deprecated.c Tue Oct 31 09:39:59 2017 @@ -822,6 +822,7 @@ svn_repos_dump_fs3(svn_repos_t *repos, TRUE, notify_func, notify_baton, + NULL, NULL, cancel_func, cancel_baton, pool)); @@ -901,6 +902,31 @@ svn_repos_verify_fs(svn_repos_t *repos, /*** From load.c ***/ svn_error_t * +svn_repos_load_fs5(svn_repos_t *repos, + svn_stream_t *dumpstream, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + enum svn_repos_load_uuid uuid_action, + const char *parent_dir, + svn_boolean_t use_pre_commit_hook, + svn_boolean_t use_post_commit_hook, + svn_boolean_t validate_props, + svn_boolean_t ignore_dates, + svn_repos_notify_func_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + return svn_repos_load_fs6(repos, dumpstream, start_rev, end_rev, + uuid_action, parent_dir, + use_post_commit_hook, use_post_commit_hook, + validate_props, ignore_dates, FALSE, + notify_func, notify_baton, + cancel_func, cancel_baton, pool); +} + +svn_error_t * svn_repos_load_fs4(svn_repos_t *repos, svn_stream_t *dumpstream, svn_revnum_t start_rev, @@ -1092,6 +1118,40 @@ svn_repos_load_fs(svn_repos_t *repos, } svn_error_t * +svn_repos_get_fs_build_parser5(const svn_repos_parse_fns3_t **parser, + void **parse_baton, + svn_repos_t *repos, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_boolean_t use_history, + svn_boolean_t validate_props, + enum svn_repos_load_uuid uuid_action, + const char *parent_dir, + svn_boolean_t use_pre_commit_hook, + svn_boolean_t use_post_commit_hook, + svn_boolean_t ignore_dates, + svn_repos_notify_func_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + SVN_ERR(svn_repos_get_fs_build_parser6(parser, parse_baton, + repos, + start_rev, end_rev, + use_history, + validate_props, + uuid_action, + parent_dir, + use_pre_commit_hook, + use_post_commit_hook, + ignore_dates, + FALSE /* normalize_props */, + notify_func, + notify_baton, + pool)); + return SVN_NO_ERROR; +} + +svn_error_t * svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **callbacks, void **parse_baton, svn_repos_t *repos,
