summaryrefslogtreecommitdiff
path: root/subversion/libsvn_client/blame.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/blame.c')
-rw-r--r--subversion/libsvn_client/blame.c307
1 files changed, 223 insertions, 84 deletions
diff --git a/subversion/libsvn_client/blame.c b/subversion/libsvn_client/blame.c
index 188fdd2..b9363e2 100644
--- a/subversion/libsvn_client/blame.c
+++ b/subversion/libsvn_client/blame.c
@@ -34,6 +34,7 @@
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_props.h"
+#include "svn_hash.h"
#include "svn_sorts.h"
#include "private/svn_wc_private.h"
@@ -73,16 +74,16 @@ struct diff_baton {
const struct rev *rev;
};
-/* The baton used for a file revision. */
+/* The baton used for a file revision. Lives the entire operation */
struct file_rev_baton {
svn_revnum_t start_rev, end_rev;
+ svn_boolean_t backwards;
const char *target;
svn_client_ctx_t *ctx;
const svn_diff_file_options_t *diff_options;
/* name of file containing the previous revision of the file */
const char *last_filename;
- struct rev *rev; /* the rev for which blame is being assigned
- during a diff */
+ struct rev *last_rev; /* the rev of the last modification */
struct blame_chain *chain; /* the original blame chain. */
const char *repos_root_url; /* To construct a url */
apr_pool_t *mainpool; /* lives during the whole sequence of calls */
@@ -91,22 +92,32 @@ struct file_rev_baton {
/* These are used for tracking merged revisions. */
svn_boolean_t include_merged_revisions;
- svn_boolean_t merged_revision;
struct blame_chain *merged_chain; /* the merged blame chain. */
/* name of file containing the previous merged revision of the file */
const char *last_original_filename;
/* pools for files which may need to persist for more than one rev. */
apr_pool_t *filepool;
apr_pool_t *prevfilepool;
+
+ svn_boolean_t check_mime_type;
+
+ /* When blaming backwards we have to use the changes
+ on the *next* revision, as the interesting change
+ happens when we move to the previous revision */
+ svn_revnum_t last_revnum;
+ apr_hash_t *last_props;
};
-/* The baton used by the txdelta window handler. */
+/* The baton used by the txdelta window handler. Allocated per revision */
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;
+ svn_stream_t *source_stream; /* the delta source */
const char *filename;
+ svn_boolean_t is_merged_revision;
+ struct rev *rev; /* the rev struct for the current revision */
};
@@ -280,6 +291,8 @@ add_file_blame(const char *last_file,
struct blame_chain *chain,
struct rev *rev,
const svn_diff_file_options_t *diff_options,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
apr_pool_t *pool)
{
if (!last_file)
@@ -298,32 +311,28 @@ add_file_blame(const char *last_file,
/* We have a previous file. Get the diff and adjust blame info. */
SVN_ERR(svn_diff_file_diff_2(&diff, last_file, cur_file,
diff_options, pool));
- SVN_ERR(svn_diff_output(diff, &diff_baton, &output_fns));
+ SVN_ERR(svn_diff_output2(diff, &diff_baton, &output_fns,
+ cancel_func, cancel_baton));
}
return SVN_NO_ERROR;
}
-/* The delta window handler for the text delta between the previously seen
- * revision and the revision currently being handled.
- *
- * Record the blame information for this revision in BATON->file_rev_baton.
- *
- * Implements svn_txdelta_window_handler_t.
+/* Record the blame information for the revision in BATON->file_rev_baton.
*/
static svn_error_t *
-window_handler(svn_txdelta_window_t *window, void *baton)
+update_blame(void *baton)
{
struct delta_baton *dbaton = baton;
struct file_rev_baton *frb = dbaton->file_rev_baton;
struct blame_chain *chain;
- /* 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 source file 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_stream)
+ SVN_ERR(svn_stream_close(dbaton->source_stream));
/* If we are including merged revisions, we need to add each rev to the
merged chain. */
@@ -334,19 +343,23 @@ window_handler(svn_txdelta_window_t *window, void *baton)
/* Process this file. */
SVN_ERR(add_file_blame(frb->last_filename,
- dbaton->filename, chain, frb->rev,
- frb->diff_options, frb->currpool));
+ dbaton->filename, chain, dbaton->rev,
+ frb->diff_options,
+ frb->ctx->cancel_func, frb->ctx->cancel_baton,
+ frb->currpool));
/* If we are including merged revisions, and the current revision is not a
merged one, we need to add its blame info to the chain for the original
line of history. */
- if (frb->include_merged_revisions && ! frb->merged_revision)
+ if (frb->include_merged_revisions && ! dbaton->is_merged_revision)
{
apr_pool_t *tmppool;
SVN_ERR(add_file_blame(frb->last_original_filename,
- dbaton->filename, frb->chain, frb->rev,
- frb->diff_options, frb->currpool));
+ dbaton->filename, frb->chain, dbaton->rev,
+ frb->diff_options,
+ frb->ctx->cancel_func, frb->ctx->cancel_baton,
+ frb->currpool));
/* This filename could be around for a while, potentially, so
use the longer lifetime pool, and switch it with the previous one*/
@@ -374,6 +387,32 @@ window_handler(svn_txdelta_window_t *window, void *baton)
return SVN_NO_ERROR;
}
+/* The delta window handler for the text delta between the previously seen
+ * revision and the revision currently being handled.
+ *
+ * Record the blame information for this revision in BATON->file_rev_baton.
+ *
+ * Implements svn_txdelta_window_handler_t.
+ */
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct delta_baton *dbaton = baton;
+
+ /* Call the wrapped handler first. */
+ if (dbaton->wrapped_handler)
+ 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;
+
+ /* Diff and update blame info. */
+ SVN_ERR(update_blame(baton));
+
+ return SVN_NO_ERROR;
+}
+
/* Calculate and record blame information for one revision of the file,
* by comparing the file content against the previously seen revision.
@@ -402,6 +441,26 @@ file_rev_handler(void *baton, const char *path, svn_revnum_t revnum,
/* Clear the current pool. */
svn_pool_clear(frb->currpool);
+ if (frb->check_mime_type)
+ {
+ apr_hash_t *props = svn_prop_array_to_hash(prop_diffs, frb->currpool);
+ const char *value;
+
+ frb->check_mime_type = FALSE; /* Only check first */
+
+ value = svn_prop_get_value(props, SVN_PROP_MIME_TYPE);
+
+ if (value && svn_mime_type_is_binary(value))
+ {
+ return svn_error_createf(
+ SVN_ERR_CLIENT_IS_BINARY_FILE, NULL,
+ _("Cannot calculate blame information for binary file '%s'"),
+ (svn_path_is_url(frb->target)
+ ? frb->target
+ : svn_dirent_local_style(frb->target, pool)));
+ }
+ }
+
if (frb->ctx->notify_func2)
{
svn_wc_notify_t *notify
@@ -422,72 +481,112 @@ file_rev_handler(void *baton, const char *path, svn_revnum_t 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.
+ /* If there were no content changes and no (potential) merges, 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)
+ if (!content_delta_handler
+ && (!frb->include_merged_revisions || merged_revision))
return SVN_NO_ERROR;
- frb->merged_revision = merged_revision;
-
/* Create delta baton. */
- delta_baton = apr_palloc(frb->currpool, sizeof(*delta_baton));
+ delta_baton = apr_pcalloc(frb->currpool, sizeof(*delta_baton));
/* Prepare the text delta window handler. */
if (frb->last_filename)
- SVN_ERR(svn_stream_open_readonly(&last_stream, frb->last_filename,
+ SVN_ERR(svn_stream_open_readonly(&delta_baton->source_stream, frb->last_filename,
frb->currpool, pool));
else
- last_stream = svn_stream_empty(frb->currpool);
+ /* Means empty stream below. */
+ delta_baton->source_stream = NULL;
+ last_stream = svn_stream_disown(delta_baton->source_stream, pool);
- if (frb->include_merged_revisions && !frb->merged_revision)
+ if (frb->include_merged_revisions && !merged_revision)
filepool = frb->filepool;
else
filepool = frb->currpool;
SVN_ERR(svn_stream_open_unique(&cur_stream, &delta_baton->filename, NULL,
svn_io_file_del_on_pool_cleanup,
- filepool, pool));
-
- /* 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);
+ filepool, filepool));
/* Wrap the window handler with our own. */
delta_baton->file_rev_baton = frb;
- *content_delta_handler = window_handler;
- *content_delta_baton = delta_baton;
+ delta_baton->is_merged_revision = merged_revision;
/* Create the rev structure. */
- frb->rev = apr_pcalloc(frb->mainpool, sizeof(struct rev));
+ delta_baton->rev = apr_pcalloc(frb->mainpool, sizeof(struct rev));
- if (revnum < frb->start_rev)
+ if (frb->backwards)
{
- /* We shouldn't get more than one revision before the starting
- revision (unless of including merged revisions). */
- SVN_ERR_ASSERT((frb->last_filename == NULL)
- || frb->include_merged_revisions);
+ /* Use from last round...
+ SVN_INVALID_REVNUM on first, which is exactly
+ what we want */
+ delta_baton->rev->revision = frb->last_revnum;
+ delta_baton->rev->rev_props = frb->last_props;
+
+ /* Store for next delta */
+ if (revnum >= MIN(frb->start_rev, frb->end_rev))
+ {
+ frb->last_revnum = revnum;
+ frb->last_props = svn_prop_hash_dup(rev_props, frb->mainpool);
+ }
+ /* Else: Not needed on last rev */
+ }
+ else if (merged_revision
+ || (revnum >= MIN(frb->start_rev, frb->end_rev)))
+ {
+ /* 1+ for the "youngest to oldest" blame */
+ SVN_ERR_ASSERT(revnum <= 1 + MAX(frb->end_rev, frb->start_rev));
- /* The file existed before start_rev; generate no blame info for
- lines from this revision (or before). */
- frb->rev->revision = SVN_INVALID_REVNUM;
+ /* Set values from revision props. */
+ delta_baton->rev->revision = revnum;
+ delta_baton->rev->rev_props = svn_prop_hash_dup(rev_props, frb->mainpool);
}
else
{
- SVN_ERR_ASSERT(revnum <= frb->end_rev);
+ /* We shouldn't get more than one revision outside the
+ specified range (unless we alsoe receive merged revisions) */
+ SVN_ERR_ASSERT((frb->last_filename == NULL)
+ || frb->include_merged_revisions);
- /* Set values from revision props. */
- frb->rev->revision = revnum;
- frb->rev->rev_props = svn_prop_hash_dup(rev_props, frb->mainpool);
+ /* The file existed before start_rev; generate no blame info for
+ lines from this revision (or before).
+
+ This revision specifies the state as it was at the start revision */
+
+ delta_baton->rev->revision = SVN_INVALID_REVNUM;
}
if (frb->include_merged_revisions)
- frb->rev->path = apr_pstrdup(frb->mainpool, path);
+ delta_baton->rev->path = apr_pstrdup(frb->mainpool, path);
+
+ /* Keep last revision for postprocessing after all changes */
+ frb->last_rev = delta_baton->rev;
+
+ /* Handle all delta - even if it is empty.
+ We must do the latter to "merge" blame info from other branches. */
+ if (content_delta_handler)
+ {
+ /* Proper delta - get window handler for applying delta.
+ svn_ra_get_file_revs2 will drive the delta editor. */
+ svn_txdelta_apply(last_stream, cur_stream, NULL, NULL,
+ frb->currpool,
+ &delta_baton->wrapped_handler,
+ &delta_baton->wrapped_baton);
+ *content_delta_handler = window_handler;
+ *content_delta_baton = delta_baton;
+ }
+ else
+ {
+ /* Apply an empty delta, i.e. simply copy the old contents.
+ We can't simply use the existing file due to the pool rotation logic.
+ Trigger the blame update magic. */
+ SVN_ERR(svn_stream_copy3(last_stream, cur_stream, NULL, NULL, pool));
+ SVN_ERR(update_blame(delta_baton));
+ }
return SVN_NO_ERROR;
}
@@ -572,7 +671,6 @@ svn_client_blame5(const char *target,
struct file_rev_baton frb;
svn_ra_session_t *ra_session;
svn_revnum_t start_revnum, end_revnum;
- svn_client__pathrev_t *end_loc;
struct blame *walk, *walk_merged = NULL;
apr_pool_t *iterpool;
svn_stream_t *last_stream;
@@ -590,44 +688,77 @@ svn_client_blame5(const char *target,
SVN_ERR(svn_dirent_get_absolute(&target_abspath_or_url, target, pool));
/* Get an RA plugin for this filesystem object. */
- SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &end_loc,
- target, NULL, peg_revision, end,
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, NULL,
+ target, NULL, peg_revision,
+ peg_revision,
ctx, pool));
- end_revnum = end_loc->rev;
SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx,
target_abspath_or_url, ra_session,
start, pool));
- if (end_revnum < start_revnum)
- return svn_error_create
- (SVN_ERR_CLIENT_BAD_REVISION, NULL,
- _("Start revision must precede end revision"));
+ SVN_ERR(svn_client__get_revision_number(&end_revnum, NULL, ctx->wc_ctx,
+ target_abspath_or_url, ra_session,
+ end, pool));
+
+ {
+ svn_client__pathrev_t *loc;
+ svn_opt_revision_t younger_end;
+ younger_end.kind = svn_opt_revision_number;
+ younger_end.value.number = MAX(start_revnum, end_revnum);
+
+ SVN_ERR(svn_client__resolve_rev_and_url(&loc, ra_session,
+ target, peg_revision,
+ &younger_end,
+ ctx, pool));
+
+ /* Make the session point to the real URL. */
+ SVN_ERR(svn_ra_reparent(ra_session, loc->url, pool));
+ }
/* We check the mime-type of the yougest revision before getting all
the older revisions. */
- if (!ignore_mime_type)
+ if (!ignore_mime_type
+ && start_revnum < end_revnum)
{
apr_hash_t *props;
- apr_hash_index_t *hi;
+ const char *mime_type = NULL;
- SVN_ERR(svn_client_propget5(&props, NULL, SVN_PROP_MIME_TYPE,
- target_abspath_or_url, peg_revision,
- end, NULL, svn_depth_empty, NULL, ctx,
- pool, pool));
+ if (svn_path_is_url(target)
+ || start_revnum > end_revnum
+ || (end->kind != svn_opt_revision_working
+ && end->kind != svn_opt_revision_base))
+ {
+ SVN_ERR(svn_ra_get_file(ra_session, "", end_revnum, NULL, NULL,
+ &props, pool));
- /* props could be keyed on URLs or paths depending on the
- peg_revision and end values so avoid using the key. */
- hi = apr_hash_first(pool, props);
- if (hi)
+ mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE);
+ }
+ else
{
- svn_string_t *value;
+ const svn_string_t *value;
+
+ if (end->kind == svn_opt_revision_working)
+ SVN_ERR(svn_wc_prop_get2(&value, ctx->wc_ctx,
+ target_abspath_or_url,
+ SVN_PROP_MIME_TYPE,
+ pool, pool));
+ else
+ {
+ SVN_ERR(svn_wc_get_pristine_props(&props, ctx->wc_ctx,
+ target_abspath_or_url,
+ pool, pool));
- /* Should only be one value */
- SVN_ERR_ASSERT(apr_hash_count(props) == 1);
+ value = props ? svn_hash_gets(props, SVN_PROP_MIME_TYPE)
+ : NULL;
+ }
- value = svn__apr_hash_index_val(hi);
- if (value && svn_mime_type_is_binary(value->data))
+ mime_type = value ? value->data : NULL;
+ }
+
+ if (mime_type)
+ {
+ if (svn_mime_type_is_binary(mime_type))
return svn_error_createf
(SVN_ERR_CLIENT_IS_BINARY_FILE, 0,
_("Cannot calculate blame information for binary file '%s'"),
@@ -643,6 +774,7 @@ svn_client_blame5(const char *target,
frb.diff_options = diff_options;
frb.include_merged_revisions = include_merged_revisions;
frb.last_filename = NULL;
+ frb.last_rev = NULL;
frb.last_original_filename = NULL;
frb.chain = apr_palloc(pool, sizeof(*frb.chain));
frb.chain->blame = NULL;
@@ -655,6 +787,10 @@ svn_client_blame5(const char *target,
frb.merged_chain->avail = NULL;
frb.merged_chain->pool = pool;
}
+ frb.backwards = (frb.start_rev > frb.end_rev);
+ frb.last_revnum = SVN_INVALID_REVNUM;
+ frb.last_props = NULL;
+ frb.check_mime_type = (frb.backwards && !ignore_mime_type);
SVN_ERR(svn_ra_get_repos_root2(ra_session, &frb.repos_root_url, pool));
@@ -675,8 +811,10 @@ svn_client_blame5(const char *target,
if available so that we can know what was actually changed in the start
revision. */
SVN_ERR(svn_ra_get_file_revs2(ra_session, "",
- start_revnum - (start_revnum > 0 ? 1 : 0),
- end_revnum, include_merged_revisions,
+ frb.backwards ? start_revnum
+ : MAX(0, start_revnum-1),
+ end_revnum,
+ include_merged_revisions,
file_rev_handler, &frb, pool));
if (end->kind == svn_opt_revision_working)
@@ -732,7 +870,8 @@ svn_client_blame5(const char *target,
ctx->cancel_baton, pool));
SVN_ERR(add_file_blame(frb.last_filename, temppath, frb.chain, NULL,
- frb.diff_options, pool));
+ frb.diff_options,
+ ctx->cancel_func, ctx->cancel_baton, pool));
frb.last_filename = temppath;
}
@@ -762,7 +901,7 @@ svn_client_blame5(const char *target,
the most recently changed revision. ### Is this really what we want
to do here? Do the sematics of copy change? */
if (!frb.chain->blame)
- frb.chain->blame = blame_create(frb.chain, frb.rev, 0);
+ frb.chain->blame = blame_create(frb.chain, frb.last_rev, 0);
normalize_blames(frb.chain, frb.merged_chain, pool);
walk_merged = frb.merged_chain->blame;