1251881Speter/*
2251881Speter * blame.c:  return blame 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#include <apr_pools.h>
25251881Speter
26251881Speter#include "client.h"
27251881Speter
28251881Speter#include "svn_client.h"
29251881Speter#include "svn_subst.h"
30251881Speter#include "svn_string.h"
31251881Speter#include "svn_error.h"
32251881Speter#include "svn_diff.h"
33251881Speter#include "svn_pools.h"
34251881Speter#include "svn_dirent_uri.h"
35251881Speter#include "svn_path.h"
36251881Speter#include "svn_props.h"
37299742Sdim#include "svn_hash.h"
38251881Speter#include "svn_sorts.h"
39251881Speter
40251881Speter#include "private/svn_wc_private.h"
41251881Speter
42251881Speter#include "svn_private_config.h"
43251881Speter
44251881Speter#include <assert.h>
45251881Speter
46251881Speter/* The metadata associated with a particular revision. */
47251881Speterstruct rev
48251881Speter{
49251881Speter  svn_revnum_t revision; /* the revision number */
50251881Speter  apr_hash_t *rev_props; /* the revision properties */
51251881Speter  /* Used for merge reporting. */
52251881Speter  const char *path;      /* the absolute repository path */
53251881Speter};
54251881Speter
55251881Speter/* One chunk of blame */
56251881Speterstruct blame
57251881Speter{
58251881Speter  const struct rev *rev;    /* the responsible revision */
59251881Speter  apr_off_t start;          /* the starting diff-token (line) */
60251881Speter  struct blame *next;       /* the next chunk */
61251881Speter};
62251881Speter
63251881Speter/* A chain of blame chunks */
64251881Speterstruct blame_chain
65251881Speter{
66251881Speter  struct blame *blame;      /* linked list of blame chunks */
67251881Speter  struct blame *avail;      /* linked list of free blame chunks */
68251881Speter  struct apr_pool_t *pool;  /* Allocate members from this pool. */
69251881Speter};
70251881Speter
71251881Speter/* The baton use for the diff output routine. */
72251881Speterstruct diff_baton {
73251881Speter  struct blame_chain *chain;
74251881Speter  const struct rev *rev;
75251881Speter};
76251881Speter
77299742Sdim/* The baton used for a file revision. Lives the entire operation */
78251881Speterstruct file_rev_baton {
79251881Speter  svn_revnum_t start_rev, end_rev;
80299742Sdim  svn_boolean_t backwards;
81251881Speter  const char *target;
82251881Speter  svn_client_ctx_t *ctx;
83251881Speter  const svn_diff_file_options_t *diff_options;
84251881Speter  /* name of file containing the previous revision of the file */
85251881Speter  const char *last_filename;
86299742Sdim  struct rev *last_rev;   /* the rev of the last modification */
87251881Speter  struct blame_chain *chain;      /* the original blame chain. */
88251881Speter  const char *repos_root_url;    /* To construct a url */
89251881Speter  apr_pool_t *mainpool;  /* lives during the whole sequence of calls */
90251881Speter  apr_pool_t *lastpool;  /* pool used during previous call */
91251881Speter  apr_pool_t *currpool;  /* pool used during this call */
92251881Speter
93251881Speter  /* These are used for tracking merged revisions. */
94251881Speter  svn_boolean_t include_merged_revisions;
95251881Speter  struct blame_chain *merged_chain;  /* the merged blame chain. */
96251881Speter  /* name of file containing the previous merged revision of the file */
97251881Speter  const char *last_original_filename;
98251881Speter  /* pools for files which may need to persist for more than one rev. */
99251881Speter  apr_pool_t *filepool;
100251881Speter  apr_pool_t *prevfilepool;
101299742Sdim
102299742Sdim  svn_boolean_t check_mime_type;
103299742Sdim
104299742Sdim  /* When blaming backwards we have to use the changes
105299742Sdim     on the *next* revision, as the interesting change
106299742Sdim     happens when we move to the previous revision */
107299742Sdim  svn_revnum_t last_revnum;
108299742Sdim  apr_hash_t *last_props;
109251881Speter};
110251881Speter
111299742Sdim/* The baton used by the txdelta window handler. Allocated per revision */
112251881Speterstruct delta_baton {
113251881Speter  /* Our underlying handler/baton that we wrap */
114251881Speter  svn_txdelta_window_handler_t wrapped_handler;
115251881Speter  void *wrapped_baton;
116251881Speter  struct file_rev_baton *file_rev_baton;
117299742Sdim  svn_stream_t *source_stream;  /* the delta source */
118251881Speter  const char *filename;
119299742Sdim  svn_boolean_t is_merged_revision;
120299742Sdim  struct rev *rev;     /* the rev struct for the current revision */
121251881Speter};
122251881Speter
123251881Speter
124251881Speter
125251881Speter
126251881Speter/* Return a blame chunk associated with REV for a change starting
127251881Speter   at token START, and allocated in CHAIN->mainpool. */
128251881Speterstatic struct blame *
129251881Speterblame_create(struct blame_chain *chain,
130251881Speter             const struct rev *rev,
131251881Speter             apr_off_t start)
132251881Speter{
133251881Speter  struct blame *blame;
134251881Speter  if (chain->avail)
135251881Speter    {
136251881Speter      blame = chain->avail;
137251881Speter      chain->avail = blame->next;
138251881Speter    }
139251881Speter  else
140251881Speter    blame = apr_palloc(chain->pool, sizeof(*blame));
141251881Speter  blame->rev = rev;
142251881Speter  blame->start = start;
143251881Speter  blame->next = NULL;
144251881Speter  return blame;
145251881Speter}
146251881Speter
147251881Speter/* Destroy a blame chunk. */
148251881Speterstatic void
149251881Speterblame_destroy(struct blame_chain *chain,
150251881Speter              struct blame *blame)
151251881Speter{
152251881Speter  blame->next = chain->avail;
153251881Speter  chain->avail = blame;
154251881Speter}
155251881Speter
156251881Speter/* Return the blame chunk that contains token OFF, starting the search at
157251881Speter   BLAME. */
158251881Speterstatic struct blame *
159251881Speterblame_find(struct blame *blame, apr_off_t off)
160251881Speter{
161251881Speter  struct blame *prev = NULL;
162251881Speter  while (blame)
163251881Speter    {
164251881Speter      if (blame->start > off) break;
165251881Speter      prev = blame;
166251881Speter      blame = blame->next;
167251881Speter    }
168251881Speter  return prev;
169251881Speter}
170251881Speter
171251881Speter/* Shift the start-point of BLAME and all subsequence blame-chunks
172251881Speter   by ADJUST tokens */
173251881Speterstatic void
174251881Speterblame_adjust(struct blame *blame, apr_off_t adjust)
175251881Speter{
176251881Speter  while (blame)
177251881Speter    {
178251881Speter      blame->start += adjust;
179251881Speter      blame = blame->next;
180251881Speter    }
181251881Speter}
182251881Speter
183251881Speter/* Delete the blame associated with the region from token START to
184251881Speter   START + LENGTH */
185251881Speterstatic svn_error_t *
186251881Speterblame_delete_range(struct blame_chain *chain,
187251881Speter                   apr_off_t start,
188251881Speter                   apr_off_t length)
189251881Speter{
190251881Speter  struct blame *first = blame_find(chain->blame, start);
191251881Speter  struct blame *last = blame_find(chain->blame, start + length);
192251881Speter  struct blame *tail = last->next;
193251881Speter
194251881Speter  if (first != last)
195251881Speter    {
196251881Speter      struct blame *walk = first->next;
197251881Speter      while (walk != last)
198251881Speter        {
199251881Speter          struct blame *next = walk->next;
200251881Speter          blame_destroy(chain, walk);
201251881Speter          walk = next;
202251881Speter        }
203251881Speter      first->next = last;
204251881Speter      last->start = start;
205251881Speter      if (first->start == start)
206251881Speter        {
207251881Speter          *first = *last;
208251881Speter          blame_destroy(chain, last);
209251881Speter          last = first;
210251881Speter        }
211251881Speter    }
212251881Speter
213251881Speter  if (tail && tail->start == last->start + length)
214251881Speter    {
215251881Speter      *last = *tail;
216251881Speter      blame_destroy(chain, tail);
217251881Speter      tail = last->next;
218251881Speter    }
219251881Speter
220251881Speter  blame_adjust(tail, -length);
221251881Speter
222251881Speter  return SVN_NO_ERROR;
223251881Speter}
224251881Speter
225251881Speter/* Insert a chunk of blame associated with REV starting
226251881Speter   at token START and continuing for LENGTH tokens */
227251881Speterstatic svn_error_t *
228251881Speterblame_insert_range(struct blame_chain *chain,
229251881Speter                   const struct rev *rev,
230251881Speter                   apr_off_t start,
231251881Speter                   apr_off_t length)
232251881Speter{
233251881Speter  struct blame *head = chain->blame;
234251881Speter  struct blame *point = blame_find(head, start);
235251881Speter  struct blame *insert;
236251881Speter
237251881Speter  if (point->start == start)
238251881Speter    {
239251881Speter      insert = blame_create(chain, point->rev, point->start + length);
240251881Speter      point->rev = rev;
241251881Speter      insert->next = point->next;
242251881Speter      point->next = insert;
243251881Speter    }
244251881Speter  else
245251881Speter    {
246251881Speter      struct blame *middle;
247251881Speter      middle = blame_create(chain, rev, start);
248251881Speter      insert = blame_create(chain, point->rev, start + length);
249251881Speter      middle->next = insert;
250251881Speter      insert->next = point->next;
251251881Speter      point->next = middle;
252251881Speter    }
253251881Speter  blame_adjust(insert->next, length);
254251881Speter
255251881Speter  return SVN_NO_ERROR;
256251881Speter}
257251881Speter
258251881Speter/* Callback for diff between subsequent revisions */
259251881Speterstatic svn_error_t *
260251881Speteroutput_diff_modified(void *baton,
261251881Speter                     apr_off_t original_start,
262251881Speter                     apr_off_t original_length,
263251881Speter                     apr_off_t modified_start,
264251881Speter                     apr_off_t modified_length,
265251881Speter                     apr_off_t latest_start,
266251881Speter                     apr_off_t latest_length)
267251881Speter{
268251881Speter  struct diff_baton *db = baton;
269251881Speter
270251881Speter  if (original_length)
271251881Speter    SVN_ERR(blame_delete_range(db->chain, modified_start, original_length));
272251881Speter
273251881Speter  if (modified_length)
274251881Speter    SVN_ERR(blame_insert_range(db->chain, db->rev, modified_start,
275251881Speter                               modified_length));
276251881Speter
277251881Speter  return SVN_NO_ERROR;
278251881Speter}
279251881Speter
280251881Speterstatic const svn_diff_output_fns_t output_fns = {
281251881Speter        NULL,
282251881Speter        output_diff_modified
283251881Speter};
284251881Speter
285251881Speter/* Add the blame for the diffs between LAST_FILE and CUR_FILE to CHAIN,
286251881Speter   for revision REV.  LAST_FILE may be NULL in which
287251881Speter   case blame is added for every line of CUR_FILE. */
288251881Speterstatic svn_error_t *
289251881Speteradd_file_blame(const char *last_file,
290251881Speter               const char *cur_file,
291251881Speter               struct blame_chain *chain,
292251881Speter               struct rev *rev,
293251881Speter               const svn_diff_file_options_t *diff_options,
294299742Sdim               svn_cancel_func_t cancel_func,
295299742Sdim               void *cancel_baton,
296251881Speter               apr_pool_t *pool)
297251881Speter{
298251881Speter  if (!last_file)
299251881Speter    {
300251881Speter      SVN_ERR_ASSERT(chain->blame == NULL);
301251881Speter      chain->blame = blame_create(chain, rev, 0);
302251881Speter    }
303251881Speter  else
304251881Speter    {
305251881Speter      svn_diff_t *diff;
306251881Speter      struct diff_baton diff_baton;
307251881Speter
308251881Speter      diff_baton.chain = chain;
309251881Speter      diff_baton.rev = rev;
310251881Speter
311251881Speter      /* We have a previous file.  Get the diff and adjust blame info. */
312251881Speter      SVN_ERR(svn_diff_file_diff_2(&diff, last_file, cur_file,
313251881Speter                                   diff_options, pool));
314299742Sdim      SVN_ERR(svn_diff_output2(diff, &diff_baton, &output_fns,
315299742Sdim                               cancel_func, cancel_baton));
316251881Speter    }
317251881Speter
318251881Speter  return SVN_NO_ERROR;
319251881Speter}
320251881Speter
321299742Sdim/* Record the blame information for the revision in BATON->file_rev_baton.
322251881Speter */
323251881Speterstatic svn_error_t *
324299742Sdimupdate_blame(void *baton)
325251881Speter{
326251881Speter  struct delta_baton *dbaton = baton;
327251881Speter  struct file_rev_baton *frb = dbaton->file_rev_baton;
328251881Speter  struct blame_chain *chain;
329251881Speter
330299742Sdim  /* Close the source file used for the delta.
331299742Sdim     It is important to do this early, since otherwise, they will be deleted
332299742Sdim     before all handles are closed, which leads to failures on some platforms
333299742Sdim     when new tempfiles are to be created. */
334299742Sdim  if (dbaton->source_stream)
335299742Sdim    SVN_ERR(svn_stream_close(dbaton->source_stream));
336251881Speter
337251881Speter  /* If we are including merged revisions, we need to add each rev to the
338251881Speter     merged chain. */
339251881Speter  if (frb->include_merged_revisions)
340251881Speter    chain = frb->merged_chain;
341251881Speter  else
342251881Speter    chain = frb->chain;
343251881Speter
344251881Speter  /* Process this file. */
345251881Speter  SVN_ERR(add_file_blame(frb->last_filename,
346299742Sdim                         dbaton->filename, chain, dbaton->rev,
347299742Sdim                         frb->diff_options,
348299742Sdim                         frb->ctx->cancel_func, frb->ctx->cancel_baton,
349299742Sdim                         frb->currpool));
350251881Speter
351251881Speter  /* If we are including merged revisions, and the current revision is not a
352251881Speter     merged one, we need to add its blame info to the chain for the original
353251881Speter     line of history. */
354299742Sdim  if (frb->include_merged_revisions && ! dbaton->is_merged_revision)
355251881Speter    {
356251881Speter      apr_pool_t *tmppool;
357251881Speter
358251881Speter      SVN_ERR(add_file_blame(frb->last_original_filename,
359299742Sdim                             dbaton->filename, frb->chain, dbaton->rev,
360299742Sdim                             frb->diff_options,
361299742Sdim                             frb->ctx->cancel_func, frb->ctx->cancel_baton,
362299742Sdim                             frb->currpool));
363251881Speter
364251881Speter      /* This filename could be around for a while, potentially, so
365251881Speter         use the longer lifetime pool, and switch it with the previous one*/
366251881Speter      svn_pool_clear(frb->prevfilepool);
367251881Speter      tmppool = frb->filepool;
368251881Speter      frb->filepool = frb->prevfilepool;
369251881Speter      frb->prevfilepool = tmppool;
370251881Speter
371251881Speter      frb->last_original_filename = apr_pstrdup(frb->filepool,
372251881Speter                                                dbaton->filename);
373251881Speter    }
374251881Speter
375251881Speter  /* Prepare for next revision. */
376251881Speter
377251881Speter  /* Remember the file name so we can diff it with the next revision. */
378251881Speter  frb->last_filename = dbaton->filename;
379251881Speter
380251881Speter  /* Switch pools. */
381251881Speter  {
382251881Speter    apr_pool_t *tmp_pool = frb->lastpool;
383251881Speter    frb->lastpool = frb->currpool;
384251881Speter    frb->currpool = tmp_pool;
385251881Speter  }
386251881Speter
387251881Speter  return SVN_NO_ERROR;
388251881Speter}
389251881Speter
390299742Sdim/* The delta window handler for the text delta between the previously seen
391299742Sdim * revision and the revision currently being handled.
392299742Sdim *
393299742Sdim * Record the blame information for this revision in BATON->file_rev_baton.
394299742Sdim *
395299742Sdim * Implements svn_txdelta_window_handler_t.
396299742Sdim */
397299742Sdimstatic svn_error_t *
398299742Sdimwindow_handler(svn_txdelta_window_t *window, void *baton)
399299742Sdim{
400299742Sdim  struct delta_baton *dbaton = baton;
401251881Speter
402299742Sdim  /* Call the wrapped handler first. */
403299742Sdim  if (dbaton->wrapped_handler)
404299742Sdim    SVN_ERR(dbaton->wrapped_handler(window, dbaton->wrapped_baton));
405299742Sdim
406299742Sdim  /* We patiently wait for the NULL window marking the end. */
407299742Sdim  if (window)
408299742Sdim    return SVN_NO_ERROR;
409299742Sdim
410299742Sdim  /* Diff and update blame info. */
411299742Sdim  SVN_ERR(update_blame(baton));
412299742Sdim
413299742Sdim  return SVN_NO_ERROR;
414299742Sdim}
415299742Sdim
416299742Sdim
417251881Speter/* Calculate and record blame information for one revision of the file,
418251881Speter * by comparing the file content against the previously seen revision.
419251881Speter *
420251881Speter * This handler is called once for each interesting revision of the file.
421251881Speter *
422251881Speter * Record the blame information for this revision in (file_rev_baton) BATON.
423251881Speter *
424251881Speter * Implements svn_file_rev_handler_t.
425251881Speter */
426251881Speterstatic svn_error_t *
427251881Speterfile_rev_handler(void *baton, const char *path, svn_revnum_t revnum,
428251881Speter                 apr_hash_t *rev_props,
429251881Speter                 svn_boolean_t merged_revision,
430251881Speter                 svn_txdelta_window_handler_t *content_delta_handler,
431251881Speter                 void **content_delta_baton,
432251881Speter                 apr_array_header_t *prop_diffs,
433251881Speter                 apr_pool_t *pool)
434251881Speter{
435251881Speter  struct file_rev_baton *frb = baton;
436251881Speter  svn_stream_t *last_stream;
437251881Speter  svn_stream_t *cur_stream;
438251881Speter  struct delta_baton *delta_baton;
439251881Speter  apr_pool_t *filepool;
440251881Speter
441251881Speter  /* Clear the current pool. */
442251881Speter  svn_pool_clear(frb->currpool);
443251881Speter
444299742Sdim  if (frb->check_mime_type)
445299742Sdim    {
446299742Sdim      apr_hash_t *props = svn_prop_array_to_hash(prop_diffs, frb->currpool);
447299742Sdim      const char *value;
448299742Sdim
449299742Sdim      frb->check_mime_type = FALSE; /* Only check first */
450299742Sdim
451299742Sdim      value = svn_prop_get_value(props, SVN_PROP_MIME_TYPE);
452299742Sdim
453299742Sdim      if (value && svn_mime_type_is_binary(value))
454299742Sdim        {
455299742Sdim          return svn_error_createf(
456299742Sdim              SVN_ERR_CLIENT_IS_BINARY_FILE, NULL,
457299742Sdim              _("Cannot calculate blame information for binary file '%s'"),
458299742Sdim               (svn_path_is_url(frb->target)
459299742Sdim                      ? frb->target
460299742Sdim                      : svn_dirent_local_style(frb->target, pool)));
461299742Sdim        }
462299742Sdim    }
463299742Sdim
464251881Speter  if (frb->ctx->notify_func2)
465251881Speter    {
466251881Speter      svn_wc_notify_t *notify
467251881Speter            = svn_wc_create_notify_url(
468251881Speter                            svn_path_url_add_component2(frb->repos_root_url,
469251881Speter                                                        path+1, pool),
470251881Speter                            svn_wc_notify_blame_revision, pool);
471251881Speter      notify->path = path;
472251881Speter      notify->kind = svn_node_none;
473251881Speter      notify->content_state = notify->prop_state
474251881Speter        = svn_wc_notify_state_inapplicable;
475251881Speter      notify->lock_state = svn_wc_notify_lock_state_inapplicable;
476251881Speter      notify->revision = revnum;
477251881Speter      notify->rev_props = rev_props;
478251881Speter      frb->ctx->notify_func2(frb->ctx->notify_baton2, notify, pool);
479251881Speter    }
480251881Speter
481251881Speter  if (frb->ctx->cancel_func)
482251881Speter    SVN_ERR(frb->ctx->cancel_func(frb->ctx->cancel_baton));
483251881Speter
484299742Sdim  /* If there were no content changes and no (potential) merges, we couldn't
485299742Sdim     care less about this revision now.  Note that we checked the mime type
486299742Sdim     above, so things work if the user just changes the mime type in a commit.
487251881Speter     Also note that we don't switch the pools in this case.  This is important,
488251881Speter     since the tempfile will be removed by the pool and we need the tempfile
489251881Speter     from the last revision with content changes. */
490299742Sdim  if (!content_delta_handler
491299742Sdim      && (!frb->include_merged_revisions || merged_revision))
492251881Speter    return SVN_NO_ERROR;
493251881Speter
494251881Speter  /* Create delta baton. */
495299742Sdim  delta_baton = apr_pcalloc(frb->currpool, sizeof(*delta_baton));
496251881Speter
497251881Speter  /* Prepare the text delta window handler. */
498251881Speter  if (frb->last_filename)
499299742Sdim    SVN_ERR(svn_stream_open_readonly(&delta_baton->source_stream, frb->last_filename,
500251881Speter                                     frb->currpool, pool));
501251881Speter  else
502299742Sdim    /* Means empty stream below. */
503299742Sdim    delta_baton->source_stream = NULL;
504299742Sdim  last_stream = svn_stream_disown(delta_baton->source_stream, pool);
505251881Speter
506299742Sdim  if (frb->include_merged_revisions && !merged_revision)
507251881Speter    filepool = frb->filepool;
508251881Speter  else
509251881Speter    filepool = frb->currpool;
510251881Speter
511251881Speter  SVN_ERR(svn_stream_open_unique(&cur_stream, &delta_baton->filename, NULL,
512251881Speter                                 svn_io_file_del_on_pool_cleanup,
513299742Sdim                                 filepool, filepool));
514251881Speter
515251881Speter  /* Wrap the window handler with our own. */
516251881Speter  delta_baton->file_rev_baton = frb;
517299742Sdim  delta_baton->is_merged_revision = merged_revision;
518251881Speter
519251881Speter  /* Create the rev structure. */
520299742Sdim  delta_baton->rev = apr_pcalloc(frb->mainpool, sizeof(struct rev));
521251881Speter
522299742Sdim  if (frb->backwards)
523251881Speter    {
524299742Sdim      /* Use from last round...
525299742Sdim         SVN_INVALID_REVNUM on first, which is exactly
526299742Sdim         what we want */
527299742Sdim      delta_baton->rev->revision = frb->last_revnum;
528299742Sdim      delta_baton->rev->rev_props = frb->last_props;
529299742Sdim
530299742Sdim      /* Store for next delta */
531299742Sdim      if (revnum >= MIN(frb->start_rev, frb->end_rev))
532299742Sdim        {
533299742Sdim          frb->last_revnum = revnum;
534299742Sdim          frb->last_props = svn_prop_hash_dup(rev_props, frb->mainpool);
535299742Sdim        }
536299742Sdim      /* Else: Not needed on last rev */
537299742Sdim    }
538299742Sdim  else if (merged_revision
539299742Sdim           || (revnum >= MIN(frb->start_rev, frb->end_rev)))
540299742Sdim    {
541299742Sdim      /* 1+ for the "youngest to oldest" blame */
542299742Sdim      SVN_ERR_ASSERT(revnum <= 1 + MAX(frb->end_rev, frb->start_rev));
543299742Sdim
544299742Sdim      /* Set values from revision props. */
545299742Sdim      delta_baton->rev->revision = revnum;
546299742Sdim      delta_baton->rev->rev_props = svn_prop_hash_dup(rev_props, frb->mainpool);
547299742Sdim    }
548299742Sdim  else
549299742Sdim    {
550299742Sdim      /* We shouldn't get more than one revision outside the
551299742Sdim         specified range (unless we alsoe receive merged revisions) */
552251881Speter      SVN_ERR_ASSERT((frb->last_filename == NULL)
553251881Speter                     || frb->include_merged_revisions);
554251881Speter
555251881Speter      /* The file existed before start_rev; generate no blame info for
556299742Sdim         lines from this revision (or before).
557299742Sdim
558299742Sdim         This revision specifies the state as it was at the start revision */
559299742Sdim
560299742Sdim      delta_baton->rev->revision = SVN_INVALID_REVNUM;
561251881Speter    }
562299742Sdim
563299742Sdim  if (frb->include_merged_revisions)
564299742Sdim    delta_baton->rev->path = apr_pstrdup(frb->mainpool, path);
565299742Sdim
566299742Sdim  /* Keep last revision for postprocessing after all changes */
567299742Sdim  frb->last_rev = delta_baton->rev;
568299742Sdim
569299742Sdim  /* Handle all delta - even if it is empty.
570299742Sdim     We must do the latter to "merge" blame info from other branches. */
571299742Sdim  if (content_delta_handler)
572299742Sdim    {
573299742Sdim      /* Proper delta - get window handler for applying delta.
574299742Sdim         svn_ra_get_file_revs2 will drive the delta editor. */
575299742Sdim      svn_txdelta_apply(last_stream, cur_stream, NULL, NULL,
576299742Sdim                        frb->currpool,
577299742Sdim                        &delta_baton->wrapped_handler,
578299742Sdim                        &delta_baton->wrapped_baton);
579299742Sdim      *content_delta_handler = window_handler;
580299742Sdim      *content_delta_baton = delta_baton;
581299742Sdim    }
582251881Speter  else
583251881Speter    {
584299742Sdim      /* Apply an empty delta, i.e. simply copy the old contents.
585299742Sdim         We can't simply use the existing file due to the pool rotation logic.
586299742Sdim         Trigger the blame update magic. */
587299742Sdim      SVN_ERR(svn_stream_copy3(last_stream, cur_stream, NULL, NULL, pool));
588299742Sdim      SVN_ERR(update_blame(delta_baton));
589251881Speter    }
590251881Speter
591251881Speter  return SVN_NO_ERROR;
592251881Speter}
593251881Speter
594251881Speter/* Ensure that CHAIN_ORIG and CHAIN_MERGED have the same number of chunks,
595251881Speter   and that for every chunk C, CHAIN_ORIG[C] and CHAIN_MERGED[C] have the
596251881Speter   same starting value.  Both CHAIN_ORIG and CHAIN_MERGED should not be
597251881Speter   NULL.  */
598251881Speterstatic void
599251881Speternormalize_blames(struct blame_chain *chain,
600251881Speter                 struct blame_chain *chain_merged,
601251881Speter                 apr_pool_t *pool)
602251881Speter{
603251881Speter  struct blame *walk, *walk_merged;
604251881Speter
605251881Speter  /* Walk over the CHAIN's blame chunks and CHAIN_MERGED's blame chunks,
606251881Speter     creating new chunks as needed. */
607251881Speter  for (walk = chain->blame, walk_merged = chain_merged->blame;
608251881Speter       walk->next && walk_merged->next;
609251881Speter       walk = walk->next, walk_merged = walk_merged->next)
610251881Speter    {
611251881Speter      /* The current chunks should always be starting at the same offset. */
612251881Speter      assert(walk->start == walk_merged->start);
613251881Speter
614251881Speter      if (walk->next->start < walk_merged->next->start)
615251881Speter        {
616251881Speter          /* insert a new chunk in CHAIN_MERGED. */
617251881Speter          struct blame *tmp = blame_create(chain_merged, walk_merged->rev,
618251881Speter                                           walk->next->start);
619251881Speter          tmp->next = walk_merged->next;
620251881Speter          walk_merged->next = tmp;
621251881Speter        }
622251881Speter
623251881Speter      if (walk->next->start > walk_merged->next->start)
624251881Speter        {
625251881Speter          /* insert a new chunk in CHAIN. */
626251881Speter          struct blame *tmp = blame_create(chain, walk->rev,
627251881Speter                                           walk_merged->next->start);
628251881Speter          tmp->next = walk->next;
629251881Speter          walk->next = tmp;
630251881Speter        }
631251881Speter    }
632251881Speter
633251881Speter  /* If both NEXT pointers are null, the lists are equally long, otherwise
634251881Speter     we need to extend one of them.  If CHAIN is longer, append new chunks
635251881Speter     to CHAIN_MERGED until its length matches that of CHAIN. */
636251881Speter  while (walk->next != NULL)
637251881Speter    {
638251881Speter      struct blame *tmp = blame_create(chain_merged, walk_merged->rev,
639251881Speter                                       walk->next->start);
640251881Speter      walk_merged->next = tmp;
641251881Speter
642251881Speter      walk_merged = walk_merged->next;
643251881Speter      walk = walk->next;
644251881Speter    }
645251881Speter
646251881Speter  /* Same as above, only extend CHAIN to match CHAIN_MERGED. */
647251881Speter  while (walk_merged->next != NULL)
648251881Speter    {
649251881Speter      struct blame *tmp = blame_create(chain, walk->rev,
650251881Speter                                       walk_merged->next->start);
651251881Speter      walk->next = tmp;
652251881Speter
653251881Speter      walk = walk->next;
654251881Speter      walk_merged = walk_merged->next;
655251881Speter    }
656251881Speter}
657251881Speter
658251881Spetersvn_error_t *
659251881Spetersvn_client_blame5(const char *target,
660251881Speter                  const svn_opt_revision_t *peg_revision,
661251881Speter                  const svn_opt_revision_t *start,
662251881Speter                  const svn_opt_revision_t *end,
663251881Speter                  const svn_diff_file_options_t *diff_options,
664251881Speter                  svn_boolean_t ignore_mime_type,
665251881Speter                  svn_boolean_t include_merged_revisions,
666251881Speter                  svn_client_blame_receiver3_t receiver,
667251881Speter                  void *receiver_baton,
668251881Speter                  svn_client_ctx_t *ctx,
669251881Speter                  apr_pool_t *pool)
670251881Speter{
671251881Speter  struct file_rev_baton frb;
672251881Speter  svn_ra_session_t *ra_session;
673251881Speter  svn_revnum_t start_revnum, end_revnum;
674251881Speter  struct blame *walk, *walk_merged = NULL;
675251881Speter  apr_pool_t *iterpool;
676251881Speter  svn_stream_t *last_stream;
677251881Speter  svn_stream_t *stream;
678251881Speter  const char *target_abspath_or_url;
679251881Speter
680251881Speter  if (start->kind == svn_opt_revision_unspecified
681251881Speter      || end->kind == svn_opt_revision_unspecified)
682251881Speter    return svn_error_create
683251881Speter      (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
684251881Speter
685251881Speter  if (svn_path_is_url(target))
686251881Speter    target_abspath_or_url = target;
687251881Speter  else
688251881Speter    SVN_ERR(svn_dirent_get_absolute(&target_abspath_or_url, target, pool));
689251881Speter
690251881Speter  /* Get an RA plugin for this filesystem object. */
691299742Sdim  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, NULL,
692299742Sdim                                            target, NULL, peg_revision,
693299742Sdim                                            peg_revision,
694251881Speter                                            ctx, pool));
695251881Speter
696251881Speter  SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx,
697251881Speter                                          target_abspath_or_url, ra_session,
698251881Speter                                          start, pool));
699251881Speter
700299742Sdim  SVN_ERR(svn_client__get_revision_number(&end_revnum, NULL, ctx->wc_ctx,
701299742Sdim                                          target_abspath_or_url, ra_session,
702299742Sdim                                          end, pool));
703251881Speter
704299742Sdim  {
705299742Sdim    svn_client__pathrev_t *loc;
706299742Sdim    svn_opt_revision_t younger_end;
707299742Sdim    younger_end.kind = svn_opt_revision_number;
708299742Sdim    younger_end.value.number = MAX(start_revnum, end_revnum);
709299742Sdim
710299742Sdim    SVN_ERR(svn_client__resolve_rev_and_url(&loc, ra_session,
711299742Sdim                                            target, peg_revision,
712299742Sdim                                            &younger_end,
713299742Sdim                                            ctx, pool));
714299742Sdim
715299742Sdim    /* Make the session point to the real URL. */
716299742Sdim    SVN_ERR(svn_ra_reparent(ra_session, loc->url, pool));
717299742Sdim  }
718299742Sdim
719251881Speter  /* We check the mime-type of the yougest revision before getting all
720251881Speter     the older revisions. */
721299742Sdim  if (!ignore_mime_type
722299742Sdim      && start_revnum < end_revnum)
723251881Speter    {
724251881Speter      apr_hash_t *props;
725299742Sdim      const char *mime_type = NULL;
726251881Speter
727299742Sdim      if (svn_path_is_url(target)
728299742Sdim          || start_revnum > end_revnum
729299742Sdim          || (end->kind != svn_opt_revision_working
730299742Sdim              && end->kind != svn_opt_revision_base))
731299742Sdim        {
732299742Sdim          SVN_ERR(svn_ra_get_file(ra_session, "", end_revnum, NULL, NULL,
733299742Sdim                                  &props, pool));
734251881Speter
735299742Sdim          mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE);
736299742Sdim        }
737299742Sdim      else
738251881Speter        {
739299742Sdim          const svn_string_t *value;
740251881Speter
741299742Sdim          if (end->kind == svn_opt_revision_working)
742299742Sdim            SVN_ERR(svn_wc_prop_get2(&value, ctx->wc_ctx,
743299742Sdim                                     target_abspath_or_url,
744299742Sdim                                     SVN_PROP_MIME_TYPE,
745299742Sdim                                     pool, pool));
746299742Sdim          else
747299742Sdim            {
748299742Sdim              SVN_ERR(svn_wc_get_pristine_props(&props, ctx->wc_ctx,
749299742Sdim                                                target_abspath_or_url,
750299742Sdim                                                pool, pool));
751251881Speter
752299742Sdim              value = props ? svn_hash_gets(props, SVN_PROP_MIME_TYPE)
753299742Sdim                            : NULL;
754299742Sdim            }
755299742Sdim
756299742Sdim          mime_type = value ? value->data : NULL;
757299742Sdim        }
758299742Sdim
759299742Sdim      if (mime_type)
760299742Sdim        {
761299742Sdim          if (svn_mime_type_is_binary(mime_type))
762251881Speter            return svn_error_createf
763251881Speter              (SVN_ERR_CLIENT_IS_BINARY_FILE, 0,
764251881Speter               _("Cannot calculate blame information for binary file '%s'"),
765251881Speter               (svn_path_is_url(target)
766251881Speter                ? target : svn_dirent_local_style(target, pool)));
767251881Speter        }
768251881Speter    }
769251881Speter
770251881Speter  frb.start_rev = start_revnum;
771251881Speter  frb.end_rev = end_revnum;
772251881Speter  frb.target = target;
773251881Speter  frb.ctx = ctx;
774251881Speter  frb.diff_options = diff_options;
775251881Speter  frb.include_merged_revisions = include_merged_revisions;
776251881Speter  frb.last_filename = NULL;
777299742Sdim  frb.last_rev = NULL;
778251881Speter  frb.last_original_filename = NULL;
779251881Speter  frb.chain = apr_palloc(pool, sizeof(*frb.chain));
780251881Speter  frb.chain->blame = NULL;
781251881Speter  frb.chain->avail = NULL;
782251881Speter  frb.chain->pool = pool;
783251881Speter  if (include_merged_revisions)
784251881Speter    {
785251881Speter      frb.merged_chain = apr_palloc(pool, sizeof(*frb.merged_chain));
786251881Speter      frb.merged_chain->blame = NULL;
787251881Speter      frb.merged_chain->avail = NULL;
788251881Speter      frb.merged_chain->pool = pool;
789251881Speter    }
790299742Sdim  frb.backwards = (frb.start_rev > frb.end_rev);
791299742Sdim  frb.last_revnum = SVN_INVALID_REVNUM;
792299742Sdim  frb.last_props = NULL;
793299742Sdim  frb.check_mime_type = (frb.backwards && !ignore_mime_type);
794251881Speter
795251881Speter  SVN_ERR(svn_ra_get_repos_root2(ra_session, &frb.repos_root_url, pool));
796251881Speter
797251881Speter  frb.mainpool = pool;
798251881Speter  /* The callback will flip the following two pools, because it needs
799251881Speter     information from the previous call.  Obviously, it can't rely on
800251881Speter     the lifetime of the pool provided by get_file_revs. */
801251881Speter  frb.lastpool = svn_pool_create(pool);
802251881Speter  frb.currpool = svn_pool_create(pool);
803251881Speter  if (include_merged_revisions)
804251881Speter    {
805251881Speter      frb.filepool = svn_pool_create(pool);
806251881Speter      frb.prevfilepool = svn_pool_create(pool);
807251881Speter    }
808251881Speter
809251881Speter  /* Collect all blame information.
810251881Speter     We need to ensure that we get one revision before the start_rev,
811251881Speter     if available so that we can know what was actually changed in the start
812251881Speter     revision. */
813251881Speter  SVN_ERR(svn_ra_get_file_revs2(ra_session, "",
814299742Sdim                                frb.backwards ? start_revnum
815299742Sdim                                              : MAX(0, start_revnum-1),
816299742Sdim                                end_revnum,
817299742Sdim                                include_merged_revisions,
818251881Speter                                file_rev_handler, &frb, pool));
819251881Speter
820251881Speter  if (end->kind == svn_opt_revision_working)
821251881Speter    {
822251881Speter      /* If the local file is modified we have to call the handler on the
823251881Speter         working copy file with keywords unexpanded */
824251881Speter      svn_wc_status3_t *status;
825251881Speter
826251881Speter      SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, target_abspath_or_url, pool,
827251881Speter                             pool));
828251881Speter
829251881Speter      if (status->text_status != svn_wc_status_normal
830251881Speter          || (status->prop_status != svn_wc_status_normal
831251881Speter              && status->prop_status != svn_wc_status_none))
832251881Speter        {
833251881Speter          svn_stream_t *wcfile;
834251881Speter          svn_stream_t *tempfile;
835251881Speter          svn_opt_revision_t rev;
836251881Speter          svn_boolean_t normalize_eols = FALSE;
837251881Speter          const char *temppath;
838251881Speter
839251881Speter          if (status->prop_status != svn_wc_status_none)
840251881Speter            {
841251881Speter              const svn_string_t *eol_style;
842251881Speter              SVN_ERR(svn_wc_prop_get2(&eol_style, ctx->wc_ctx,
843251881Speter                                       target_abspath_or_url,
844251881Speter                                       SVN_PROP_EOL_STYLE,
845251881Speter                                       pool, pool));
846251881Speter
847251881Speter              if (eol_style)
848251881Speter                {
849251881Speter                  svn_subst_eol_style_t style;
850251881Speter                  const char *eol;
851251881Speter                  svn_subst_eol_style_from_value(&style, &eol, eol_style->data);
852251881Speter
853251881Speter                  normalize_eols = (style == svn_subst_eol_style_native);
854251881Speter                }
855251881Speter            }
856251881Speter
857251881Speter          rev.kind = svn_opt_revision_working;
858251881Speter          SVN_ERR(svn_client__get_normalized_stream(&wcfile, ctx->wc_ctx,
859251881Speter                                                    target_abspath_or_url, &rev,
860251881Speter                                                    FALSE, normalize_eols,
861251881Speter                                                    ctx->cancel_func,
862251881Speter                                                    ctx->cancel_baton,
863251881Speter                                                    pool, pool));
864251881Speter
865251881Speter          SVN_ERR(svn_stream_open_unique(&tempfile, &temppath, NULL,
866251881Speter                                         svn_io_file_del_on_pool_cleanup,
867251881Speter                                         pool, pool));
868251881Speter
869251881Speter          SVN_ERR(svn_stream_copy3(wcfile, tempfile, ctx->cancel_func,
870251881Speter                                   ctx->cancel_baton, pool));
871251881Speter
872251881Speter          SVN_ERR(add_file_blame(frb.last_filename, temppath, frb.chain, NULL,
873299742Sdim                                 frb.diff_options,
874299742Sdim                                 ctx->cancel_func, ctx->cancel_baton, pool));
875251881Speter
876251881Speter          frb.last_filename = temppath;
877251881Speter        }
878251881Speter    }
879251881Speter
880251881Speter  /* Report the blame to the caller. */
881251881Speter
882251881Speter  /* The callback has to have been called at least once. */
883251881Speter  SVN_ERR_ASSERT(frb.last_filename != NULL);
884251881Speter
885251881Speter  /* Create a pool for the iteration below. */
886251881Speter  iterpool = svn_pool_create(pool);
887251881Speter
888251881Speter  /* Open the last file and get a stream. */
889251881Speter  SVN_ERR(svn_stream_open_readonly(&last_stream, frb.last_filename,
890251881Speter                                   pool, pool));
891251881Speter  stream = svn_subst_stream_translated(last_stream,
892251881Speter                                       "\n", TRUE, NULL, FALSE, pool);
893251881Speter
894251881Speter  /* Perform optional merged chain normalization. */
895251881Speter  if (include_merged_revisions)
896251881Speter    {
897251881Speter      /* If we never created any blame for the original chain, create it now,
898251881Speter         with the most recent changed revision.  This could occur if a file
899251881Speter         was created on a branch and them merged to another branch.  This is
900251881Speter         semanticly a copy, and we want to use the revision on the branch as
901251881Speter         the most recently changed revision.  ### Is this really what we want
902251881Speter         to do here?  Do the sematics of copy change? */
903251881Speter      if (!frb.chain->blame)
904299742Sdim        frb.chain->blame = blame_create(frb.chain, frb.last_rev, 0);
905251881Speter
906251881Speter      normalize_blames(frb.chain, frb.merged_chain, pool);
907251881Speter      walk_merged = frb.merged_chain->blame;
908251881Speter    }
909251881Speter
910251881Speter  /* Process each blame item. */
911251881Speter  for (walk = frb.chain->blame; walk; walk = walk->next)
912251881Speter    {
913251881Speter      apr_off_t line_no;
914251881Speter      svn_revnum_t merged_rev;
915251881Speter      const char *merged_path;
916251881Speter      apr_hash_t *merged_rev_props;
917251881Speter
918251881Speter      if (walk_merged)
919251881Speter        {
920251881Speter          merged_rev = walk_merged->rev->revision;
921251881Speter          merged_rev_props = walk_merged->rev->rev_props;
922251881Speter          merged_path = walk_merged->rev->path;
923251881Speter        }
924251881Speter      else
925251881Speter        {
926251881Speter          merged_rev = SVN_INVALID_REVNUM;
927251881Speter          merged_rev_props = NULL;
928251881Speter          merged_path = NULL;
929251881Speter        }
930251881Speter
931251881Speter      for (line_no = walk->start;
932251881Speter           !walk->next || line_no < walk->next->start;
933251881Speter           ++line_no)
934251881Speter        {
935251881Speter          svn_boolean_t eof;
936251881Speter          svn_stringbuf_t *sb;
937251881Speter
938251881Speter          svn_pool_clear(iterpool);
939251881Speter          SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, iterpool));
940251881Speter          if (ctx->cancel_func)
941251881Speter            SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
942251881Speter          if (!eof || sb->len)
943251881Speter            {
944251881Speter              if (walk->rev)
945251881Speter                SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum,
946251881Speter                                 line_no, walk->rev->revision,
947251881Speter                                 walk->rev->rev_props, merged_rev,
948251881Speter                                 merged_rev_props, merged_path,
949251881Speter                                 sb->data, FALSE, iterpool));
950251881Speter              else
951251881Speter                SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum,
952251881Speter                                 line_no, SVN_INVALID_REVNUM,
953251881Speter                                 NULL, SVN_INVALID_REVNUM,
954251881Speter                                 NULL, NULL,
955251881Speter                                 sb->data, TRUE, iterpool));
956251881Speter            }
957251881Speter          if (eof) break;
958251881Speter        }
959251881Speter
960251881Speter      if (walk_merged)
961251881Speter        walk_merged = walk_merged->next;
962251881Speter    }
963251881Speter
964251881Speter  SVN_ERR(svn_stream_close(stream));
965251881Speter
966251881Speter  svn_pool_destroy(frb.lastpool);
967251881Speter  svn_pool_destroy(frb.currpool);
968251881Speter  if (include_merged_revisions)
969251881Speter    {
970251881Speter      svn_pool_destroy(frb.filepool);
971251881Speter      svn_pool_destroy(frb.prevfilepool);
972251881Speter    }
973251881Speter  svn_pool_destroy(iterpool);
974251881Speter
975251881Speter  return SVN_NO_ERROR;
976251881Speter}
977