📄 blame.c
字号:
/* 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; /* Create the rev structure. */ frb->rev = 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->rev->revision = SVN_INVALID_REVNUM; frb->rev->author = NULL; frb->rev->date = NULL; } else { svn_string_t *str; assert(revnum <= frb->end_rev); /* Set values from revision props. */ frb->rev->revision = revnum; if ((str = apr_hash_get(rev_props, SVN_PROP_REVISION_AUTHOR, sizeof(SVN_PROP_REVISION_AUTHOR) - 1))) frb->rev->author = apr_pstrdup(frb->mainpool, str->data); else frb->rev->author = NULL; if ((str = apr_hash_get(rev_props, SVN_PROP_REVISION_DATE, sizeof(SVN_PROP_REVISION_DATE) - 1))) frb->rev->date = apr_pstrdup(frb->mainpool, str->data); else frb->rev->date = NULL; } return SVN_NO_ERROR;}static svn_error_t *old_blame(const char *target, const char *url, svn_ra_session_t *ra_session, struct file_rev_baton *frb);svn_error_t *svn_client_blame3(const char *target, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *start, const svn_opt_revision_t *end, const svn_diff_file_options_t *diff_options, svn_boolean_t ignore_mime_type, 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; if (start->kind == svn_opt_revision_unspecified || end->kind == svn_opt_revision_unspecified) return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); else if (start->kind == svn_opt_revision_working || end->kind == svn_opt_revision_working) return svn_error_create (SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("blame of the WORKING revision is not supported")); /* 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.start_rev = start_revnum; frb.end_rev = end_revnum; frb.target = target; frb.ctx = ctx; frb.diff_options = diff_options; frb.ignore_mime_type = ignore_mime_type; frb.last_filename = NULL; frb.blame = NULL; frb.avail = NULL; SVN_ERR(svn_io_temp_dir(&frb.tmp_path, pool)); frb.tmp_path = svn_path_join(frb.tmp_path, "tmp", pool), 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, file_rev_handler, &frb, pool); /* Fall back if it wasn't supported by the server. Servers earlier than 1.1 need this. */ if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) { svn_error_clear(err); err = old_blame(target, url, ra_session, &frb); } SVN_ERR(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); /* Open the last file and get a stream. */ SVN_ERR(svn_io_file_open(&file, frb.last_filename, APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool)); stream = svn_subst_stream_translated(svn_stream_from_aprfile(file, pool), "\n", TRUE, NULL, FALSE, pool); /* Process each blame item. */ for (walk = frb.blame; walk; walk = walk->next) { apr_off_t line_no; for (line_no = walk->start; !walk->next || line_no < walk->next->start; ++line_no) { svn_boolean_t eof; svn_stringbuf_t *sb; apr_pool_clear(iterpool); SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, iterpool)); if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); if (!eof || sb->len) SVN_ERR(receiver(receiver_baton, line_no, walk->rev->revision, walk->rev->author, walk->rev->date, sb->data, iterpool)); if (eof) break; } } SVN_ERR(svn_stream_close(stream)); /* We don't need the temp file any more. */ SVN_ERR(svn_io_file_close(file, pool)); svn_pool_destroy(frb.lastpool); svn_pool_destroy(frb.currpool); svn_pool_destroy(iterpool); return SVN_NO_ERROR;}/* svn_client_blame3 guarantees 'no EOL chars' as part of the receiver LINE argument. Older versions depend on the fact that if a CR is required, that CR is already part of the LINE data. Because of this difference, we need to trap old receivers and append a CR to LINE before passing it on to the actual receiver on platforms which want CRLF line termination.*/struct wrapped_receiver_baton_s{ svn_client_blame_receiver_t orig_receiver; void *orig_baton;};static svn_error_t *wrapped_receiver(void *baton, apr_int64_t line_no, svn_revnum_t revision, const char *author, const char *date, const char *line, apr_pool_t *pool){ struct wrapped_receiver_baton_s *b = baton; svn_stringbuf_t *expanded_line = svn_stringbuf_create(line, pool); svn_stringbuf_appendbytes(expanded_line, "\r", 1); return b->orig_receiver(b->orig_baton, line_no, revision, author, date, expanded_line->data, pool);}static voidwrap_pre_blame3_receiver(svn_client_blame_receiver_t *receiver, void **receiver_baton, apr_pool_t *pool){ if (strlen(APR_EOL_STR) > 1) { struct wrapped_receiver_baton_s *b = apr_palloc(pool,sizeof(*b)); b->orig_receiver = *receiver; b->orig_baton = *receiver_baton; *receiver_baton = b; *receiver = wrapped_receiver; }}svn_error_t *svn_client_blame2(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){ wrap_pre_blame3_receiver(&receiver, &receiver_baton, pool); return svn_client_blame3(target, peg_revision, start, end, svn_diff_file_options_create(pool), FALSE, receiver, receiver_baton, ctx, pool);}svn_error_t *svn_client_blame(const char *target, 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){ wrap_pre_blame3_receiver(&receiver, &receiver_baton, pool); return svn_client_blame2(target, end, start, end, receiver, receiver_baton, ctx, pool);}/* This is used when there is no get_file_revs available. */static svn_error_t *old_blame(const char *target, const char *url, svn_ra_session_t *ra_session, struct file_rev_baton *frb){ const char *reposURL; struct log_message_baton lmb; apr_array_header_t *condensed_targets; apr_file_t *file; svn_stream_t *stream; struct rev *rev; svn_node_kind_t kind; apr_pool_t *pool = frb->mainpool; SVN_ERR(svn_ra_check_path(ra_session, "", frb->end_rev, &kind, pool)); if (kind == svn_node_dir) return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, NULL, _("URL '%s' refers to a directory"), url); condensed_targets = apr_array_make(pool, 1, sizeof(const char *)); (*((const char **)apr_array_push(condensed_targets))) = ""; SVN_ERR(svn_ra_get_repos_root(ra_session, &reposURL, pool)); /* URI decode the path before placing it in the baton, since changed_paths passed into log_message_receiver will not be URI encoded. */ lmb.path = svn_path_uri_decode(url + strlen(reposURL), pool); lmb.cancel_func = frb->ctx->cancel_func; lmb.cancel_baton = frb->ctx->cancel_baton; lmb.eldest = NULL; lmb.pool = pool; /* Accumulate revision metadata by walking the revisions backwards; this allows us to follow moves/copies correctly. */ SVN_ERR(svn_ra_get_log(ra_session, condensed_targets, frb->end_rev, frb->start_rev, 0, /* no limit */ TRUE, FALSE, log_message_receiver, &lmb, pool)); SVN_ERR(svn_client__open_ra_session_internal(&ra_session, reposURL, NULL, NULL, NULL, FALSE, FALSE, frb->ctx, pool)); /* Inspect the first revision's change metadata; if there are any prior revisions, compute a new starting revision/path. If no revisions were selected, no blame is assigned. A modified item certainly has a prior revision. It is reasonable for an added item to have none, but anything else is unexpected. */ if (!lmb.eldest) { lmb.eldest = apr_palloc(pool, sizeof(*rev)); lmb.eldest->revision = frb->end_rev; lmb.eldest->path = lmb.path; lmb.eldest->next = NULL; rev = apr_palloc(pool, sizeof(*rev)); rev->revision = SVN_INVALID_REVNUM; rev->author = NULL; rev->date = NULL; frb->blame = blame_create(frb, rev, 0); } else if (lmb.action == 'M' || SVN_IS_VALID_REVNUM(lmb.copyrev)) { rev = apr_palloc(pool, sizeof(*rev)); if (SVN_IS_VALID_REVNUM(lmb.copyrev)) rev->revision = lmb.copyrev; else rev->revision = lmb.eldest->revision - 1; rev->path = lmb.path; rev->next = lmb.eldest; lmb.eldest = rev; rev = apr_palloc(pool, sizeof(*rev)); rev->revision = SVN_INVALID_REVNUM; rev->author = NULL; rev->date = NULL; frb->blame = blame_create(frb, rev, 0); } else if (lmb.action == 'A') { frb->blame = blame_create(frb, lmb.eldest, 0); } else return svn_error_createf(APR_EGENERAL, NULL, _("Revision action '%c' for " "revision %ld of '%s' " "lacks a prior revision"), lmb.action, lmb.eldest->revision, svn_path_local_style(lmb.eldest->path, pool)); /* Walk the revision list in chronological order, downloading each fulltext, diffing it with its predecessor, and accumulating the blame information into db.blame. Use two iteration pools rather than one, because the diff routines need to look at a sliding window of revisions. Two pools gives us a ring buffer of sorts. */ for (rev = lmb.eldest; rev; rev = rev->next) { const char *tmp; const char *temp_dir; apr_hash_t *props; svn_string_t *mimetype; apr_pool_clear(frb->currpool); SVN_ERR(svn_io_temp_dir(&temp_dir, frb->currpool)); SVN_ERR(svn_io_open_unique_file2 (&file, &tmp, svn_path_join(temp_dir, "tmp", frb->currpool), ".tmp", svn_io_file_del_on_pool_cleanup, frb->currpool)); stream = svn_stream_from_aprfile(file, frb->currpool); SVN_ERR(svn_ra_get_file(ra_session, rev->path + 1, rev->revision, stream, NULL, &props, frb->currpool)); SVN_ERR(svn_stream_close(stream)); SVN_ERR(svn_io_file_close(file, frb->currpool)); /* If this file has a non-textual mime-type, bail out. */ if (! frb->ignore_mime_type && props && ((mimetype = apr_hash_get(props, SVN_PROP_MIME_TYPE, sizeof(SVN_PROP_MIME_TYPE) - 1)))) { if (svn_mime_type_is_binary(mimetype->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, frb->currpool)); } if (frb->ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(rev->path, svn_wc_notify_blame_revision, pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; notify->revision = rev->revision; frb->ctx->notify_func2(frb->ctx->notify_baton2, notify, pool); } if (frb->ctx->cancel_func) SVN_ERR(frb->ctx->cancel_func(frb->ctx->cancel_baton)); if (frb->last_filename) { frb->rev = rev; SVN_ERR(add_file_blame(frb->last_filename, tmp, frb)); } frb->last_filename = tmp; { apr_pool_t *tmppool = frb->currpool; frb->currpool = frb->lastpool; frb->lastpool = tmppool; } } return SVN_NO_ERROR;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -