Index: subversion/libsvn_ra/wrapper_template.h =================================================================== --- subversion/libsvn_ra/wrapper_template.h (revision 13154) +++ subversion/libsvn_ra/wrapper_template.h (working copy) @@ -258,6 +258,18 @@ handler_baton, pool); } +static svn_error_t *compat_get_file_revs_reverse (void *session_baton, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_ra_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + return VTBL.get_file_revs_reverse (session_baton, path, start, end, handler, + handler_baton, pool); +} + static const svn_version_t *compat_get_version (void) { return VTBL.get_version (); @@ -285,7 +297,8 @@ compat_get_repos_root, compat_get_locations, compat_get_file_revs, - compat_get_version + compat_get_version, + compat_get_file_revs_reverse }; svn_error_t * Index: subversion/libsvn_ra/ra_loader.c =================================================================== --- subversion/libsvn_ra/ra_loader.c (revision 13154) +++ subversion/libsvn_ra/ra_loader.c (working copy) @@ -472,12 +472,18 @@ const char *path, svn_revnum_t start, svn_revnum_t end, + svn_boolean_t reverse, svn_ra_file_rev_handler_t handler, void *handler_baton, apr_pool_t *pool) { - return session->vtable->get_file_revs (session, path, start, end, handler, - handler_baton, pool); + if (reverse) + return session->vtable->get_file_revs_reverse (session, path, start, end, + handler, + handler_baton, pool); + else + return session->vtable->get_file_revs (session, path, start, end, handler, + handler_baton, pool); } Index: subversion/libsvn_ra/ra_loader.h =================================================================== --- subversion/libsvn_ra/ra_loader.h (revision 13154) +++ subversion/libsvn_ra/ra_loader.h (working copy) @@ -170,6 +170,14 @@ svn_ra_file_rev_handler_t handler, void *handler_baton, apr_pool_t *pool); + svn_error_t *(*get_file_revs_reverse) (svn_ra_session_t *session, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_ra_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *pool); + } svn_ra__vtable_t; /* The RA session object. */ Index: subversion/include/svn_error_codes.h =================================================================== --- subversion/include/svn_error_codes.h (revision 13154) +++ subversion/include/svn_error_codes.h (working copy) @@ -792,6 +792,10 @@ SVN_ERR_CLIENT_CATEGORY_START + 12, "Two versioned resources are unrelated") + /* @since New in 1.2. */ + SVN_ERRDEF (SVN_ERR_CLIENT_DONE_WITH_BLAME, + SVN_ERR_CLIENT_CATEGORY_START + 13, + "Client finished blame early") /* misc errors */ Index: subversion/include/svn_repos.h =================================================================== --- subversion/include/svn_repos.h (revision 13154) +++ subversion/include/svn_repos.h (working copy) @@ -817,6 +817,18 @@ svn_repos_file_rev_handler_t handler, void *handler_baton, apr_pool_t *pool); +/** Same as svn_repos_get_file_revs, except return the revisions in order from + * youngest to oldest. */ +svn_error_t *svn_repos_get_file_revs_reverse (svn_repos_t *repos, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + svn_repos_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *pool); + /* ---------------------------------------------------------------*/ Index: subversion/include/svn_ra.h =================================================================== --- subversion/include/svn_ra.h (revision 13154) +++ subversion/include/svn_ra.h (working copy) @@ -822,6 +822,7 @@ const char *path, svn_revnum_t start, svn_revnum_t end, + svn_boolean_t reverse, svn_ra_file_rev_handler_t handler, void *handler_baton, apr_pool_t *pool); @@ -1049,7 +1050,8 @@ * @since New in 1.1. * * Call @c svn_ra_get_file_revs with the session associated with - * @a session_baton and all other arguments. + * @a session_baton and all other arguments, and the reverse argument + * set to false. */ svn_error_t *(*get_file_revs) (void *session_baton, const char *path, @@ -1058,7 +1060,6 @@ svn_ra_file_rev_handler_t handler, void *handler_baton, apr_pool_t *pool); - /** Call @c svn_ra_get_dated_revision with the session associated with * @a session_baton and all other arguments. */ @@ -1068,6 +1069,22 @@ * Return the plugin's version information. */ const svn_version_t *(*get_version) (void); + + /** + * @since New in 1.2 + * + * Call @c svn_ra_get_file_revs with the session associated with + * @a session_baton and all other arguments, and the reverse argument + * set to true. + */ + svn_error_t *(*get_file_revs_reverse) (void *session_baton, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_ra_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *pool); + } svn_ra_plugin_t; /** Index: subversion/libsvn_ra_local/ra_plugin.c =================================================================== --- subversion/libsvn_ra_local/ra_plugin.c (revision 13154) +++ subversion/libsvn_ra_local/ra_plugin.c (working copy) @@ -153,7 +153,26 @@ return svn_repos_get_file_revs (sbaton->repos, abs_path, start, end, NULL, NULL, handler, handler_baton, pool); } +static svn_error_t * +svn_ra_local__get_file_revs_reverse (svn_ra_session_t *session, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_ra_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + svn_ra_local__session_baton_t *sbaton = session->priv; + const char *abs_path = sbaton->fs_path; + /* Concatenate paths */ + abs_path = svn_path_join (abs_path, path, pool); + + return svn_repos_get_file_revs_reverse (sbaton->repos, abs_path, start, end, + NULL, NULL, handler, handler_baton, + pool); +} + static svn_error_t * get_username (svn_ra_session_t *session, apr_pool_t *pool) @@ -961,6 +980,7 @@ svn_ra_local__get_repos_root, svn_ra_local__get_locations, svn_ra_local__get_file_revs, + svn_ra_local__get_file_revs_reverse, }; Index: subversion/libsvn_client/client.h =================================================================== --- subversion/libsvn_client/client.h (revision 13154) +++ subversion/libsvn_client/client.h (working copy) @@ -618,6 +618,17 @@ svn_boolean_t no_ignore, svn_client_ctx_t *ctx, apr_pool_t *pool); +/* Perform a blame operation from the youngest revision to the newest one. */ + +svn_error_t * +svn_client__reverse_blame (const char *target, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *start, + const svn_opt_revision_t *end, + svn_client_blame_receiver_t receiver, + void *receiver_baton, + svn_client_ctx_t *ctx, + apr_pool_t *pool); #ifdef __cplusplus Index: subversion/libsvn_client/blame-new.c =================================================================== --- subversion/libsvn_client/blame-new.c (revision 0) +++ subversion/libsvn_client/blame-new.c (revision 0) @@ -0,0 +1,861 @@ +/* + * blame.c: return blame messages + * + * ==================================================================== + * Copyright (c) 2000-2004 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ +#include +#include +#include +#include + +#include "client.h" + +#include "svn_client.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_diff.h" +#include "svn_io.h" +#include "svn_pools.h" +#include "svn_path.h" +#include "svn_props.h" + +#include "svn_private_config.h" + +#include + +/* The metadata associated with a particular revision. */ +struct rev +{ + svn_revnum_t revision; /* the revision number */ + const char *author; /* the author of the revision */ + const char *date; /* the date of the revision */ + /* Only used by the pre-1.1 code. */ + const char *path; /* the absolute repository path */ + struct rev *next; /* the next revision */ +}; + + +/* The baton used by the txdelta window handler. */ +struct delta_baton { + /* Our underlying handler/baton that we wrap */ + svn_txdelta_window_handler_t wrapped_handler; + void *wrapped_baton; + struct file_rev_baton *file_rev_baton; + apr_file_t *source_file; /* the delta source */ + apr_file_t *file; /* the result of the delta */ + const char *filename; +}; + +/* Remove a temporary file F, which is of type apr_file_t *. First, try + to close the file, ignoring any errors. Return an error if the remove + fails. */ +static apr_status_t +cleanup_tempfile (void *f) +{ + apr_file_t *file = f; + apr_status_t apr_err; + const char *fname; + + /* the file may or may not have been closed; try it */ + apr_file_close (file); + + apr_err = apr_file_name_get (&fname, file); + if (apr_err == APR_SUCCESS) + apr_err = apr_file_remove (fname, apr_file_pool_get (file)); + + return apr_err; +} + +/* Note that these structures can use up a lot of memory if we don't do something (refcounting, or something) + the lines so we delete them when they aren't used by any version anymore, + We could also not copy the text like we do now, and share it + with whatever handed it to us. +*/ + +/* These were the structures and functions described to me by someone who + knows cvs. + I was told they do backwards blame as follows: + + CVS creates two linevectors, one called "headlines" and one called + "curlines". + curlines is initialized to the text of the most recent revision by calling + linevector_add on the text, and then linevector_copy'ing it ot headlines. + + Each reverse diff is then applied, and curlines is updated. Because + curlines and headlines share struct line *'s, as the curlines vector is + updated, all the lines that are really in headlines are also automatically + updated. At the end, headlines now contains properly blamed lines. */ +/* One line in a line vector. */ + + +struct line +{ + char *text; + size_t len; + struct rev *version; + +}; + +/* The entire linevector. */ +struct linevector +{ + unsigned int nlines; + struct line **vector; +}; + +/* Define DOUBLECHECK if you want to verify that linevector properly reflects + the state of the output file after each diff is applied. Very useful for + debugging. */ + +#undef DOUBLECHECK +/* The baton used for a file revision. Also used for the diff output + routine. */ +struct file_rev_baton { + svn_revnum_t start_rev, end_rev; + const char *target; + svn_client_ctx_t *ctx; + size_t count; + const char *first_filename; + const char *last_filename; + struct linevector curlines; + struct linevector headlines; +#ifdef DOUBLECHECK + char **linebuffer; +#endif + struct rev *rev; /* The rev for which blame will be assigned */ + struct rev *nextrev; /* the rev for which blame will be next assigned */ + apr_pool_t *mainpool; /* lives during the whole sequence of calls */ + apr_pool_t *lastpool; /* pool used during previous call */ + apr_pool_t *currpool; /* pool used during this call */ +}; + + +/* Initialize *VEC to be a linevector with no lines. */ +static void +linevector_init (struct linevector *vec) +{ + vec->nlines = 0; + vec->vector = NULL; +} + +/* Count the number of new lines that will be added to a linevector if we + insert TEXT into it. */ + +static int +count_new_lines (const char *text, size_t len) +{ + /* There is always at least one new line in the text, which is the line that + starts *at* the beginning of text. */ + int num = 1; + int i; + /* We want every newline but the newline at the very end of the text. */ + for (i = 0; i < len; i++) + if (*(text + i) == '\n' && (i + 1) < len) + num++; + return num; +} + +/* Ensure that our line vector contains enough space for LINES number of + lines. Of course, we could just allocate more lines than we need and + double it every time we ran out or something, but this is a first pass + implementation. */ + +static struct linevector * +ensure_vector_size (struct linevector *vec, size_t lines) +{ + if (vec->nlines < lines) + { + vec->vector = realloc (vec->vector, + lines * sizeof (struct line)); + } + return vec; +} + +/* Add each line of TEXT (of length LEN) to VEC at position POS. + Each line gets VERSION as it's associated version. */ + +static int +linevector_add (struct linevector *vec, const char *text, size_t len, + size_t pos, struct rev *version) +{ + const char *textend; + unsigned int i; + unsigned int new_lines; + const char *p; + const char *nextline_text; + size_t nextline_len; + int nextline_newline; + struct line *line; + + if (len == 0) + return 1; + + textend = text + len; + + new_lines = count_new_lines (text, len); + + vec = ensure_vector_size (vec, vec->nlines + new_lines); + + /* Move the part of the line vector we are adding into out of the way. */ + for (i = vec->nlines + new_lines - 1; i >= pos + new_lines; --i) + vec->vector[i] = vec->vector[i - new_lines]; + + if (pos > vec->nlines) + return 0; + + /* Actually add the lines, to VEC->VECTOR. */ + i = pos; + + nextline_text = text; + for (p = text; p < textend; ++p) + if (*p == '\n') + { + /* No need to add another line for the final newline. */ + if (p + 1 == textend) + break; + nextline_len = p - nextline_text; + line = malloc (sizeof (struct line)); + line->version = version; + line->text = calloc (nextline_len + 1, 1); + line->len = nextline_len; + vec->vector[i++] = line; + memcpy (line->text, nextline_text, nextline_len); + nextline_text = p + 1; + } + /* Add the final line. */ + nextline_len = p - nextline_text; + line = malloc (sizeof (struct line)); + line->version = version; + line->text = calloc (nextline_len + 1, 1); + line->len = nextline_len; + vec->vector[i] = line; + memcpy (line->text, nextline_text, nextline_len); + vec->nlines += new_lines; + + return 1; +} + +/* Remove NLINES lines from VEC at position POS (where line 0 is the + first line). */ + +static void +linevector_delete (struct linevector *vec, unsigned int pos, + unsigned int nlines) +{ + unsigned int i; + + for (i = pos; i < (vec->nlines - nlines); ++i) + vec->vector[i] = vec->vector[i + nlines]; + vec->nlines -= nlines; +} + + +/* Copy FROM to TO, copying the vectors but not the lines pointed to. */ + +static void +linevector_copy (struct linevector *to, struct linevector *from) +{ + to = ensure_vector_size (to, from->nlines); + + memcpy (to->vector, from->vector, + from->nlines * sizeof (*to->vector)); + to->nlines = from->nlines; +} + + + +/* Used only by the old code. */ +/* The baton used for RA->get_log */ +struct log_message_baton { + const char *path; /* The path to be processed */ + struct rev *eldest; /* The eldest revision processed */ + char action; /* The action associated with the eldest */ + svn_revnum_t copyrev; /* The revision the eldest was copied from */ + svn_cancel_func_t cancel_func; /* cancellation callback */ + void *cancel_baton; /* cancellation baton */ + apr_pool_t *pool; +}; + + +/* Callback for log messages: accumulates revision metadata into + a chronologically ordered list stored in the baton. */ +static svn_error_t * +log_message_receiver (void *baton, + apr_hash_t *changed_paths, + svn_revnum_t revision, + const char *author, + const char *date, + const char *message, + apr_pool_t *pool) +{ + struct log_message_baton *lmb = baton; + struct rev *rev; + + if (lmb->cancel_func) + SVN_ERR (lmb->cancel_func (lmb->cancel_baton)); + + rev = apr_palloc (lmb->pool, sizeof (*rev)); + rev->revision = revision; + rev->author = apr_pstrdup (lmb->pool, author); + rev->date = apr_pstrdup (lmb->pool, date); + rev->path = lmb->path; + rev->next = lmb->eldest; + lmb->eldest = rev; + + SVN_ERR (svn_client__prev_log_path (&lmb->path, &lmb->action, + &lmb->copyrev, changed_paths, + lmb->path, svn_node_file, revision, + lmb->pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +read_file_into_linevector (struct file_rev_baton *frb, const char *filename, + struct rev *version) +{ + apr_file_t *file; + apr_finfo_t finfo; + apr_status_t rv; + char *buffer; + + /* Initialize */ + SVN_ERR(svn_io_file_open(&file, filename, APR_READ, APR_OS_DEFAULT, + frb->currpool)); + SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE, file, frb->currpool)); + linevector_init (&frb->curlines); + linevector_init (&frb->headlines); + buffer = apr_palloc (frb->mainpool, finfo.size); + SVN_ERR(svn_io_file_read_full(file, buffer, finfo.size, NULL, frb->currpool)); + + /* The last null here should really be the oldest revision we asked about */ + linevector_add (&frb->curlines, buffer, finfo.size, 0, NULL); + linevector_copy (&frb->headlines, &frb->curlines); + return NULL; +} + +struct deltafrag { + enum { + FRAG_ADD, + FRAG_DELETE + } type; + unsigned long pos; + unsigned long nlines; + const char *new_lines; + size_t len; + struct deltafrag *next; +}; + + +/* Code to apply rcs style diffs to a generic linebuffer structure. + Found on the internet for some program trying to do more or less what we + are trying to do. + This code should not be part of any submission to subversion, as we do not + know it's real origin. + + The only important part to remember when replacing this is to set the version on the deleted + lines before deleting them from the linevector. Lines *added* in earlier + versions make no difference to the output of annotate. */ +static int +apply_rcs_changes (struct linevector *lines, const char *diffbuf, + size_t difflen, struct rev *version, apr_pool_t *pool) +{ + const char *name = "generated rcs diff file"; + const char *p; + const char *q; + apr_pool_t *childpool = svn_pool_create (pool); + int op; + + /* The RCS format throws us for a loop in that the deltafrags (if + we define a deltafrag as an add or a delete) need to be applied + in reverse order. So we stick them into a linked list. */ + struct deltafrag *dfhead; + struct deltafrag *df; + + dfhead = NULL; + for (p = diffbuf; p != NULL && p < diffbuf + difflen; ) + { + op = *p++; + if (op != 'a' && op != 'd') + /* Can't just skip over the deltafrag, because the value + of op determines the syntax. */ + error (1, 0, "unrecognized operation '\\x%x' in %s", + op, name); + df = apr_palloc (childpool, sizeof (struct deltafrag)); + df->next = dfhead; + dfhead = df; + df->pos = strtoul (p, (char **) &q, 10); + + if (p == q) + error (1, 0, "number expected in %s", name); + p = q; + if (*p++ != ' ') + error (1, 0, "space expected in %s", name); + df->nlines = strtoul (p, (char **) &q, 10); + if (p == q) + error (1, 0, "number expected in %s", name); + p = q; + if (*p++ != '\012') + error (1, 0, "linefeed expected in %s", name); + + if (op == 'a') + { + unsigned int i; + + df->type = FRAG_ADD; + i = df->nlines; + /* The text we want is the number of lines specified, or + until the end of the value, whichever comes first (it + will be the former except in the case where we are + adding a line which does not end in newline). */ + for (q = p; i != 0; ++q) + if (*q == '\n') + --i; + else if (q == diffbuf + difflen) + { + if (i != 1) + error (1, 0, "premature end of change in %s", name); + else + break; + } + + /* Stash away a pointer to the text we are adding. */ + df->new_lines = p; + df->len = q - p; + + p = q; + } + else + { + /* Correct for the fact that line numbers in RCS files + start with 1. */ + --df->pos; + + assert (op == 'd'); + df->type = FRAG_DELETE; + } + } + + for (df = dfhead; df != NULL; df = df->next) + { + unsigned int ln; + + switch (df->type) + { + case FRAG_ADD: + if (! linevector_add (lines, df->new_lines, df->len, + df->pos, NULL)) + return 0; + break; + case FRAG_DELETE: + if (df->pos > lines->nlines + || df->pos + df->nlines > lines->nlines) + return 0; + if (version != NULL) + for (ln = df->pos; ln < df->pos + df->nlines; ++ln) + lines->vector[ln]->version = version; + linevector_delete (lines, df->pos, df->nlines); + break; + } + } + svn_pool_destroy (childpool); + + return 1; +} + +/* Add the blame for the diffs between LAST_FILE and CUR_FILE with the rev + specified in FRB. LAST_FILE may be NULL in which + case blame is added for every line of CUR_FILE. */ +static svn_error_t * +add_file_blame (const char *last_file, const char *cur_file, + struct file_rev_baton *frb) +{ + if (!last_file) + { + read_file_into_linevector (frb, cur_file, frb->nextrev); + } + else + { + apr_file_t *file; + apr_finfo_t finfo; + svn_stream_t *stream; + char *tempbuffer; + svn_stringbuf_t *sb; + svn_boolean_t eof = FALSE; + size_t i; + apr_proc_t proc; + apr_procattr_t *procattr; + const char *diffargs[5]; + + SVN_ERR(svn_io_file_open(&file, cur_file, APR_READ, APR_OS_DEFAULT, + frb->currpool)); + SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE, file, + frb->currpool)); + +#ifdef DOUBLECHECK + if (finfo.size) + { + frb->linebuffer = apr_palloc (frb->currpool, + finfo.size * sizeof (char *)); + stream = svn_stream_from_aprfile (file, frb->currpool); + i = 0; + while (!eof) + { + sb = NULL; + SVN_ERR (svn_stream_readline (stream, &sb, "\n", &eof, + frb->currpool)); + frb->linebuffer[i++] = sb->data; + } + svn_stream_close (stream); + } +#endif + apr_procattr_create (&procattr, frb->currpool); + apr_procattr_io_set (procattr, 0, 1, 0); + diffargs[0] = "/usr/bin/diff"; + diffargs[1] = "-n"; + diffargs[2] = last_file; + diffargs[3] = cur_file; + diffargs[4] = NULL; + apr_proc_create (&proc, "/usr/bin/diff", diffargs, NULL, procattr, + frb->currpool); + tempbuffer = apr_pcalloc (frb->currpool, (finfo.size == 0 ? 1024 : finfo.size) * 2); + apr_file_read_full (proc.out, tempbuffer, (finfo.size == 0 ? 1024 : finfo.size) * 2, NULL); + + /* Child should have died because of the read above. */ + apr_proc_wait (&proc, NULL, NULL, APR_WAIT); + + apply_rcs_changes (&frb->curlines, tempbuffer, strlen(tempbuffer), + frb->rev, frb->currpool); +#ifdef DOUBLECHECK + if (finfo.size) + { + for (i = 0; i < frb->curlines.nlines; i++) + if (strcmp (frb->curlines.vector[i]->text, frb->linebuffer[i]) != 0) + abort (); + } +#endif + apr_file_close (file); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +window_handler (svn_txdelta_window_t *window, void *baton) +{ + struct delta_baton *dbaton = baton; + struct file_rev_baton *frb = dbaton->file_rev_baton; + + /* Call the wrapped handler first. */ + SVN_ERR (dbaton->wrapped_handler (window, dbaton->wrapped_baton)); + + /* We patiently wait for the NULL window marking the end. */ + if (window) + return SVN_NO_ERROR; + + /* Close the files used for the delta. + It is important to do this early, since otherwise, they will be deleted + before all handles are closed, which leads to failures on some platforms + when new tempfiles are to be created. */ + if (dbaton->source_file) + SVN_ERR (svn_io_file_close (dbaton->source_file, frb->currpool)); + SVN_ERR (svn_io_file_close (dbaton->file, frb->currpool)); + + /* Process this file. */ + SVN_ERR (add_file_blame (frb->last_filename, + dbaton->filename, frb)); + + /* Prepare for next revision. */ + if (!frb->last_filename) + frb->first_filename = dbaton->filename; + /* Remember the file name so we can diff it with the next revision. */ + frb->last_filename = dbaton->filename; + + /* Switch pools. */ + { + apr_pool_t *tmp_pool = frb->lastpool; + frb->lastpool = frb->currpool; + frb->currpool = tmp_pool; + } + + return SVN_NO_ERROR; +} + +/* Throw an SVN_ERR_CLIENT_IS_BINARY_FILE error if PROP_DIFFS indicates a + binary MIME type. Else, return SVN_NO_ERROR. */ +static svn_error_t * +check_mimetype (apr_array_header_t *prop_diffs, const char *target, + apr_pool_t *pool) +{ + int i; + + for (i = 0; i < prop_diffs->nelts; ++i) + { + const svn_prop_t *prop = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t); + if (strcmp (prop->name, SVN_PROP_MIME_TYPE) == 0 + && prop->value + && svn_mime_type_is_binary (prop->value->data)) + return svn_error_createf + (SVN_ERR_CLIENT_IS_BINARY_FILE, 0, + _("Cannot calculate blame information for binary file '%s'"), + svn_path_local_style (target, pool)); + } + return SVN_NO_ERROR; +} + +static svn_error_t * +file_rev_handler (void *baton, const char *path, svn_revnum_t revnum, + apr_hash_t *rev_props, + svn_txdelta_window_handler_t *content_delta_handler, + void **content_delta_baton, + apr_array_header_t *prop_diffs, + apr_pool_t *pool) +{ + struct file_rev_baton *frb = baton; + svn_stream_t *last_stream; + svn_stream_t *cur_stream; + const char *temp_dir; + struct delta_baton *delta_baton; + svn_boolean_t notversioned = FALSE; + + frb->count++; + /* Clear the current pool. */ + svn_pool_clear (frb->currpool); + fprintf (stderr, "Handling revision %ld\n", revnum); + /* If this file has a non-textual mime-type, bail out. */ + SVN_ERR (check_mimetype (prop_diffs, frb->target, frb->currpool)); + + if (frb->ctx->notify_func) + frb->ctx->notify_func (frb->ctx->notify_baton, + path, + svn_wc_notify_blame_revision, + svn_node_none, + NULL, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable, + revnum); + + if (frb->ctx->cancel_func) + SVN_ERR (frb->ctx->cancel_func (frb->ctx->cancel_baton)); + + /* If there were no content changes, we couldn't care less about this + revision now. Note that we checked the mime type above, so things + work if the user just changes the mime type in a commit. + Also note that we don't switch the pools in this case. This is important, + since the tempfile will be removed by the pool and we need the tempfile + from the last revision with content changes. */ + if (!content_delta_handler) + return SVN_NO_ERROR; + + /* Create delta baton. */ + delta_baton = apr_palloc (frb->currpool, sizeof (*delta_baton)); + + /* Prepare the text delta window handler. */ + if (frb->last_filename) + SVN_ERR (svn_io_file_open (&delta_baton->source_file, frb->last_filename, + APR_READ, APR_OS_DEFAULT, frb->currpool)); + else + /* Means empty stream below. */ + delta_baton->source_file = NULL; + last_stream = svn_stream_from_aprfile (delta_baton->source_file, pool); + + SVN_ERR (svn_io_temp_dir (&temp_dir, frb->currpool)); + SVN_ERR (svn_io_open_unique_file (&delta_baton->file, &delta_baton->filename, + svn_path_join (temp_dir, "tmp", + frb->currpool), + ".tmp", FALSE, frb->currpool)); + apr_pool_cleanup_register (frb->currpool, delta_baton->file, + cleanup_tempfile, apr_pool_cleanup_null); + cur_stream = svn_stream_from_aprfile (delta_baton->file, frb->currpool); + + /* Get window handler for applying delta. */ + svn_txdelta_apply (last_stream, cur_stream, NULL, NULL, + frb->currpool, + &delta_baton->wrapped_handler, + &delta_baton->wrapped_baton); + + /* Wrap the window handler with our own. */ + delta_baton->file_rev_baton = frb; + *content_delta_handler = window_handler; + *content_delta_baton = delta_baton; + + if ((frb->count % 100) == 0) + { + size_t i; + for (i = 0; i < frb->headlines.nlines; i++) + if (frb->headlines.vector[i]->version == NULL) + { + notversioned = TRUE; + break; + } + if (!notversioned) + return svn_error_create (SVN_ERR_CLIENT_DONE_WITH_BLAME, NULL, NULL); + } + + /* At this point, frb->nextrev is the revision before the current one, which + is what we want to assign blame when we are diffing in the backwards + direction. */ + frb->rev = frb->nextrev; + + /* Create the rev structure. */ + frb->nextrev = apr_palloc (frb->mainpool, sizeof (struct rev)); + + if (revnum < frb->start_rev) + { + /* We shouldn't get more than one revision before start. */ + assert (frb->last_filename == NULL); + + /* The file existed before start_rev; generate no blame info for + lines from this revision (or before). */ + frb->nextrev->revision = SVN_INVALID_REVNUM; + frb->nextrev->author = NULL; + frb->nextrev->date = NULL; + } + else + { + svn_string_t *str; + assert (revnum <= frb->end_rev); + + /* Set values from revision props. */ + frb->nextrev->revision = revnum; + + if ((str = apr_hash_get (rev_props, SVN_PROP_REVISION_AUTHOR, + sizeof (SVN_PROP_REVISION_AUTHOR) - 1))) + frb->nextrev->author = apr_pstrdup (frb->mainpool, str->data); + else + frb->nextrev->author = NULL; + + if ((str = apr_hash_get (rev_props, SVN_PROP_REVISION_DATE, + sizeof (SVN_PROP_REVISION_DATE) - 1))) + frb->nextrev->date = apr_pstrdup (frb->mainpool, str->data); + else + frb->nextrev->date = NULL; + } + + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_client__reverse_blame (const char *target, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *start, + const svn_opt_revision_t *end, + svn_client_blame_receiver_t receiver, + void *receiver_baton, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + struct file_rev_baton frb; + svn_ra_session_t *ra_session; + const char *url; + svn_revnum_t start_revnum, end_revnum; + struct blame *walk; + apr_file_t *file; + apr_pool_t *iterpool; + svn_stream_t *stream; + svn_error_t *err; + size_t i; + if (start->kind == svn_opt_revision_unspecified + || end->kind == svn_opt_revision_unspecified) + return svn_error_create + (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); + + /* Get an RA plugin for this filesystem object. */ + SVN_ERR (svn_client__ra_session_from_path (&ra_session, &end_revnum, + &url, target, peg_revision, end, + ctx, pool)); + + SVN_ERR (svn_client__get_revision_number (&start_revnum, ra_session, + start, target, pool)); + + if (end_revnum < start_revnum) + return svn_error_create + (SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("Start revision must precede end revision")); + + frb.count = 0; + frb.start_rev = start_revnum; + frb.end_rev = end_revnum; + frb.target = target; + frb.ctx = ctx; + frb.last_filename = NULL; + frb.first_filename = NULL; + frb.mainpool = pool; + /* The callback will flip the following two pools, because it needs + information from the previous call. Obviously, it can't rely on + the lifetime of the pool provided by get_file_revs. */ + frb.lastpool = svn_pool_create (pool); + frb.currpool = svn_pool_create (pool); + + /* Collect all blame information. + We need to ensure that we get one revision before the start_rev, + if available so that we can know what was actually changed in the start + revision. */ + err = svn_ra_get_file_revs (ra_session, "", + start_revnum - (start_revnum > 0 ? 1 : 0), + end_revnum, TRUE, + file_rev_handler, &frb, pool); + if (err && err->apr_err != SVN_ERR_CLIENT_DONE_WITH_BLAME) + return err; + + /* Report the blame to the caller. */ + + /* The callback has to have been called at least once. */ + assert (frb.last_filename != NULL); + + /* Create a pool for the iteration below. */ + iterpool = svn_pool_create (pool); + + /* Perform the final blame, which is against a null stream. + FIXME: This should be simply an empty stream, not making it open + /dev/null. This is just proof of concept. */ + frb.rev = frb.nextrev; + add_file_blame (frb.last_filename, "/dev/null", &frb); + + for (i = 0; i < frb.headlines.nlines; i++) + { + struct line *curline = frb.headlines.vector[i]; + apr_pool_clear (iterpool); + char *text = apr_pcalloc (iterpool, curline->len + 1); + memcpy (text, curline->text, curline->len); + if (ctx->cancel_func) + SVN_ERR (ctx->cancel_func (ctx->cancel_baton)); + + /* Right now null means it's been there since before the we set null to mean it's been there since the first version + we asked about */ + if (curline->version == NULL) + { + SVN_ERR (receiver (receiver_baton, i, 0, + "?", "?l", + text, iterpool)); + } + else + { + SVN_ERR (receiver (receiver_baton, i, curline->version->revision, + curline->version->author, curline->version->date, + text, iterpool)); + } + } + + svn_pool_destroy (frb.lastpool); + svn_pool_destroy (frb.currpool); + svn_pool_destroy (iterpool); + + return SVN_NO_ERROR; +} + + Index: subversion/libsvn_client/blame.c =================================================================== --- subversion/libsvn_client/blame.c (revision 13154) +++ subversion/libsvn_client/blame.c (working copy) @@ -527,6 +527,7 @@ return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); + /* Get an RA plugin for this filesystem object. */ SVN_ERR (svn_client__ra_session_from_path (&ra_session, &end_revnum, &url, target, peg_revision, end, @@ -539,7 +540,19 @@ return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL, _("Start revision must precede end revision")); + + err = svn_client__reverse_blame (target, peg_revision, start, + end, receiver, receiver_baton, + ctx, pool); + /* Fall back if it wasn't supported by the server. Servers earlier + than 1.2 need this. */ + if (err == SVN_NO_ERROR) + return SVN_NO_ERROR; + if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) + svn_error_clear (err); + + frb.start_rev = start_revnum; frb.end_rev = end_revnum; frb.target = target; @@ -560,7 +573,7 @@ revision. */ err = svn_ra_get_file_revs (ra_session, "", start_revnum - (start_revnum > 0 ? 1 : 0), - end_revnum, + end_revnum, FALSE, file_rev_handler, &frb, pool); /* Fall back if it wasn't supported by the server. Servers earlier Index: subversion/mod_dav_svn/file_revs.c =================================================================== --- subversion/mod_dav_svn/file_revs.c (revision 13154) +++ subversion/mod_dav_svn/file_revs.c (working copy) @@ -306,3 +306,119 @@ resource->pool); return derr; } + +dav_error * +dav_svn__file_revs_reverse_report(const dav_resource *resource, + const apr_xml_doc *doc, + ap_filter_t *output) +{ + svn_error_t *serr; + dav_error *derr = NULL; + apr_status_t apr_err; + apr_xml_elem *child; + int ns; + struct file_rev_baton frb; + dav_svn_authz_read_baton arb; + const char *path = NULL; + + /* These get determined from the request document. */ + svn_revnum_t start = SVN_INVALID_REVNUM; + svn_revnum_t end = SVN_INVALID_REVNUM; + + /* Construct the authz read check baton. */ + arb.r = resource->info->r; + arb.repos = resource->info->repos; + + /* Sanity check. */ + ns = dav_svn_find_ns(doc->namespaces, SVN_XML_NAMESPACE); + /* ### This is done on other places, but the document element is + in this namespace, so is this necessary at all? */ + if (ns == -1) + { + return dav_new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0, + "The request does not contain the 'svn:' " + "namespace, so it is not going to have certain " + "required elements.", + SVN_DAV_ERROR_NAMESPACE, + SVN_DAV_ERROR_TAG); + } + + /* Get request information. */ + for (child = doc->root->first_child; child != NULL; child = child->next) + { + /* if this element isn't one of ours, then skip it */ + if (child->ns != ns) + continue; + + if (strcmp(child->name, "start-revision") == 0) + start = SVN_STR_TO_REV(dav_xml_get_cdata(child, resource->pool, 1)); + else if (strcmp(child->name, "end-revision") == 0) + end = SVN_STR_TO_REV(dav_xml_get_cdata(child, resource->pool, 1)); + else if (strcmp(child->name, "path") == 0) + { + /* Convert this relative path to an absolute path in the + repository. */ + path = apr_pstrdup(resource->pool, resource->info->repos_path); + + if (child->first_cdata.first) + { + if ((derr = dav_svn__test_canonical + (child->first_cdata.first->text, resource->pool))) + return derr; + path = svn_path_join(path, + child->first_cdata.first->text, + resource->pool); + } + } + /* else unknown element; skip it */ + } + + frb.bb = apr_brigade_create(resource->pool, + output->c->bucket_alloc); + frb.output = output; + frb.needs_header = TRUE; + + /* file_rev_handler will send header first time it is called. */ + + /* Get the revisions and send them. */ + serr = svn_repos_get_file_revs_reverse(resource->info->repos->repos, + path, start, end, + dav_svn_authz_read, &arb, + file_rev_handler, &frb, + resource->pool); + + if (serr) + { + derr = dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, serr->message, + resource->pool); + goto cleanup; + } + + if ((serr = maybe_send_header(&frb))) + { + derr = dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "Error beginning REPORT reponse", + resource->pool); + goto cleanup; + } + + if ((serr = dav_svn__send_xml(frb.bb, frb.output, + "" DEBUG_CR))) + { + derr = dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "Error ending REPORT reponse", + resource->pool); + goto cleanup; + } + + cleanup: + + /* Flush the contents of the brigade (returning an error only if we + don't already have one). */ + if (((apr_err = ap_fflush(output, frb.bb))) && (! derr)) + derr = dav_svn_convert_err(svn_error_create(apr_err, 0, NULL), + HTTP_INTERNAL_SERVER_ERROR, + "Error flushing brigade", + resource->pool); + return derr; +} Index: subversion/mod_dav_svn/version.c =================================================================== --- subversion/mod_dav_svn/version.c (revision 13154) +++ subversion/mod_dav_svn/version.c (working copy) @@ -1067,6 +1067,11 @@ { return dav_svn__file_revs_report(resource, doc, output); } + else if (strcmp(doc->root->name, "file-revs-reverse-report") == 0) + { + return dav_svn__file_revs_reverse_report(resource, doc, output); + } + } /* ### what is a good error for an unknown report? */ Index: subversion/mod_dav_svn/dav_svn.h =================================================================== --- subversion/mod_dav_svn/dav_svn.h (revision 13154) +++ subversion/mod_dav_svn/dav_svn.h (working copy) @@ -497,6 +497,12 @@ const apr_xml_doc *doc, ap_filter_t *output); +/* Respond to a client request for a REPORT of type file-revs-report for the + RESOURCE. Get request body from DOC and send result to OUTPUT. */ +dav_error * dav_svn__file_revs_reverse_report(const dav_resource *resource, + const apr_xml_doc *doc, + ap_filter_t *output); + int dav_svn_find_ns(apr_array_header_t *namespaces, const char *uri); /* Output XML data to OUTPUT using BB. Use FMT as format string for the. Index: subversion/libsvn_repos/rev_hunt.c =================================================================== --- subversion/libsvn_repos/rev_hunt.c (revision 13154) +++ subversion/libsvn_repos/rev_hunt.c (working copy) @@ -522,7 +522,7 @@ int i; svn_node_kind_t kind; - /* We switch betwwen two pools while looping, since we need information from + /* We switch between two pools while looping, since we need information from the last iteration to be available. */ iter_pool = svn_pool_create (pool); last_pool = svn_pool_create (pool); @@ -662,3 +662,140 @@ return SVN_NO_ERROR; } + + +svn_error_t * +svn_repos_get_file_revs_reverse (svn_repos_t *repos, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + svn_repos_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + apr_pool_t *iter_pool, *last_pool; + svn_fs_history_t *history; + apr_hash_t *last_props; + svn_fs_root_t *root, *last_root; + const char *last_path; + int i; + svn_node_kind_t kind; + + /* We switch between two pools while looping, since we need information from + the last iteration to be available. */ + iter_pool = svn_pool_create (pool); + last_pool = svn_pool_create (pool); + + /* Open revision root for path@end. */ + /* ### Can we use last_pool for this? How long does the history + object need the root? */ + SVN_ERR (svn_fs_revision_root (&root, repos->fs, end, pool)); + + /* The path had better be a file in this revision. This avoids calling + the callback before reporting an uglier error below. */ + SVN_ERR (svn_fs_check_path (&kind, root, path, pool)); + if (kind != svn_node_file) + return svn_error_create (SVN_ERR_FS_NOT_FILE, NULL, NULL); + + /* Open a history object. */ + SVN_ERR (svn_fs_node_history (&history, root, path, last_pool)); + + /* We want the first txdelta to be against the empty file. */ + last_root = NULL; + last_path = NULL; + + /* Create an empty hash table for the first property diff. */ + last_props = apr_hash_make (last_pool); + + /* Get the revisions we are interested in. */ + while (1) + { + const char* rev_path; + svn_revnum_t rev; + apr_pool_t *tmp_pool; + apr_hash_t *rev_props; + apr_hash_t *props; + apr_array_header_t *prop_diffs; + svn_txdelta_stream_t *delta_stream; + svn_txdelta_window_handler_t delta_handler = NULL; + void *delta_baton = NULL; + svn_boolean_t contents_changed; + svn_fs_root_t *tmp_root; + + svn_pool_clear (iter_pool); + + SVN_ERR (svn_fs_history_prev (&history, history, TRUE, iter_pool)); + if (!history) + break; + SVN_ERR (svn_fs_history_location (&rev_path, &rev, history, iter_pool)); + + SVN_ERR (svn_fs_revision_root (&tmp_root, repos->fs, rev, iter_pool)); + if (authz_read_func) + { + svn_boolean_t readable; + + SVN_ERR (authz_read_func (&readable, tmp_root, rev_path, + authz_read_baton, iter_pool)); + if (!readable) + break; + } + if (rev <= start) + break; + + /* Get the revision properties. */ + SVN_ERR (svn_fs_revision_proplist (&rev_props, repos->fs, + rev, iter_pool)); + + /* Get the file's properties for this revision and compute the diffs. */ + SVN_ERR (svn_fs_node_proplist (&props, tmp_root, rev_path, iter_pool)); + SVN_ERR (svn_prop_diffs (&prop_diffs, props, last_props, pool)); + + /* Check if the contents changed. */ + /* Special case: In the first revision, we always provide a delta. */ + if (last_root) + SVN_ERR (svn_fs_contents_changed (&contents_changed, + last_root, last_path, + tmp_root, rev_path, iter_pool)); + else + contents_changed = TRUE; + /* We have all we need, give to the handler. */ + SVN_ERR (handler (handler_baton, rev_path, rev, rev_props, + contents_changed ? &delta_handler : NULL, + contents_changed ? &delta_baton : NULL, + prop_diffs, iter_pool)); + + /* Compute and send delta if client asked for it. + Note that this was initialized to NULL, so if !contents_changed, + no deltas will be computed. */ + if (delta_handler) + { + /* Get the content delta. */ + SVN_ERR (svn_fs_get_file_delta_stream (&delta_stream, + last_root, last_path, + tmp_root, rev_path, + iter_pool)); + /* And send. */ + SVN_ERR (svn_txdelta_send_txstream (delta_stream, + delta_handler, delta_baton, + iter_pool)); + } + + + /* Remember root, path and props for next iteration. */ + last_root = tmp_root; + last_path = rev_path; + last_props = props; + + /* Swap pools. */ + tmp_pool = iter_pool; + iter_pool = last_pool; + last_pool = tmp_pool; + } + + svn_pool_destroy (last_pool); + svn_pool_destroy (iter_pool); + + return SVN_NO_ERROR; +} Index: subversion/libsvn_ra_svn/client.c =================================================================== --- subversion/libsvn_ra_svn/client.c (revision 13154) +++ subversion/libsvn_ra_svn/client.c (working copy) @@ -1203,11 +1203,14 @@ return SVN_NO_ERROR; } -static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, +/* The main work of get_file revs, both forwards and backwards versions. + */ + +static svn_error_t *get_file_revs_work(svn_ra_session_t *session, const char *path, svn_revnum_t start, svn_revnum_t end, svn_ra_file_rev_handler_t handler, - void *handler_baton, apr_pool_t *pool) + void *handler_baton, apr_pool_t *pool) { ra_svn_session_baton_t *sess_baton = session->priv; svn_error_t *err; @@ -1224,23 +1227,11 @@ svn_txdelta_window_handler_t d_handler; void *d_baton; apr_size_t size; - /* One sub-pool for each revision and one for each txdelta chunk. Note that the rev_pool must live during the following txdelta. */ rev_pool = svn_pool_create(pool); chunk_pool = svn_pool_create(pool); - SVN_ERR(svn_ra_svn_write_cmd(sess_baton->conn, pool, "get-file-revs", - "c(?r)(?r)", path, start, end)); - - err = handle_auth_request(sess_baton, pool); - - /* Servers before 1.1 don't support this command. Check for this here. */ - if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) - return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, - _("get-file-revs not implemented")); - SVN_ERR(err); - while (1) { svn_pool_clear(rev_pool); @@ -1298,20 +1289,100 @@ } SVN_ERR(svn_ra_svn_read_cmd_response(sess_baton->conn, pool, "")); - /* Return error if we didn't get any revisions. */ if (!had_revision) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("The get-file-revs command didn't return " "any revisions")); - svn_pool_destroy(chunk_pool); svn_pool_destroy(rev_pool); return SVN_NO_ERROR; + } +static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, + const char *path, + svn_revnum_t start, svn_revnum_t end, + svn_ra_file_rev_handler_t handler, + void *handler_baton, apr_pool_t *pool) +{ + ra_svn_session_baton_t *sess_baton = session->priv; + svn_error_t *err; + apr_pool_t *rev_pool, *chunk_pool; + svn_ra_svn_item_t *item; + const char *p; + svn_revnum_t rev; + apr_array_header_t *rev_proplist, *proplist; + apr_hash_t *rev_props; + apr_array_header_t *props; + svn_boolean_t has_txdelta; + svn_boolean_t had_revision = FALSE; + svn_stream_t *stream; + svn_txdelta_window_handler_t d_handler; + void *d_baton; + apr_size_t size; + + SVN_ERR(svn_ra_svn_write_cmd(sess_baton->conn, pool, "get-file-revs", + "c(?r)(?r)", path, start, end)); + + err = handle_auth_request(sess_baton, pool); + + /* Servers before 1.1 don't support this command. Check for this here. */ + if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) + return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, + _("get-file-revs not implemented")); + SVN_ERR(err); + return get_file_revs_work (session, path, start, end, handler, handler_baton, + pool); + +} +static svn_error_t *ra_svn_gfr_reverse (svn_ra_session_t *session, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_ra_file_rev_handler_t handler, + void *handler_baton, apr_pool_t *pool) +{ + ra_svn_session_baton_t *sess_baton = session->priv; + svn_error_t *err; + apr_pool_t *rev_pool, *chunk_pool; + svn_ra_svn_item_t *item; + const char *p; + svn_revnum_t rev; + apr_array_header_t *rev_proplist, *proplist; + apr_hash_t *rev_props; + apr_array_header_t *props; + svn_boolean_t has_txdelta; + svn_boolean_t had_revision = FALSE; + svn_stream_t *stream; + svn_txdelta_window_handler_t d_handler; + void *d_baton; + apr_size_t size; + + /* One sub-pool for each revision and one for each txdelta chunk. + Note that the rev_pool must live during the following txdelta. */ + rev_pool = svn_pool_create(pool); + chunk_pool = svn_pool_create(pool); + + SVN_ERR(svn_ra_svn_write_cmd(sess_baton->conn, pool, "get-file-revs-reverse", + "c(?r)(?r)", path, start, end)); + + err = handle_auth_request(sess_baton, pool); + + /* Servers before 1.2 don't support this command. Check for this here. */ + if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) + return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, + _("get-file-revs-reverse not implemented")); + SVN_ERR(err); + + svn_pool_destroy(rev_pool); + return get_file_revs_work (session, path, start, end, handler, handler_baton, + pool); +} + + static const svn_ra__vtable_t ra_svn_vtable = { svn_ra_svn_version, ra_svn_get_description, @@ -1334,7 +1405,8 @@ ra_svn_get_uuid, ra_svn_get_repos_root, ra_svn_get_locations, - ra_svn_get_file_revs + ra_svn_get_file_revs, + ra_svn_gfr_reverse }; svn_error_t * Index: subversion/libsvn_ra_svn/protocol =================================================================== --- subversion/libsvn_ra_svn/protocol (revision 13154) +++ subversion/libsvn_ra_svn/protocol (working copy) @@ -312,6 +312,18 @@ the terminator. response: ( ) + get-file-revs-reverse + params: ( path:string [ start-rev:number ] [ end-rev:number ] ) + Before sending response, server sends file-rev entries, ending with "done". + file-rev: ( path:string rev:number rev-props:proplist + file-props:propdelta ) + | done + After each file-rev, the file delta is sent as one or more strings, + terminated by the empty string. If there is no delta, server just sends + the terminator. + response: ( ) + + 3.1.2. Editor Command Set If edit pipelining is negotiated (see section 2.1), than an edit Index: subversion/libsvn_ra_dav/file_revs.c =================================================================== --- subversion/libsvn_ra_dav/file_revs.c (revision 13154) +++ subversion/libsvn_ra_dav/file_revs.c (working copy) @@ -370,3 +370,84 @@ return SVN_NO_ERROR; } + +svn_error_t * +svn_ra_dav__gfr_reverse (svn_ra_session_t *session, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_ra_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + svn_ra_dav__session_t *ras = session->priv; + svn_stringbuf_t *request_body = svn_stringbuf_create ("", ras->pool); + svn_string_t bc_url, bc_relative; + const char *final_bc_url; + int http_status = 0; + struct report_baton rb; + svn_error_t *err; + + static const char request_head[] + = "" DEBUG_CR; + static const char request_tail[] + = ""; + + /* Construct request body. */ + svn_stringbuf_appendcstr (request_body, request_head); + svn_stringbuf_appendcstr (request_body, + apr_psprintf (ras->pool, + "%ld" + "", start)); + svn_stringbuf_appendcstr (request_body, + apr_psprintf (ras->pool, + "%ld" + "", end)); + svn_stringbuf_appendcstr (request_body, ""); + svn_stringbuf_appendcstr (request_body, + apr_xml_quote_string (ras->pool, path, 0)); + svn_stringbuf_appendcstr (request_body, ""); + svn_stringbuf_appendcstr (request_body, request_tail); + + /* Initialize the baton. */ + rb.handler = handler; + rb.handler_baton = handler_baton; + rb.cdata_accum = svn_stringbuf_create ("", pool); + rb.err = NULL; + rb.subpool = svn_pool_create (pool); + reset_file_rev (&rb); + + /* ras's URL may not exist in HEAD, and thus it's not safe to send + it as the main argument to the REPORT request; it might cause + dav_get_resource() to choke on the server. So instead, we pass a + baseline-collection URL, which we get from END. */ + SVN_ERR (svn_ra_dav__get_baseline_info (NULL, &bc_url, &bc_relative, NULL, + ras->sess, ras->url, end, + ras->pool)); + final_bc_url = svn_path_url_add_component (bc_url.data, bc_relative.data, + ras->pool); + + /* Dispatch the request. */ + err = svn_ra_dav__parsed_request (ras->sess, "REPORT", final_bc_url, + request_body->data, NULL, NULL, + start_element, cdata_handler, end_element, + &rb, NULL, &http_status, ras->pool); + + /* Map status 501: Method Not Implemented to our not implemented error. + 1.1.x servers and older don't support this report. */ + if (http_status == 501) + return svn_error_create (SVN_ERR_RA_NOT_IMPLEMENTED, err, + _("get-file-revs-reverse REPORT not implemented")); + SVN_ERR (err); + SVN_ERR (rb.err); + + /* Caller expects at least one revision. Signal error otherwise. */ + if (!SVN_IS_VALID_REVNUM(rb.revnum)) + return svn_error_create (SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, + _("The file-revs report didn't contain any " + "revisions")); + + svn_pool_destroy (rb.subpool); + + return SVN_NO_ERROR; +} Index: subversion/libsvn_ra_dav/ra_dav.h =================================================================== --- subversion/libsvn_ra_dav/ra_dav.h (revision 13154) +++ subversion/libsvn_ra_dav/ra_dav.h (working copy) @@ -280,7 +280,16 @@ void *handler_baton, apr_pool_t *pool); +svn_error_t *svn_ra_dav__gfr_reverse (svn_ra_session_t *session, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_ra_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *pool); + + /* ** SVN_RA_DAV__LP_*: local properties for RA/DAV ** Index: subversion/libsvn_ra_dav/session.c =================================================================== --- subversion/libsvn_ra_dav/session.c (revision 13154) +++ subversion/libsvn_ra_dav/session.c (working copy) @@ -873,7 +873,8 @@ svn_ra_dav__do_get_uuid, svn_ra_dav__get_repos_root, svn_ra_dav__get_locations, - svn_ra_dav__get_file_revs + svn_ra_dav__get_file_revs, + svn_ra_dav__gfr_reverse }; svn_error_t * Index: subversion/svnserve/serve.c =================================================================== --- subversion/svnserve/serve.c (revision 13154) +++ subversion/svnserve/serve.c (working copy) @@ -1137,7 +1137,44 @@ return SVN_NO_ERROR; } +static svn_error_t *get_file_revs_reverse (svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + apr_array_header_t *params, + void *baton) +{ + server_baton_t *b = baton; + svn_error_t *err, *write_err; + file_revs_baton_t frb; + svn_revnum_t start_rev, end_rev; + const char *path; + const char *full_path; + + /* Parse arguments. */ + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)(?r)", + &path, &start_rev, &end_rev)); + path = svn_path_canonicalize(path, pool); + SVN_ERR(trivial_auth_request(conn, pool, b)); + full_path = svn_path_join(b->fs_path, path, pool); + frb.conn = conn; + frb.pool = NULL; + + err = svn_repos_get_file_revs_reverse(b->repos, full_path, start_rev, + end_rev, NULL, + NULL, file_rev_handler, &frb, pool); + write_err = svn_ra_svn_write_word(conn, pool, "done"); + if (write_err) + { + svn_error_clear(err); + return write_err; + } + SVN_CMD_ERR(err); + SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); + + return SVN_NO_ERROR; +} + + static const svn_ra_svn_cmd_entry_t main_commands[] = { { "get-latest-rev", get_latest_rev }, { "get-dated-rev", get_dated_rev }, @@ -1155,6 +1192,7 @@ { "check-path", check_path }, { "get-locations", get_locations }, { "get-file-revs", get_file_revs }, + { "get-file-revs-reverse", get_file_revs_reverse }, { NULL } };