1251881Speter/* 2251881Speter * log.c: return log messages 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter#define APR_WANT_STRFUNC 25251881Speter#include <apr_want.h> 26251881Speter 27251881Speter#include <apr_strings.h> 28251881Speter#include <apr_pools.h> 29251881Speter 30251881Speter#include "svn_pools.h" 31251881Speter#include "svn_client.h" 32251881Speter#include "svn_compat.h" 33251881Speter#include "svn_error.h" 34251881Speter#include "svn_dirent_uri.h" 35251881Speter#include "svn_hash.h" 36251881Speter#include "svn_path.h" 37251881Speter#include "svn_sorts.h" 38251881Speter#include "svn_props.h" 39251881Speter 40251881Speter#include "client.h" 41251881Speter 42251881Speter#include "svn_private_config.h" 43251881Speter#include "private/svn_wc_private.h" 44251881Speter 45251881Speter#include <assert.h> 46251881Speter 47251881Speter/*** Getting misc. information ***/ 48251881Speter 49251881Speter/* The baton for use with copyfrom_info_receiver(). */ 50251881Spetertypedef struct copyfrom_info_t 51251881Speter{ 52251881Speter svn_boolean_t is_first; 53251881Speter const char *path; 54251881Speter svn_revnum_t rev; 55251881Speter apr_pool_t *pool; 56251881Speter} copyfrom_info_t; 57251881Speter 58251881Speter/* A location segment callback for obtaining the copy source of 59251881Speter a node at a path and storing it in *BATON (a struct copyfrom_info_t *). 60251881Speter Implements svn_location_segment_receiver_t. */ 61251881Speterstatic svn_error_t * 62251881Spetercopyfrom_info_receiver(svn_location_segment_t *segment, 63251881Speter void *baton, 64251881Speter apr_pool_t *pool) 65251881Speter{ 66251881Speter copyfrom_info_t *copyfrom_info = baton; 67251881Speter 68251881Speter /* If we've already identified the copy source, there's nothing more 69251881Speter to do. 70251881Speter ### FIXME: We *should* be able to send */ 71251881Speter if (copyfrom_info->path) 72251881Speter return SVN_NO_ERROR; 73251881Speter 74251881Speter /* If this is the first segment, it's not of interest to us. Otherwise 75251881Speter (so long as this segment doesn't represent a history gap), it holds 76251881Speter our path's previous location (from which it was last copied). */ 77251881Speter if (copyfrom_info->is_first) 78251881Speter { 79251881Speter copyfrom_info->is_first = FALSE; 80251881Speter } 81251881Speter else if (segment->path) 82251881Speter { 83251881Speter /* The end of the second non-gap segment is the location copied from. */ 84251881Speter copyfrom_info->path = apr_pstrdup(copyfrom_info->pool, segment->path); 85251881Speter copyfrom_info->rev = segment->range_end; 86251881Speter 87251881Speter /* ### FIXME: We *should* be able to return SVN_ERR_CEASE_INVOCATION 88251881Speter ### here so we don't get called anymore. */ 89251881Speter } 90251881Speter 91251881Speter return SVN_NO_ERROR; 92251881Speter} 93251881Speter 94251881Spetersvn_error_t * 95251881Spetersvn_client__get_copy_source(const char **original_repos_relpath, 96251881Speter svn_revnum_t *original_revision, 97251881Speter const char *path_or_url, 98251881Speter const svn_opt_revision_t *revision, 99251881Speter svn_client_ctx_t *ctx, 100251881Speter apr_pool_t *result_pool, 101251881Speter apr_pool_t *scratch_pool) 102251881Speter{ 103251881Speter svn_error_t *err; 104251881Speter copyfrom_info_t copyfrom_info = { 0 }; 105251881Speter apr_pool_t *sesspool = svn_pool_create(scratch_pool); 106251881Speter svn_ra_session_t *ra_session; 107251881Speter svn_client__pathrev_t *at_loc; 108251881Speter 109251881Speter copyfrom_info.is_first = TRUE; 110251881Speter copyfrom_info.path = NULL; 111251881Speter copyfrom_info.rev = SVN_INVALID_REVNUM; 112251881Speter copyfrom_info.pool = result_pool; 113251881Speter 114251881Speter SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &at_loc, 115251881Speter path_or_url, NULL, 116251881Speter revision, revision, 117251881Speter ctx, sesspool)); 118251881Speter 119251881Speter /* Find the copy source. Walk the location segments to find the revision 120251881Speter at which this node was created (copied or added). */ 121251881Speter 122251881Speter err = svn_ra_get_location_segments(ra_session, "", at_loc->rev, at_loc->rev, 123251881Speter SVN_INVALID_REVNUM, 124251881Speter copyfrom_info_receiver, ©from_info, 125251881Speter scratch_pool); 126251881Speter 127251881Speter svn_pool_destroy(sesspool); 128251881Speter 129251881Speter if (err) 130251881Speter { 131251881Speter if (err->apr_err == SVN_ERR_FS_NOT_FOUND || 132251881Speter err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED) 133251881Speter { 134251881Speter /* A locally-added but uncommitted versioned resource won't 135251881Speter exist in the repository. */ 136251881Speter svn_error_clear(err); 137251881Speter err = SVN_NO_ERROR; 138251881Speter 139251881Speter *original_repos_relpath = NULL; 140251881Speter *original_revision = SVN_INVALID_REVNUM; 141251881Speter } 142251881Speter return svn_error_trace(err); 143251881Speter } 144251881Speter 145251881Speter *original_repos_relpath = copyfrom_info.path; 146251881Speter *original_revision = copyfrom_info.rev; 147251881Speter return SVN_NO_ERROR; 148251881Speter} 149251881Speter 150251881Speter 151251881Speter/* compatibility with pre-1.5 servers, which send only author/date/log 152251881Speter *revprops in log entries */ 153251881Spetertypedef struct pre_15_receiver_baton_t 154251881Speter{ 155251881Speter svn_client_ctx_t *ctx; 156251881Speter /* ra session for retrieving revprops from old servers */ 157251881Speter svn_ra_session_t *ra_session; 158251881Speter /* caller's list of requested revprops, receiver, and baton */ 159251881Speter const char *ra_session_url; 160251881Speter apr_pool_t *ra_session_pool; 161251881Speter const apr_array_header_t *revprops; 162251881Speter svn_log_entry_receiver_t receiver; 163251881Speter void *baton; 164251881Speter} pre_15_receiver_baton_t; 165251881Speter 166251881Speterstatic svn_error_t * 167251881Speterpre_15_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool) 168251881Speter{ 169251881Speter pre_15_receiver_baton_t *rb = baton; 170251881Speter 171251881Speter if (log_entry->revision == SVN_INVALID_REVNUM) 172251881Speter return rb->receiver(rb->baton, log_entry, pool); 173251881Speter 174251881Speter /* If only some revprops are requested, get them one at a time on the 175251881Speter second ra connection. If all are requested, get them all with 176251881Speter svn_ra_rev_proplist. This avoids getting unrequested revprops (which 177251881Speter may be arbitrarily large), but means one round-trip per requested 178251881Speter revprop. epg isn't entirely sure which should be optimized for. */ 179251881Speter if (rb->revprops) 180251881Speter { 181251881Speter int i; 182251881Speter svn_boolean_t want_author, want_date, want_log; 183251881Speter want_author = want_date = want_log = FALSE; 184251881Speter for (i = 0; i < rb->revprops->nelts; i++) 185251881Speter { 186251881Speter const char *name = APR_ARRAY_IDX(rb->revprops, i, const char *); 187251881Speter svn_string_t *value; 188251881Speter 189251881Speter /* If a standard revprop is requested, we know it is already in 190251881Speter log_entry->revprops if available. */ 191251881Speter if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0) 192251881Speter { 193251881Speter want_author = TRUE; 194251881Speter continue; 195251881Speter } 196251881Speter if (strcmp(name, SVN_PROP_REVISION_DATE) == 0) 197251881Speter { 198251881Speter want_date = TRUE; 199251881Speter continue; 200251881Speter } 201251881Speter if (strcmp(name, SVN_PROP_REVISION_LOG) == 0) 202251881Speter { 203251881Speter want_log = TRUE; 204251881Speter continue; 205251881Speter } 206251881Speter 207251881Speter if (rb->ra_session == NULL) 208251881Speter SVN_ERR(svn_client_open_ra_session2(&rb->ra_session, 209251881Speter rb->ra_session_url, NULL, 210251881Speter rb->ctx, rb->ra_session_pool, 211251881Speter pool)); 212251881Speter 213251881Speter SVN_ERR(svn_ra_rev_prop(rb->ra_session, log_entry->revision, 214251881Speter name, &value, pool)); 215251881Speter if (log_entry->revprops == NULL) 216251881Speter log_entry->revprops = apr_hash_make(pool); 217251881Speter svn_hash_sets(log_entry->revprops, name, value); 218251881Speter } 219251881Speter if (log_entry->revprops) 220251881Speter { 221251881Speter /* Pre-1.5 servers send the standard revprops unconditionally; 222251881Speter clear those the caller doesn't want. */ 223251881Speter if (!want_author) 224251881Speter svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, NULL); 225251881Speter if (!want_date) 226251881Speter svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE, NULL); 227251881Speter if (!want_log) 228251881Speter svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG, NULL); 229251881Speter } 230251881Speter } 231251881Speter else 232251881Speter { 233251881Speter if (rb->ra_session == NULL) 234251881Speter SVN_ERR(svn_client_open_ra_session2(&rb->ra_session, 235251881Speter rb->ra_session_url, NULL, 236251881Speter rb->ctx, rb->ra_session_pool, 237251881Speter pool)); 238251881Speter 239251881Speter SVN_ERR(svn_ra_rev_proplist(rb->ra_session, log_entry->revision, 240251881Speter &log_entry->revprops, pool)); 241251881Speter } 242251881Speter 243251881Speter return rb->receiver(rb->baton, log_entry, pool); 244251881Speter} 245251881Speter 246251881Speter/* limit receiver */ 247251881Spetertypedef struct limit_receiver_baton_t 248251881Speter{ 249251881Speter int limit; 250251881Speter svn_log_entry_receiver_t receiver; 251251881Speter void *baton; 252251881Speter} limit_receiver_baton_t; 253251881Speter 254251881Speterstatic svn_error_t * 255251881Speterlimit_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool) 256251881Speter{ 257251881Speter limit_receiver_baton_t *rb = baton; 258251881Speter 259251881Speter rb->limit--; 260251881Speter 261251881Speter return rb->receiver(rb->baton, log_entry, pool); 262251881Speter} 263251881Speter 264251881Speter/* Resolve the URLs or WC path in TARGETS as per the svn_client_log5 API. 265251881Speter 266251881Speter The limitations on TARGETS specified by svn_client_log5 are enforced here. 267251881Speter So TARGETS can only contain a single WC path or a URL and zero or more 268251881Speter relative paths -- anything else will raise an error. 269251881Speter 270251881Speter PEG_REVISION, TARGETS, and CTX are as per svn_client_log5. 271251881Speter 272251881Speter If TARGETS contains a single WC path then set *RA_TARGET to the absolute 273251881Speter path of that single path if PEG_REVISION is dependent on the working copy 274251881Speter (e.g. PREV). Otherwise set *RA_TARGET to the corresponding URL for the 275251881Speter single WC path. Set *RELATIVE_TARGETS to an array with a single 276251881Speter element "". 277251881Speter 278251881Speter If TARGETS contains only a single URL, then set *RA_TARGET to a copy of 279251881Speter that URL and *RELATIVE_TARGETS to an array with a single element "". 280251881Speter 281251881Speter If TARGETS contains a single URL and one or more relative paths, then 282251881Speter set *RA_TARGET to a copy of that URL and *RELATIVE_TARGETS to a copy of 283251881Speter each relative path after the URL. 284251881Speter 285251881Speter If *PEG_REVISION is svn_opt_revision_unspecified, then *PEG_REVISION is 286251881Speter set to svn_opt_revision_head for URLs or svn_opt_revision_working for a 287251881Speter WC path. 288251881Speter 289251881Speter *RA_TARGET and *RELATIVE_TARGETS are allocated in RESULT_POOL. */ 290251881Speterstatic svn_error_t * 291251881Speterresolve_log_targets(apr_array_header_t **relative_targets, 292251881Speter const char **ra_target, 293251881Speter svn_opt_revision_t *peg_revision, 294251881Speter const apr_array_header_t *targets, 295251881Speter svn_client_ctx_t *ctx, 296251881Speter apr_pool_t *result_pool, 297251881Speter apr_pool_t *scratch_pool) 298251881Speter{ 299251881Speter int i; 300251881Speter svn_boolean_t url_targets; 301251881Speter 302251881Speter /* Per svn_client_log5, TARGETS contains either a URL followed by zero or 303251881Speter more relative paths, or one working copy path. */ 304251881Speter const char *url_or_path = APR_ARRAY_IDX(targets, 0, const char *); 305251881Speter 306251881Speter /* svn_client_log5 requires at least one target. */ 307251881Speter if (targets->nelts == 0) 308251881Speter return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, 309251881Speter _("No valid target found")); 310251881Speter 311251881Speter /* Initialize the output array. At a minimum, we need room for one 312251881Speter (possibly empty) relpath. Otherwise, we have to hold a relpath 313251881Speter for every item in TARGETS except the first. */ 314251881Speter *relative_targets = apr_array_make(result_pool, 315251881Speter MAX(1, targets->nelts - 1), 316251881Speter sizeof(const char *)); 317251881Speter 318251881Speter if (svn_path_is_url(url_or_path)) 319251881Speter { 320251881Speter /* An unspecified PEG_REVISION for a URL path defaults 321251881Speter to svn_opt_revision_head. */ 322251881Speter if (peg_revision->kind == svn_opt_revision_unspecified) 323251881Speter peg_revision->kind = svn_opt_revision_head; 324251881Speter 325251881Speter /* The logic here is this: If we get passed one argument, we assume 326251881Speter it is the full URL to a file/dir we want log info for. If we get 327251881Speter a URL plus some paths, then we assume that the URL is the base, 328251881Speter and that the paths passed are relative to it. */ 329251881Speter if (targets->nelts > 1) 330251881Speter { 331251881Speter /* We have some paths, let's use them. Start after the URL. */ 332251881Speter for (i = 1; i < targets->nelts; i++) 333251881Speter { 334251881Speter const char *target; 335251881Speter 336251881Speter target = APR_ARRAY_IDX(targets, i, const char *); 337251881Speter 338251881Speter if (svn_path_is_url(target) || svn_dirent_is_absolute(target)) 339251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 340251881Speter _("'%s' is not a relative path"), 341251881Speter target); 342251881Speter 343251881Speter APR_ARRAY_PUSH(*relative_targets, const char *) = 344251881Speter apr_pstrdup(result_pool, target); 345251881Speter } 346251881Speter } 347251881Speter else 348251881Speter { 349251881Speter /* If we have a single URL, then the session will be rooted at 350251881Speter it, so just send an empty string for the paths we are 351251881Speter interested in. */ 352251881Speter APR_ARRAY_PUSH(*relative_targets, const char *) = ""; 353251881Speter } 354251881Speter 355251881Speter /* Remember that our targets are URLs. */ 356251881Speter url_targets = TRUE; 357251881Speter } 358251881Speter else /* WC path target. */ 359251881Speter { 360251881Speter const char *target; 361251881Speter const char *target_abspath; 362251881Speter 363251881Speter url_targets = FALSE; 364251881Speter if (targets->nelts > 1) 365251881Speter return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 366251881Speter _("When specifying working copy paths, only " 367251881Speter "one target may be given")); 368251881Speter 369251881Speter /* An unspecified PEG_REVISION for a working copy path defaults 370251881Speter to svn_opt_revision_working. */ 371251881Speter if (peg_revision->kind == svn_opt_revision_unspecified) 372251881Speter peg_revision->kind = svn_opt_revision_working; 373251881Speter 374251881Speter /* Get URLs for each target */ 375251881Speter target = APR_ARRAY_IDX(targets, 0, const char *); 376251881Speter 377251881Speter SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, scratch_pool)); 378251881Speter SVN_ERR(svn_wc__node_get_url(&url_or_path, ctx->wc_ctx, target_abspath, 379251881Speter scratch_pool, scratch_pool)); 380251881Speter APR_ARRAY_PUSH(*relative_targets, const char *) = ""; 381251881Speter } 382251881Speter 383251881Speter /* If this is a revision type that requires access to the working copy, 384251881Speter * we use our initial target path to figure out where to root the RA 385251881Speter * session, otherwise we use our URL. */ 386251881Speter if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)) 387251881Speter { 388251881Speter if (url_targets) 389251881Speter return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, 390251881Speter _("PREV, BASE, or COMMITTED revision " 391251881Speter "keywords are invalid for URL")); 392251881Speter 393251881Speter else 394251881Speter SVN_ERR(svn_dirent_get_absolute( 395251881Speter ra_target, APR_ARRAY_IDX(targets, 0, const char *), result_pool)); 396251881Speter } 397251881Speter else 398251881Speter { 399251881Speter *ra_target = apr_pstrdup(result_pool, url_or_path); 400251881Speter } 401251881Speter 402251881Speter return SVN_NO_ERROR; 403251881Speter} 404251881Speter 405251881Speter/* Keep track of oldest and youngest opt revs found. 406251881Speter 407251881Speter If REV is younger than *YOUNGEST_REV, or *YOUNGEST_REV is 408251881Speter svn_opt_revision_unspecified, then set *YOUNGEST_REV equal to REV. 409251881Speter 410251881Speter If REV is older than *OLDEST_REV, or *OLDEST_REV is 411251881Speter svn_opt_revision_unspecified, then set *OLDEST_REV equal to REV. */ 412251881Speterstatic void 413251881Speterfind_youngest_and_oldest_revs(svn_revnum_t *youngest_rev, 414251881Speter svn_revnum_t *oldest_rev, 415251881Speter svn_revnum_t rev) 416251881Speter{ 417251881Speter /* Is REV younger than YOUNGEST_REV? */ 418251881Speter if (! SVN_IS_VALID_REVNUM(*youngest_rev) 419251881Speter || rev > *youngest_rev) 420251881Speter *youngest_rev = rev; 421251881Speter 422251881Speter if (! SVN_IS_VALID_REVNUM(*oldest_rev) 423251881Speter || rev < *oldest_rev) 424251881Speter *oldest_rev = rev; 425251881Speter} 426251881Speter 427251881Spetertypedef struct rev_range_t 428251881Speter{ 429251881Speter svn_revnum_t range_start; 430251881Speter svn_revnum_t range_end; 431251881Speter} rev_range_t; 432251881Speter 433251881Speter/* Convert array of svn_opt_revision_t ranges to an array of svn_revnum_t 434251881Speter ranges. 435251881Speter 436251881Speter Given a log target URL_OR_ABSPATH@PEG_REV and an array of 437251881Speter svn_opt_revision_range_t's OPT_REV_RANGES, resolve the opt revs in 438251881Speter OPT_REV_RANGES to svn_revnum_t's and return these in *REVISION_RANGES, an 439251881Speter array of rev_range_t *. 440251881Speter 441251881Speter Set *YOUNGEST_REV and *OLDEST_REV to the youngest and oldest revisions 442251881Speter found in *REVISION_RANGES. 443251881Speter 444251881Speter If the repository needs to be contacted to resolve svn_opt_revision_date or 445251881Speter svn_opt_revision_head revisions, then the session used to do this is 446251881Speter RA_SESSION; it must be an open session to any URL in the right repository. 447251881Speter*/ 448251881Speterstatic svn_error_t* 449251881Speterconvert_opt_rev_array_to_rev_range_array( 450251881Speter apr_array_header_t **revision_ranges, 451251881Speter svn_revnum_t *youngest_rev, 452251881Speter svn_revnum_t *oldest_rev, 453251881Speter svn_ra_session_t *ra_session, 454251881Speter const char *url_or_abspath, 455251881Speter const apr_array_header_t *opt_rev_ranges, 456251881Speter const svn_opt_revision_t *peg_rev, 457251881Speter svn_client_ctx_t *ctx, 458251881Speter apr_pool_t *result_pool, 459251881Speter apr_pool_t *scratch_pool) 460251881Speter{ 461251881Speter int i; 462251881Speter svn_revnum_t head_rev = SVN_INVALID_REVNUM; 463251881Speter 464251881Speter /* Initialize the input/output parameters. */ 465251881Speter *youngest_rev = *oldest_rev = SVN_INVALID_REVNUM; 466251881Speter 467251881Speter /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest 468251881Speter and oldest revision range that spans all of OPT_REV_RANGES. */ 469251881Speter *revision_ranges = apr_array_make(result_pool, opt_rev_ranges->nelts, 470251881Speter sizeof(rev_range_t *)); 471251881Speter 472251881Speter for (i = 0; i < opt_rev_ranges->nelts; i++) 473251881Speter { 474251881Speter svn_opt_revision_range_t *range; 475251881Speter rev_range_t *rev_range; 476251881Speter svn_boolean_t start_same_as_end = FALSE; 477251881Speter 478251881Speter range = APR_ARRAY_IDX(opt_rev_ranges, i, svn_opt_revision_range_t *); 479251881Speter 480251881Speter /* Right now RANGE can be any valid pair of svn_opt_revision_t's. We 481251881Speter will now convert all RANGEs in place to the corresponding 482251881Speter svn_opt_revision_number kind. */ 483251881Speter if ((range->start.kind != svn_opt_revision_unspecified) 484251881Speter && (range->end.kind == svn_opt_revision_unspecified)) 485251881Speter { 486251881Speter /* If the user specified exactly one revision, then start rev is 487251881Speter * set but end is not. We show the log message for just that 488251881Speter * revision by making end equal to start. 489251881Speter * 490251881Speter * Note that if the user requested a single dated revision, then 491251881Speter * this will cause the same date to be resolved twice. The 492251881Speter * extra code complexity to get around this slight inefficiency 493251881Speter * doesn't seem worth it, however. */ 494251881Speter range->end = range->start; 495251881Speter } 496251881Speter else if (range->start.kind == svn_opt_revision_unspecified) 497251881Speter { 498251881Speter /* Default to any specified peg revision. Otherwise, if the 499251881Speter * first target is a URL, then we default to HEAD:0. Lastly, 500251881Speter * the default is BASE:0 since WC@HEAD may not exist. */ 501251881Speter if (peg_rev->kind == svn_opt_revision_unspecified) 502251881Speter { 503251881Speter if (svn_path_is_url(url_or_abspath)) 504251881Speter range->start.kind = svn_opt_revision_head; 505251881Speter else 506251881Speter range->start.kind = svn_opt_revision_base; 507251881Speter } 508251881Speter else 509251881Speter range->start = *peg_rev; 510251881Speter 511251881Speter if (range->end.kind == svn_opt_revision_unspecified) 512251881Speter { 513251881Speter range->end.kind = svn_opt_revision_number; 514251881Speter range->end.value.number = 0; 515251881Speter } 516251881Speter } 517251881Speter 518251881Speter if ((range->start.kind == svn_opt_revision_unspecified) 519251881Speter || (range->end.kind == svn_opt_revision_unspecified)) 520251881Speter { 521251881Speter return svn_error_create 522251881Speter (SVN_ERR_CLIENT_BAD_REVISION, NULL, 523251881Speter _("Missing required revision specification")); 524251881Speter } 525251881Speter 526251881Speter /* Does RANGE describe a single svn_opt_revision_t? */ 527251881Speter if (range->start.kind == range->end.kind) 528251881Speter { 529251881Speter if (range->start.kind == svn_opt_revision_number) 530251881Speter { 531251881Speter if (range->start.value.number == range->end.value.number) 532251881Speter start_same_as_end = TRUE; 533251881Speter } 534251881Speter else if (range->start.kind == svn_opt_revision_date) 535251881Speter { 536251881Speter if (range->start.value.date == range->end.value.date) 537251881Speter start_same_as_end = TRUE; 538251881Speter } 539251881Speter else 540251881Speter { 541251881Speter start_same_as_end = TRUE; 542251881Speter } 543251881Speter } 544251881Speter 545251881Speter rev_range = apr_palloc(result_pool, sizeof(*rev_range)); 546251881Speter SVN_ERR(svn_client__get_revision_number( 547251881Speter &rev_range->range_start, &head_rev, 548251881Speter ctx->wc_ctx, url_or_abspath, ra_session, 549251881Speter &range->start, scratch_pool)); 550251881Speter if (start_same_as_end) 551251881Speter rev_range->range_end = rev_range->range_start; 552251881Speter else 553251881Speter SVN_ERR(svn_client__get_revision_number( 554251881Speter &rev_range->range_end, &head_rev, 555251881Speter ctx->wc_ctx, url_or_abspath, ra_session, 556251881Speter &range->end, scratch_pool)); 557251881Speter 558251881Speter /* Possibly update the oldest and youngest revisions requested. */ 559251881Speter find_youngest_and_oldest_revs(youngest_rev, 560251881Speter oldest_rev, 561251881Speter rev_range->range_start); 562251881Speter find_youngest_and_oldest_revs(youngest_rev, 563251881Speter oldest_rev, 564251881Speter rev_range->range_end); 565251881Speter APR_ARRAY_PUSH(*revision_ranges, rev_range_t *) = rev_range; 566251881Speter } 567251881Speter 568251881Speter return SVN_NO_ERROR; 569251881Speter} 570251881Speter 571251881Speterstatic int 572251881Spetercompare_rev_to_segment(const void *key_p, 573251881Speter const void *element_p) 574251881Speter{ 575251881Speter svn_revnum_t rev = 576251881Speter * (svn_revnum_t *)key_p; 577251881Speter const svn_location_segment_t *segment = 578251881Speter *((const svn_location_segment_t * const *) element_p); 579251881Speter 580251881Speter if (rev < segment->range_start) 581251881Speter return -1; 582251881Speter else if (rev > segment->range_end) 583251881Speter return 1; 584251881Speter else 585251881Speter return 0; 586251881Speter} 587251881Speter 588251881Speter/* Run svn_ra_get_log2 for PATHS, one or more paths relative to RA_SESSION's 589251881Speter common parent, for each revision in REVISION_RANGES, an array of 590251881Speter rev_range_t. 591251881Speter 592251881Speter RA_SESSION is an open session pointing to ACTUAL_LOC. 593251881Speter 594251881Speter LOG_SEGMENTS is an array of svn_location_segment_t * items representing the 595251881Speter history of PATHS from the oldest to youngest revisions found in 596251881Speter REVISION_RANGES. 597251881Speter 598251881Speter The TARGETS, LIMIT, DISCOVER_CHANGED_PATHS, STRICT_NODE_HISTORY, 599251881Speter INCLUDE_MERGED_REVISIONS, REVPROPS, REAL_RECEIVER, and REAL_RECEIVER_BATON 600251881Speter parameters are all as per the svn_client_log5 API. */ 601251881Speterstatic svn_error_t * 602251881Speterrun_ra_get_log(apr_array_header_t *revision_ranges, 603251881Speter apr_array_header_t *paths, 604251881Speter apr_array_header_t *log_segments, 605251881Speter svn_client__pathrev_t *actual_loc, 606251881Speter svn_ra_session_t *ra_session, 607251881Speter /* The following are as per svn_client_log5. */ 608251881Speter const apr_array_header_t *targets, 609251881Speter int limit, 610251881Speter svn_boolean_t discover_changed_paths, 611251881Speter svn_boolean_t strict_node_history, 612251881Speter svn_boolean_t include_merged_revisions, 613251881Speter const apr_array_header_t *revprops, 614251881Speter svn_log_entry_receiver_t real_receiver, 615251881Speter void *real_receiver_baton, 616251881Speter svn_client_ctx_t *ctx, 617251881Speter apr_pool_t *scratch_pool) 618251881Speter{ 619251881Speter int i; 620251881Speter pre_15_receiver_baton_t rb = {0}; 621251881Speter apr_pool_t *iterpool; 622251881Speter svn_boolean_t has_log_revprops; 623251881Speter 624251881Speter SVN_ERR(svn_ra_has_capability(ra_session, &has_log_revprops, 625251881Speter SVN_RA_CAPABILITY_LOG_REVPROPS, 626251881Speter scratch_pool)); 627251881Speter 628251881Speter if (!has_log_revprops) 629251881Speter { 630251881Speter /* See above pre-1.5 notes. */ 631251881Speter rb.ctx = ctx; 632251881Speter 633251881Speter /* Create ra session on first use */ 634251881Speter rb.ra_session_pool = scratch_pool; 635251881Speter rb.ra_session_url = actual_loc->url; 636251881Speter } 637251881Speter 638251881Speter /* It's a bit complex to correctly handle the special revision words 639251881Speter * such as "BASE", "COMMITTED", and "PREV". For example, if the 640251881Speter * user runs 641251881Speter * 642251881Speter * $ svn log -rCOMMITTED foo.txt bar.c 643251881Speter * 644251881Speter * which committed rev should be used? The younger of the two? The 645251881Speter * first one? Should we just error? 646251881Speter * 647251881Speter * None of the above, I think. Rather, the committed rev of each 648251881Speter * target in turn should be used. This is what most users would 649251881Speter * expect, and is the most useful interpretation. Of course, this 650251881Speter * goes for the other dynamic (i.e., local) revision words too. 651251881Speter * 652251881Speter * Note that the code to do this is a bit more complex than a simple 653251881Speter * loop, because the user might run 654251881Speter * 655251881Speter * $ svn log -rCOMMITTED:42 foo.txt bar.c 656251881Speter * 657251881Speter * in which case we want to avoid recomputing the static revision on 658251881Speter * every iteration. 659251881Speter * 660251881Speter * ### FIXME: However, we can't yet handle multiple wc targets anyway. 661251881Speter * 662251881Speter * We used to iterate over each target in turn, getting the logs for 663251881Speter * the named range. This led to revisions being printed in strange 664251881Speter * order or being printed more than once. This is issue 1550. 665251881Speter * 666251881Speter * In r851673, jpieper blocked multiple wc targets in svn/log-cmd.c, 667251881Speter * meaning this block not only doesn't work right in that case, but isn't 668251881Speter * even testable that way (svn has no unit test suite; we can only test 669251881Speter * via the svn command). So, that check is now moved into this function 670251881Speter * (see above). 671251881Speter * 672251881Speter * kfogel ponders future enhancements in r844260: 673251881Speter * I think that's okay behavior, since the sense of the command is 674251881Speter * that one wants a particular range of logs for *this* file, then 675251881Speter * another range for *that* file, and so on. But we should 676251881Speter * probably put some sort of separator header between the log 677251881Speter * groups. Of course, libsvn_client can't just print stuff out -- 678251881Speter * it has to take a callback from the client to do that. So we 679251881Speter * need to define that callback interface, then have the command 680251881Speter * line client pass one down here. 681251881Speter * 682251881Speter * epg wonders if the repository could send a unified stream of log 683251881Speter * entries if the paths and revisions were passed down. 684251881Speter */ 685251881Speter iterpool = svn_pool_create(scratch_pool); 686251881Speter for (i = 0; i < revision_ranges->nelts; i++) 687251881Speter { 688251881Speter const char *old_session_url; 689251881Speter const char *path = APR_ARRAY_IDX(targets, 0, const char *); 690251881Speter const char *local_abspath_or_url; 691251881Speter rev_range_t *range; 692251881Speter limit_receiver_baton_t lb; 693251881Speter svn_log_entry_receiver_t passed_receiver; 694251881Speter void *passed_receiver_baton; 695251881Speter const apr_array_header_t *passed_receiver_revprops; 696251881Speter svn_location_segment_t **matching_segment; 697251881Speter svn_revnum_t younger_rev; 698251881Speter 699251881Speter svn_pool_clear(iterpool); 700251881Speter 701251881Speter if (!svn_path_is_url(path)) 702251881Speter SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path, 703251881Speter iterpool)); 704251881Speter else 705251881Speter local_abspath_or_url = path; 706251881Speter 707251881Speter range = APR_ARRAY_IDX(revision_ranges, i, rev_range_t *); 708251881Speter 709251881Speter /* Issue #4355: Account for renames spanning requested 710251881Speter revision ranges. */ 711251881Speter younger_rev = MAX(range->range_start, range->range_end); 712251881Speter matching_segment = bsearch(&younger_rev, log_segments->elts, 713251881Speter log_segments->nelts, log_segments->elt_size, 714251881Speter compare_rev_to_segment); 715253734Speter /* LOG_SEGMENTS is supposed to represent the history of PATHS from 716253734Speter the oldest to youngest revs in REVISION_RANGES. This function's 717253734Speter current sole caller svn_client_log5 *should* be providing 718253734Speter LOG_SEGMENTS that span the oldest to youngest revs in 719253734Speter REVISION_RANGES, even if one or more of the svn_location_segment_t's 720253734Speter returned have NULL path members indicating a gap in the history. So 721253734Speter MATCHING_SEGMENT should never be NULL, but clearly sometimes it is, 722253734Speter see http://svn.haxx.se/dev/archive-2013-06/0522.shtml 723253734Speter So to be safe we handle that case. */ 724253734Speter if (matching_segment == NULL) 725253734Speter continue; 726251881Speter 727251881Speter /* A segment with a NULL path means there is gap in the history. 728251881Speter We'll just proceed and let svn_ra_get_log2 fail with a useful 729251881Speter error...*/ 730251881Speter if ((*matching_segment)->path != NULL) 731251881Speter { 732251881Speter /* ...but if there is history, then we must account for issue 733251881Speter #4355 and make sure our RA session is pointing at the correct 734251881Speter location. */ 735251881Speter const char *segment_url = svn_path_url_add_component2( 736251881Speter actual_loc->repos_root_url, (*matching_segment)->path, 737251881Speter scratch_pool); 738251881Speter SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, 739251881Speter ra_session, 740251881Speter segment_url, 741251881Speter scratch_pool)); 742251881Speter } 743251881Speter 744251881Speter if (has_log_revprops) 745251881Speter { 746251881Speter passed_receiver = real_receiver; 747251881Speter passed_receiver_baton = real_receiver_baton; 748251881Speter passed_receiver_revprops = revprops; 749251881Speter } 750251881Speter else 751251881Speter { 752251881Speter rb.revprops = revprops; 753251881Speter rb.receiver = real_receiver; 754251881Speter rb.baton = real_receiver_baton; 755251881Speter 756251881Speter passed_receiver = pre_15_receiver; 757251881Speter passed_receiver_baton = &rb; 758251881Speter passed_receiver_revprops = svn_compat_log_revprops_in(iterpool); 759251881Speter } 760251881Speter 761251881Speter if (limit && revision_ranges->nelts > 1) 762251881Speter { 763251881Speter lb.limit = limit; 764251881Speter lb.receiver = passed_receiver; 765251881Speter lb.baton = passed_receiver_baton; 766251881Speter 767251881Speter passed_receiver = limit_receiver; 768251881Speter passed_receiver_baton = &lb; 769251881Speter } 770251881Speter 771251881Speter SVN_ERR(svn_ra_get_log2(ra_session, 772251881Speter paths, 773251881Speter range->range_start, 774251881Speter range->range_end, 775251881Speter limit, 776251881Speter discover_changed_paths, 777251881Speter strict_node_history, 778251881Speter include_merged_revisions, 779251881Speter passed_receiver_revprops, 780251881Speter passed_receiver, 781251881Speter passed_receiver_baton, 782251881Speter iterpool)); 783251881Speter 784251881Speter if (limit && revision_ranges->nelts > 1) 785251881Speter { 786251881Speter limit = lb.limit; 787251881Speter if (limit == 0) 788251881Speter { 789251881Speter return SVN_NO_ERROR; 790251881Speter } 791251881Speter } 792251881Speter } 793251881Speter svn_pool_destroy(iterpool); 794251881Speter 795251881Speter return SVN_NO_ERROR; 796251881Speter} 797251881Speter 798251881Speter/*** Public Interface. ***/ 799251881Speter 800251881Spetersvn_error_t * 801251881Spetersvn_client_log5(const apr_array_header_t *targets, 802251881Speter const svn_opt_revision_t *peg_revision, 803251881Speter const apr_array_header_t *opt_rev_ranges, 804251881Speter int limit, 805251881Speter svn_boolean_t discover_changed_paths, 806251881Speter svn_boolean_t strict_node_history, 807251881Speter svn_boolean_t include_merged_revisions, 808251881Speter const apr_array_header_t *revprops, 809251881Speter svn_log_entry_receiver_t real_receiver, 810251881Speter void *real_receiver_baton, 811251881Speter svn_client_ctx_t *ctx, 812251881Speter apr_pool_t *pool) 813251881Speter{ 814251881Speter svn_ra_session_t *ra_session; 815251881Speter const char *old_session_url; 816251881Speter const char *ra_target; 817251881Speter svn_opt_revision_t youngest_opt_rev; 818251881Speter svn_revnum_t youngest_rev; 819251881Speter svn_revnum_t oldest_rev; 820251881Speter svn_opt_revision_t peg_rev; 821251881Speter svn_client__pathrev_t *actual_loc; 822251881Speter apr_array_header_t *log_segments; 823251881Speter apr_array_header_t *revision_ranges; 824251881Speter apr_array_header_t *relative_targets; 825251881Speter 826251881Speter if (opt_rev_ranges->nelts == 0) 827251881Speter { 828251881Speter return svn_error_create 829251881Speter (SVN_ERR_CLIENT_BAD_REVISION, NULL, 830251881Speter _("Missing required revision specification")); 831251881Speter } 832251881Speter 833251881Speter /* Make a copy of PEG_REVISION, we may need to change it to a 834251881Speter default value. */ 835251881Speter peg_rev = *peg_revision; 836251881Speter 837251881Speter SVN_ERR(resolve_log_targets(&relative_targets, &ra_target, &peg_rev, 838251881Speter targets, ctx, pool, pool)); 839251881Speter 840251881Speter SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &actual_loc, 841251881Speter ra_target, NULL, &peg_rev, &peg_rev, 842251881Speter ctx, pool)); 843251881Speter 844251881Speter /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest 845251881Speter and oldest revision range that spans all of OPT_REV_RANGES. */ 846251881Speter SVN_ERR(convert_opt_rev_array_to_rev_range_array(&revision_ranges, 847251881Speter &youngest_rev, 848251881Speter &oldest_rev, 849251881Speter ra_session, 850251881Speter ra_target, 851251881Speter opt_rev_ranges, &peg_rev, 852251881Speter ctx, pool, pool)); 853251881Speter 854251881Speter /* Make ACTUAL_LOC and RA_SESSION point to the youngest operative rev. */ 855251881Speter youngest_opt_rev.kind = svn_opt_revision_number; 856251881Speter youngest_opt_rev.value.number = youngest_rev; 857251881Speter SVN_ERR(svn_client__resolve_rev_and_url(&actual_loc, ra_session, 858251881Speter ra_target, &peg_rev, 859251881Speter &youngest_opt_rev, ctx, pool)); 860251881Speter SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session, 861251881Speter actual_loc->url, pool)); 862251881Speter 863253734Speter /* Save us an RA layer round trip if we are on the repository root and 864253734Speter know the result in advance. All the revision data has already been 865253734Speter validated. 866253734Speter */ 867253734Speter if (strcmp(actual_loc->url, actual_loc->repos_root_url) == 0) 868253734Speter { 869253734Speter svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment)); 870253734Speter log_segments = apr_array_make(pool, 1, sizeof(segment)); 871251881Speter 872253734Speter segment->range_start = oldest_rev; 873253734Speter segment->range_end = actual_loc->rev; 874253734Speter segment->path = ""; 875253734Speter APR_ARRAY_PUSH(log_segments, svn_location_segment_t *) = segment; 876253734Speter } 877253734Speter else 878253734Speter { 879253734Speter /* Get the svn_location_segment_t's representing the requested log 880253734Speter * ranges. */ 881253734Speter SVN_ERR(svn_client__repos_location_segments(&log_segments, ra_session, 882253734Speter actual_loc->url, 883253734Speter actual_loc->rev, /* peg */ 884253734Speter actual_loc->rev, /* start */ 885253734Speter oldest_rev, /* end */ 886253734Speter ctx, pool)); 887253734Speter } 888253734Speter 889253734Speter 890251881Speter SVN_ERR(run_ra_get_log(revision_ranges, relative_targets, log_segments, 891251881Speter actual_loc, ra_session, targets, limit, 892251881Speter discover_changed_paths, strict_node_history, 893251881Speter include_merged_revisions, revprops, real_receiver, 894251881Speter real_receiver_baton, ctx, pool)); 895251881Speter 896251881Speter return SVN_NO_ERROR; 897251881Speter} 898