1 /*
2 * diff.c: comparing
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24 /* ==================================================================== */
25
26
27
28 /*** Includes. ***/
29
30 #include <apr_strings.h>
31 #include <apr_pools.h>
32 #include <apr_hash.h>
33 #include "svn_types.h"
34 #include "svn_hash.h"
35 #include "svn_wc.h"
36 #include "svn_diff.h"
37 #include "svn_mergeinfo.h"
38 #include "svn_client.h"
39 #include "svn_string.h"
40 #include "svn_error.h"
41 #include "svn_dirent_uri.h"
42 #include "svn_path.h"
43 #include "svn_io.h"
44 #include "svn_utf.h"
45 #include "svn_pools.h"
46 #include "svn_config.h"
47 #include "svn_props.h"
48 #include "svn_subst.h"
49 #include "client.h"
50
51 #include "private/svn_wc_private.h"
52 #include "private/svn_diff_private.h"
53 #include "private/svn_subr_private.h"
54 #include "private/svn_io_private.h"
55 #include "private/svn_ra_private.h"
56
57 #include "svn_private_config.h"
58
59
60 /* Utilities */
61
62 #define DIFF_REVNUM_NONEXISTENT ((svn_revnum_t) -100)
63
64 #define MAKE_ERR_BAD_RELATIVE_PATH(path, relative_to_dir) \
65 svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL, \
66 _("Path '%s' must be an immediate child of " \
67 "the directory '%s'"), path, relative_to_dir)
68
69 /* Calculate the repository relative path of DIFF_RELPATH, using
70 * SESSION_RELPATH and WC_CTX, and return the result in *REPOS_RELPATH.
71 * ORIG_TARGET is the related original target passed to the diff command,
72 * and may be used to derive leading path components missing from PATH.
73 * ANCHOR is the local path where the diff editor is anchored.
74 * Do all allocations in POOL. */
75 static svn_error_t *
make_repos_relpath(const char ** repos_relpath,const char * diff_relpath,const char * orig_target,const char * session_relpath,svn_wc_context_t * wc_ctx,const char * anchor,apr_pool_t * result_pool,apr_pool_t * scratch_pool)76 make_repos_relpath(const char **repos_relpath,
77 const char *diff_relpath,
78 const char *orig_target,
79 const char *session_relpath,
80 svn_wc_context_t *wc_ctx,
81 const char *anchor,
82 apr_pool_t *result_pool,
83 apr_pool_t *scratch_pool)
84 {
85 const char *local_abspath;
86
87 if (! session_relpath
88 || (anchor && !svn_path_is_url(orig_target)))
89 {
90 svn_error_t *err;
91 /* We're doing a WC-WC diff, so we can retrieve all information we
92 * need from the working copy. */
93 SVN_ERR(svn_dirent_get_absolute(&local_abspath,
94 svn_dirent_join(anchor, diff_relpath,
95 scratch_pool),
96 scratch_pool));
97
98 err = svn_wc__node_get_repos_info(NULL, repos_relpath, NULL, NULL,
99 wc_ctx, local_abspath,
100 result_pool, scratch_pool);
101
102 if (!session_relpath
103 || ! err
104 || (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND))
105 {
106 return svn_error_trace(err);
107 }
108
109 /* The path represents a local working copy path, but does not
110 exist. Fall through to calculate an in-repository location
111 based on the ra session */
112
113 /* ### Maybe we should use the nearest existing ancestor instead? */
114 svn_error_clear(err);
115 }
116
117 *repos_relpath = svn_relpath_join(session_relpath, diff_relpath,
118 result_pool);
119
120 return SVN_NO_ERROR;
121 }
122
123 /* Adjust *INDEX_PATH, *ORIG_PATH_1 and *ORIG_PATH_2, representing the changed
124 * node and the two original targets passed to the diff command, to handle the
125 * case when we're dealing with different anchors. RELATIVE_TO_DIR is the
126 * directory the diff target should be considered relative to.
127 * ANCHOR is the local path where the diff editor is anchored. The resulting
128 * values are allocated in RESULT_POOL and temporary allocations are performed
129 * in SCRATCH_POOL. */
130 static svn_error_t *
adjust_paths_for_diff_labels(const char ** index_path,const char ** orig_path_1,const char ** orig_path_2,const char * relative_to_dir,const char * anchor,apr_pool_t * result_pool,apr_pool_t * scratch_pool)131 adjust_paths_for_diff_labels(const char **index_path,
132 const char **orig_path_1,
133 const char **orig_path_2,
134 const char *relative_to_dir,
135 const char *anchor,
136 apr_pool_t *result_pool,
137 apr_pool_t *scratch_pool)
138 {
139 const char *new_path = *index_path;
140 const char *new_path1 = *orig_path_1;
141 const char *new_path2 = *orig_path_2;
142
143 if (anchor)
144 new_path = svn_dirent_join(anchor, new_path, result_pool);
145
146 if (relative_to_dir)
147 {
148 /* Possibly adjust the paths shown in the output (see issue #2723). */
149 const char *child_path = svn_dirent_is_child(relative_to_dir, new_path,
150 result_pool);
151
152 if (child_path)
153 new_path = child_path;
154 else if (! strcmp(relative_to_dir, new_path))
155 new_path = ".";
156 else
157 return MAKE_ERR_BAD_RELATIVE_PATH(new_path, relative_to_dir);
158 }
159
160 {
161 apr_size_t len;
162 svn_boolean_t is_url1;
163 svn_boolean_t is_url2;
164 /* ### Holy cow. Due to anchor/target weirdness, we can't
165 simply join dwi->orig_path_1 with path, ditto for
166 orig_path_2. That will work when they're directory URLs, but
167 not for file URLs. Nor can we just use anchor1 and anchor2
168 from do_diff(), at least not without some more logic here.
169 What a nightmare.
170
171 For now, to distinguish the two paths, we'll just put the
172 unique portions of the original targets in parentheses after
173 the received path, with ellipses for handwaving. This makes
174 the labels a bit clumsy, but at least distinctive. Better
175 solutions are possible, they'll just take more thought. */
176
177 /* ### BH: We can now just construct the repos_relpath, etc. as the
178 anchor is available. See also make_repos_relpath() */
179
180 is_url1 = svn_path_is_url(new_path1);
181 is_url2 = svn_path_is_url(new_path2);
182
183 if (is_url1 && is_url2)
184 len = strlen(svn_uri_get_longest_ancestor(new_path1, new_path2,
185 scratch_pool));
186 else if (!is_url1 && !is_url2)
187 len = strlen(svn_dirent_get_longest_ancestor(new_path1, new_path2,
188 scratch_pool));
189 else
190 len = 0; /* Path and URL */
191
192 new_path1 += len;
193 new_path2 += len;
194 }
195
196 /* ### Should diff labels print paths in local style? Is there
197 already a standard for this? In any case, this code depends on
198 a particular style, so not calling svn_dirent_local_style() on the
199 paths below.*/
200
201 if (new_path[0] == '\0')
202 new_path = ".";
203
204 if (new_path1[0] == '\0')
205 new_path1 = new_path;
206 else if (svn_path_is_url(new_path1))
207 new_path1 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path1);
208 else if (new_path1[0] == '/')
209 new_path1 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path1);
210 else
211 new_path1 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path1);
212
213 if (new_path2[0] == '\0')
214 new_path2 = new_path;
215 else if (svn_path_is_url(new_path2))
216 new_path2 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path2);
217 else if (new_path2[0] == '/')
218 new_path2 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path2);
219 else
220 new_path2 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path2);
221
222 *index_path = new_path;
223 *orig_path_1 = new_path1;
224 *orig_path_2 = new_path2;
225
226 return SVN_NO_ERROR;
227 }
228
229
230 /* Generate a label for the diff output for file PATH at revision REVNUM.
231 If REVNUM is invalid then it is assumed to be the current working
232 copy. Assumes the paths are already in the desired style (local
233 vs internal). Allocate the label in RESULT-POOL. */
234 static const char *
diff_label(const char * path,svn_revnum_t revnum,apr_pool_t * result_pool)235 diff_label(const char *path,
236 svn_revnum_t revnum,
237 apr_pool_t *result_pool)
238 {
239 const char *label;
240 if (revnum >= 0)
241 label = apr_psprintf(result_pool, _("%s\t(revision %ld)"), path, revnum);
242 else if (revnum == DIFF_REVNUM_NONEXISTENT)
243 label = apr_psprintf(result_pool, _("%s\t(nonexistent)"), path);
244 else /* SVN_INVALID_REVNUM */
245 label = apr_psprintf(result_pool, _("%s\t(working copy)"), path);
246
247 return label;
248 }
249
250 /* Standard modes produced in git style diffs */
251 static const int exec_mode = 0755;
252 static const int noexec_mode = 0644;
253 static const int kind_file_mode = 0100000;
254 /*static const kind_dir_mode = 0040000;*/
255 static const int kind_symlink_mode = 0120000;
256
257 /* Print a git diff header for an addition within a diff between PATH1 and
258 * PATH2 to the stream OS using HEADER_ENCODING. */
259 static svn_error_t *
print_git_diff_header_added(svn_stream_t * os,const char * header_encoding,const char * path1,const char * path2,svn_boolean_t exec_bit,svn_boolean_t symlink_bit,apr_pool_t * scratch_pool)260 print_git_diff_header_added(svn_stream_t *os, const char *header_encoding,
261 const char *path1, const char *path2,
262 svn_boolean_t exec_bit,
263 svn_boolean_t symlink_bit,
264 apr_pool_t *scratch_pool)
265 {
266 int new_mode = (exec_bit ? exec_mode : noexec_mode)
267 | (symlink_bit ? kind_symlink_mode : kind_file_mode);
268
269 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
270 "diff --git a/%s b/%s%s",
271 path1, path2, APR_EOL_STR));
272 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
273 "new file mode %06o" APR_EOL_STR,
274 new_mode));
275 return SVN_NO_ERROR;
276 }
277
278 /* Print a git diff header for a deletion within a diff between PATH1 and
279 * PATH2 to the stream OS using HEADER_ENCODING. */
280 static svn_error_t *
print_git_diff_header_deleted(svn_stream_t * os,const char * header_encoding,const char * path1,const char * path2,svn_boolean_t exec_bit,svn_boolean_t symlink_bit,apr_pool_t * scratch_pool)281 print_git_diff_header_deleted(svn_stream_t *os, const char *header_encoding,
282 const char *path1, const char *path2,
283 svn_boolean_t exec_bit,
284 svn_boolean_t symlink_bit,
285 apr_pool_t *scratch_pool)
286 {
287 int old_mode = (exec_bit ? exec_mode : noexec_mode)
288 | (symlink_bit ? kind_symlink_mode : kind_file_mode);
289 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
290 "diff --git a/%s b/%s%s",
291 path1, path2, APR_EOL_STR));
292 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
293 "deleted file mode %06o" APR_EOL_STR,
294 old_mode));
295 return SVN_NO_ERROR;
296 }
297
298 /* Print a git diff header for a copy from COPYFROM_PATH to PATH to the stream
299 * OS using HEADER_ENCODING. */
300 static svn_error_t *
print_git_diff_header_copied(svn_stream_t * os,const char * header_encoding,const char * copyfrom_path,svn_revnum_t copyfrom_rev,const char * path,apr_pool_t * scratch_pool)301 print_git_diff_header_copied(svn_stream_t *os, const char *header_encoding,
302 const char *copyfrom_path,
303 svn_revnum_t copyfrom_rev,
304 const char *path,
305 apr_pool_t *scratch_pool)
306 {
307 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
308 "diff --git a/%s b/%s%s",
309 copyfrom_path, path, APR_EOL_STR));
310 if (copyfrom_rev != SVN_INVALID_REVNUM)
311 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
312 "copy from %s@%ld%s", copyfrom_path,
313 copyfrom_rev, APR_EOL_STR));
314 else
315 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
316 "copy from %s%s", copyfrom_path,
317 APR_EOL_STR));
318 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
319 "copy to %s%s", path, APR_EOL_STR));
320 return SVN_NO_ERROR;
321 }
322
323 /* Print a git diff header for a rename from COPYFROM_PATH to PATH to the
324 * stream OS using HEADER_ENCODING. */
325 static svn_error_t *
print_git_diff_header_renamed(svn_stream_t * os,const char * header_encoding,const char * copyfrom_path,const char * path,apr_pool_t * scratch_pool)326 print_git_diff_header_renamed(svn_stream_t *os, const char *header_encoding,
327 const char *copyfrom_path, const char *path,
328 apr_pool_t *scratch_pool)
329 {
330 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
331 "diff --git a/%s b/%s%s",
332 copyfrom_path, path, APR_EOL_STR));
333 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
334 "rename from %s%s", copyfrom_path,
335 APR_EOL_STR));
336 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
337 "rename to %s%s", path, APR_EOL_STR));
338 return SVN_NO_ERROR;
339 }
340
341 /* Print a git diff header for a modification within a diff between PATH1 and
342 * PATH2 to the stream OS using HEADER_ENCODING. */
343 static svn_error_t *
print_git_diff_header_modified(svn_stream_t * os,const char * header_encoding,const char * path1,const char * path2,apr_pool_t * scratch_pool)344 print_git_diff_header_modified(svn_stream_t *os, const char *header_encoding,
345 const char *path1, const char *path2,
346 apr_pool_t *scratch_pool)
347 {
348 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
349 "diff --git a/%s b/%s%s",
350 path1, path2, APR_EOL_STR));
351 return SVN_NO_ERROR;
352 }
353
354 /* Helper function for print_git_diff_header */
355 static svn_error_t *
maybe_print_mode_change(svn_stream_t * os,const char * header_encoding,svn_boolean_t exec_bit1,svn_boolean_t exec_bit2,svn_boolean_t symlink_bit1,svn_boolean_t symlink_bit2,const char * git_index_shas,apr_pool_t * scratch_pool)356 maybe_print_mode_change(svn_stream_t *os,
357 const char *header_encoding,
358 svn_boolean_t exec_bit1,
359 svn_boolean_t exec_bit2,
360 svn_boolean_t symlink_bit1,
361 svn_boolean_t symlink_bit2,
362 const char *git_index_shas,
363 apr_pool_t *scratch_pool)
364 {
365 int old_mode = (exec_bit1 ? exec_mode : noexec_mode)
366 | (symlink_bit1 ? kind_symlink_mode : kind_file_mode);
367 int new_mode = (exec_bit2 ? exec_mode : noexec_mode)
368 | (symlink_bit2 ? kind_symlink_mode : kind_file_mode);
369 if (old_mode == new_mode)
370 {
371 if (git_index_shas)
372 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
373 "index %s %06o" APR_EOL_STR,
374 git_index_shas, old_mode));
375 return SVN_NO_ERROR;
376 }
377
378 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
379 "old mode %06o" APR_EOL_STR, old_mode));
380 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
381 "new mode %06o" APR_EOL_STR, new_mode));
382 return SVN_NO_ERROR;
383 }
384
385 /* Print a git diff header showing the OPERATION to the stream OS using
386 * HEADER_ENCODING. Return suitable diff labels for the git diff in *LABEL1
387 * and *LABEL2. REPOS_RELPATH1 and REPOS_RELPATH2 are relative to reposroot.
388 * are the paths passed to the original diff command. REV1 and REV2 are
389 * revisions being diffed. COPYFROM_PATH and COPYFROM_REV indicate where the
390 * diffed item was copied from.
391 * Use SCRATCH_POOL for temporary allocations. */
392 static svn_error_t *
print_git_diff_header(svn_stream_t * os,const char ** label1,const char ** label2,svn_diff_operation_kind_t operation,const char * repos_relpath1,const char * repos_relpath2,svn_revnum_t rev1,svn_revnum_t rev2,const char * copyfrom_path,svn_revnum_t copyfrom_rev,apr_hash_t * left_props,apr_hash_t * right_props,const char * git_index_shas,const char * header_encoding,apr_pool_t * scratch_pool)393 print_git_diff_header(svn_stream_t *os,
394 const char **label1, const char **label2,
395 svn_diff_operation_kind_t operation,
396 const char *repos_relpath1,
397 const char *repos_relpath2,
398 svn_revnum_t rev1,
399 svn_revnum_t rev2,
400 const char *copyfrom_path,
401 svn_revnum_t copyfrom_rev,
402 apr_hash_t *left_props,
403 apr_hash_t *right_props,
404 const char *git_index_shas,
405 const char *header_encoding,
406 apr_pool_t *scratch_pool)
407 {
408 svn_boolean_t exec_bit1 = (svn_prop_get_value(left_props,
409 SVN_PROP_EXECUTABLE) != NULL);
410 svn_boolean_t exec_bit2 = (svn_prop_get_value(right_props,
411 SVN_PROP_EXECUTABLE) != NULL);
412 svn_boolean_t symlink_bit1 = (svn_prop_get_value(left_props,
413 SVN_PROP_SPECIAL) != NULL);
414 svn_boolean_t symlink_bit2 = (svn_prop_get_value(right_props,
415 SVN_PROP_SPECIAL) != NULL);
416
417 if (operation == svn_diff_op_deleted)
418 {
419 SVN_ERR(print_git_diff_header_deleted(os, header_encoding,
420 repos_relpath1, repos_relpath2,
421 exec_bit1, symlink_bit1,
422 scratch_pool));
423 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
424 rev1, scratch_pool);
425 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
426 rev2, scratch_pool);
427
428 }
429 else if (operation == svn_diff_op_copied)
430 {
431 SVN_ERR(print_git_diff_header_copied(os, header_encoding,
432 copyfrom_path, copyfrom_rev,
433 repos_relpath2,
434 scratch_pool));
435 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
436 rev1, scratch_pool);
437 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
438 rev2, scratch_pool);
439 SVN_ERR(maybe_print_mode_change(os, header_encoding,
440 exec_bit1, exec_bit2,
441 symlink_bit1, symlink_bit2,
442 git_index_shas,
443 scratch_pool));
444 }
445 else if (operation == svn_diff_op_added)
446 {
447 SVN_ERR(print_git_diff_header_added(os, header_encoding,
448 repos_relpath1, repos_relpath2,
449 exec_bit2, symlink_bit2,
450 scratch_pool));
451 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
452 rev1, scratch_pool);
453 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
454 rev2, scratch_pool);
455 }
456 else if (operation == svn_diff_op_modified)
457 {
458 SVN_ERR(print_git_diff_header_modified(os, header_encoding,
459 repos_relpath1, repos_relpath2,
460 scratch_pool));
461 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
462 rev1, scratch_pool);
463 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
464 rev2, scratch_pool);
465 SVN_ERR(maybe_print_mode_change(os, header_encoding,
466 exec_bit1, exec_bit2,
467 symlink_bit1, symlink_bit2,
468 git_index_shas,
469 scratch_pool));
470 }
471 else if (operation == svn_diff_op_moved)
472 {
473 SVN_ERR(print_git_diff_header_renamed(os, header_encoding,
474 copyfrom_path, repos_relpath2,
475 scratch_pool));
476 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
477 rev1, scratch_pool);
478 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
479 rev2, scratch_pool);
480 SVN_ERR(maybe_print_mode_change(os, header_encoding,
481 exec_bit1, exec_bit2,
482 symlink_bit1, symlink_bit2,
483 git_index_shas,
484 scratch_pool));
485 }
486
487 return SVN_NO_ERROR;
488 }
489
490 /* A helper func that writes out verbal descriptions of property diffs
491 to OUTSTREAM. Of course, OUTSTREAM will probably be whatever was
492 passed to svn_client_diff6(), which is probably stdout.
493
494 ### FIXME needs proper docstring
495
496 If USE_GIT_DIFF_FORMAT is TRUE, pring git diff headers, which always
497 show paths relative to the repository root. RA_SESSION and WC_CTX are
498 needed to normalize paths relative the repository root, and are ignored
499 if USE_GIT_DIFF_FORMAT is FALSE.
500
501 ANCHOR is the local path where the diff editor is anchored. */
502 static svn_error_t *
display_prop_diffs(const apr_array_header_t * propchanges,apr_hash_t * left_props,apr_hash_t * right_props,const char * diff_relpath,const char * anchor,const char * orig_path1,const char * orig_path2,svn_revnum_t rev1,svn_revnum_t rev2,const char * encoding,svn_stream_t * outstream,const char * relative_to_dir,svn_boolean_t show_diff_header,svn_boolean_t use_git_diff_format,const char * ra_session_relpath,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_context_t * wc_ctx,apr_pool_t * scratch_pool)503 display_prop_diffs(const apr_array_header_t *propchanges,
504 apr_hash_t *left_props,
505 apr_hash_t *right_props,
506 const char *diff_relpath,
507 const char *anchor,
508 const char *orig_path1,
509 const char *orig_path2,
510 svn_revnum_t rev1,
511 svn_revnum_t rev2,
512 const char *encoding,
513 svn_stream_t *outstream,
514 const char *relative_to_dir,
515 svn_boolean_t show_diff_header,
516 svn_boolean_t use_git_diff_format,
517 const char *ra_session_relpath,
518 svn_cancel_func_t cancel_func,
519 void *cancel_baton,
520 svn_wc_context_t *wc_ctx,
521 apr_pool_t *scratch_pool)
522 {
523 const char *repos_relpath1 = NULL;
524 const char *repos_relpath2 = NULL;
525 const char *index_path = diff_relpath;
526 const char *adjusted_path1 = orig_path1;
527 const char *adjusted_path2 = orig_path2;
528
529 if (use_git_diff_format)
530 {
531 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, orig_path1,
532 ra_session_relpath, wc_ctx, anchor,
533 scratch_pool, scratch_pool));
534 SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, orig_path2,
535 ra_session_relpath, wc_ctx, anchor,
536 scratch_pool, scratch_pool));
537 }
538
539 /* If we're creating a diff on the wc root, path would be empty. */
540 SVN_ERR(adjust_paths_for_diff_labels(&index_path, &adjusted_path1,
541 &adjusted_path2,
542 relative_to_dir, anchor,
543 scratch_pool, scratch_pool));
544
545 if (show_diff_header)
546 {
547 const char *label1;
548 const char *label2;
549
550 label1 = diff_label(adjusted_path1, rev1, scratch_pool);
551 label2 = diff_label(adjusted_path2, rev2, scratch_pool);
552
553 /* ### Should we show the paths in platform specific format,
554 * ### diff_content_changed() does not! */
555
556 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
557 "Index: %s" APR_EOL_STR
558 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
559 index_path));
560
561 if (use_git_diff_format)
562 SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
563 svn_diff_op_modified,
564 repos_relpath1, repos_relpath2,
565 rev1, rev2, NULL,
566 SVN_INVALID_REVNUM,
567 left_props,
568 right_props,
569 NULL,
570 encoding, scratch_pool));
571
572 /* --- label1
573 * +++ label2 */
574 SVN_ERR(svn_diff__unidiff_write_header(
575 outstream, encoding, label1, label2, scratch_pool));
576 }
577
578 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
579 APR_EOL_STR
580 "Property changes on: %s"
581 APR_EOL_STR,
582 use_git_diff_format
583 ? repos_relpath1
584 : index_path));
585
586 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
587 SVN_DIFF__UNDER_STRING APR_EOL_STR));
588
589 SVN_ERR(svn_diff__display_prop_diffs(
590 outstream, encoding, propchanges, left_props,
591 TRUE /* pretty_print_mergeinfo */,
592 -1 /* context_size */,
593 cancel_func, cancel_baton, scratch_pool));
594
595 return SVN_NO_ERROR;
596 }
597
598 /*-----------------------------------------------------------------*/
599
600 /*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/
601
602 /* State provided by the diff drivers; used by the diff writer */
603 typedef struct diff_driver_info_t
604 {
605 /* The anchor to prefix before wc paths */
606 const char *anchor;
607
608 /* Relative path of ra session from repos_root_url */
609 const char *session_relpath;
610
611 /* The original targets passed to the diff command. We may need
612 these to construct distinctive diff labels when comparing the
613 same relative path in the same revision, under different anchors
614 (for example, when comparing a trunk against a branch). */
615 const char *orig_path_1;
616 const char *orig_path_2;
617 } diff_driver_info_t;
618
619
620 /* Diff writer state */
621 typedef struct diff_writer_info_t
622 {
623 /* If non-null, the external diff command to invoke. */
624 const char *diff_cmd;
625
626 /* This is allocated in this struct's pool or a higher-up pool. */
627 union {
628 /* If 'diff_cmd' is null, then this is the parsed options to
629 pass to the internal libsvn_diff implementation. */
630 svn_diff_file_options_t *for_internal;
631 /* Else if 'diff_cmd' is non-null, then... */
632 struct {
633 /* ...this is an argument array for the external command, and */
634 const char **argv;
635 /* ...this is the length of argv. */
636 int argc;
637 } for_external;
638 } options;
639
640 apr_pool_t *pool;
641 svn_stream_t *outstream;
642 svn_stream_t *errstream;
643
644 const char *header_encoding;
645
646 /* Set this if you want diff output even for binary files. */
647 svn_boolean_t force_binary;
648
649 /* The directory that diff target paths should be considered as
650 relative to for output generation (see issue #2723). */
651 const char *relative_to_dir;
652
653 /* Whether property differences are ignored. */
654 svn_boolean_t ignore_properties;
655
656 /* Whether to show only property changes. */
657 svn_boolean_t properties_only;
658
659 /* Whether we're producing a git-style diff. */
660 svn_boolean_t use_git_diff_format;
661
662 /* Whether addition of a file is summarized versus showing a full diff. */
663 svn_boolean_t no_diff_added;
664
665 /* Whether deletion of a file is summarized versus showing a full diff. */
666 svn_boolean_t no_diff_deleted;
667
668 /* Whether to ignore copyfrom information when showing adds */
669 svn_boolean_t show_copies_as_adds;
670
671 /* Empty files for creating diffs or NULL if not used yet */
672 const char *empty_file;
673
674 svn_wc_context_t *wc_ctx;
675
676 svn_cancel_func_t cancel_func;
677 void *cancel_baton;
678
679 struct diff_driver_info_t ddi;
680 } diff_writer_info_t;
681
682 /* An helper for diff_dir_props_changed, diff_file_changed and diff_file_added
683 */
684 static svn_error_t *
diff_props_changed(const char * diff_relpath,svn_revnum_t rev1,svn_revnum_t rev2,const apr_array_header_t * propchanges,apr_hash_t * left_props,apr_hash_t * right_props,svn_boolean_t show_diff_header,diff_writer_info_t * dwi,apr_pool_t * scratch_pool)685 diff_props_changed(const char *diff_relpath,
686 svn_revnum_t rev1,
687 svn_revnum_t rev2,
688 const apr_array_header_t *propchanges,
689 apr_hash_t *left_props,
690 apr_hash_t *right_props,
691 svn_boolean_t show_diff_header,
692 diff_writer_info_t *dwi,
693 apr_pool_t *scratch_pool)
694 {
695 apr_array_header_t *props;
696
697 /* If property differences are ignored, there's nothing to do. */
698 if (dwi->ignore_properties)
699 return SVN_NO_ERROR;
700
701 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props,
702 scratch_pool));
703
704 if (props->nelts > 0)
705 {
706 /* We're using the revnums from the dwi since there's
707 * no revision argument to the svn_wc_diff_callback_t
708 * dir_props_changed(). */
709 SVN_ERR(display_prop_diffs(props, left_props, right_props,
710 diff_relpath,
711 dwi->ddi.anchor,
712 dwi->ddi.orig_path_1,
713 dwi->ddi.orig_path_2,
714 rev1,
715 rev2,
716 dwi->header_encoding,
717 dwi->outstream,
718 dwi->relative_to_dir,
719 show_diff_header,
720 dwi->use_git_diff_format,
721 dwi->ddi.session_relpath,
722 dwi->cancel_func,
723 dwi->cancel_baton,
724 dwi->wc_ctx,
725 scratch_pool));
726 }
727
728 return SVN_NO_ERROR;
729 }
730
731 /* Given a file ORIG_TMPFILE, return a path to a temporary file that lives at
732 * least as long as RESULT_POOL, containing the git-like represention of
733 * ORIG_TMPFILE */
734 static svn_error_t *
transform_link_to_git(const char ** new_tmpfile,const char ** git_sha1,const char * orig_tmpfile,apr_pool_t * result_pool,apr_pool_t * scratch_pool)735 transform_link_to_git(const char **new_tmpfile,
736 const char **git_sha1,
737 const char *orig_tmpfile,
738 apr_pool_t *result_pool,
739 apr_pool_t *scratch_pool)
740 {
741 apr_file_t *orig;
742 apr_file_t *gitlike;
743 svn_stringbuf_t *line;
744
745 *git_sha1 = NULL;
746
747 SVN_ERR(svn_io_file_open(&orig, orig_tmpfile, APR_READ, APR_OS_DEFAULT,
748 scratch_pool));
749 SVN_ERR(svn_io_open_unique_file3(&gitlike, new_tmpfile, NULL,
750 svn_io_file_del_on_pool_cleanup,
751 result_pool, scratch_pool));
752
753 SVN_ERR(svn_io_file_readline(orig, &line, NULL, NULL, 2 * APR_PATH_MAX + 2,
754 scratch_pool, scratch_pool));
755
756 if (line->len > 5 && !strncmp(line->data, "link ", 5))
757 {
758 const char *sz_str;
759 svn_checksum_t *checksum;
760
761 svn_stringbuf_remove(line, 0, 5);
762
763 SVN_ERR(svn_io_file_write_full(gitlike, line->data, line->len,
764 NULL, scratch_pool));
765
766 /* git calculates the sha over "blob X\0" + the actual data,
767 where X is the decimal size of the blob. */
768 sz_str = apr_psprintf(scratch_pool, "blob %u", (unsigned int)line->len);
769 svn_stringbuf_insert(line, 0, sz_str, strlen(sz_str) + 1);
770
771 SVN_ERR(svn_checksum(&checksum, svn_checksum_sha1,
772 line->data, line->len, scratch_pool));
773
774 *git_sha1 = svn_checksum_to_cstring(checksum, result_pool);
775 }
776 else
777 {
778 /* Not a link... so can't convert */
779 *new_tmpfile = apr_pstrdup(result_pool, orig_tmpfile);
780 }
781
782 SVN_ERR(svn_io_file_close(orig, scratch_pool));
783 SVN_ERR(svn_io_file_close(gitlike, scratch_pool));
784 return SVN_NO_ERROR;
785 }
786
787 /* Show differences between TMPFILE1 and TMPFILE2. DIFF_RELPATH, REV1, and
788 REV2 are used in the headers to indicate the file and revisions. If either
789 MIMETYPE1 or MIMETYPE2 indicate binary content, don't show a diff,
790 but instead print a warning message.
791
792 If FORCE_DIFF is TRUE, always write a diff, even for empty diffs.
793
794 Set *WROTE_HEADER to TRUE if a diff header was written */
795 static svn_error_t *
diff_content_changed(svn_boolean_t * wrote_header,const char * diff_relpath,const char * tmpfile1,const char * tmpfile2,svn_revnum_t rev1,svn_revnum_t rev2,apr_hash_t * left_props,apr_hash_t * right_props,svn_diff_operation_kind_t operation,svn_boolean_t force_diff,const char * copyfrom_path,svn_revnum_t copyfrom_rev,diff_writer_info_t * dwi,apr_pool_t * scratch_pool)796 diff_content_changed(svn_boolean_t *wrote_header,
797 const char *diff_relpath,
798 const char *tmpfile1,
799 const char *tmpfile2,
800 svn_revnum_t rev1,
801 svn_revnum_t rev2,
802 apr_hash_t *left_props,
803 apr_hash_t *right_props,
804 svn_diff_operation_kind_t operation,
805 svn_boolean_t force_diff,
806 const char *copyfrom_path,
807 svn_revnum_t copyfrom_rev,
808 diff_writer_info_t *dwi,
809 apr_pool_t *scratch_pool)
810 {
811 const char *rel_to_dir = dwi->relative_to_dir;
812 svn_stream_t *outstream = dwi->outstream;
813 const char *label1, *label2;
814 svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE;
815 const char *index_path = diff_relpath;
816 const char *path1 = dwi->ddi.orig_path_1;
817 const char *path2 = dwi->ddi.orig_path_2;
818 const char *mimetype1 = svn_prop_get_value(left_props, SVN_PROP_MIME_TYPE);
819 const char *mimetype2 = svn_prop_get_value(right_props, SVN_PROP_MIME_TYPE);
820 const char *index_shas = NULL;
821
822 /* If only property differences are shown, there's nothing to do. */
823 if (dwi->properties_only)
824 return SVN_NO_ERROR;
825
826 /* Generate the diff headers. */
827 SVN_ERR(adjust_paths_for_diff_labels(&index_path, &path1, &path2,
828 rel_to_dir, dwi->ddi.anchor,
829 scratch_pool, scratch_pool));
830
831 label1 = diff_label(path1, rev1, scratch_pool);
832 label2 = diff_label(path2, rev2, scratch_pool);
833
834 /* Possible easy-out: if either mime-type is binary and force was not
835 specified, don't attempt to generate a viewable diff at all.
836 Print a warning and exit. */
837 if (mimetype1)
838 mt1_binary = svn_mime_type_is_binary(mimetype1);
839 if (mimetype2)
840 mt2_binary = svn_mime_type_is_binary(mimetype2);
841
842 if (dwi->use_git_diff_format)
843 {
844 const char *l_hash = NULL;
845 const char *r_hash = NULL;
846
847 /* Change symlinks to their 'git like' plain format */
848 if (svn_prop_get_value(left_props, SVN_PROP_SPECIAL))
849 SVN_ERR(transform_link_to_git(&tmpfile1, &l_hash, tmpfile1,
850 scratch_pool, scratch_pool));
851 if (svn_prop_get_value(right_props, SVN_PROP_SPECIAL))
852 SVN_ERR(transform_link_to_git(&tmpfile2, &r_hash, tmpfile2,
853 scratch_pool, scratch_pool));
854
855 if (l_hash && r_hash)
856 {
857 /* The symlink has changed. But we can't tell the user of the
858 diff whether we are writing git diffs or svn diffs of the
859 symlink... except when we add a git-like index line */
860
861 l_hash = apr_pstrndup(scratch_pool, l_hash, 8);
862 r_hash = apr_pstrndup(scratch_pool, r_hash, 8);
863
864 index_shas = apr_psprintf(scratch_pool, "%8s..%8s",
865 l_hash, r_hash);
866 }
867 }
868
869 if (! dwi->force_binary && (mt1_binary || mt2_binary))
870 {
871 /* Print out the diff header. */
872 SVN_ERR(svn_stream_printf_from_utf8(outstream,
873 dwi->header_encoding, scratch_pool,
874 "Index: %s" APR_EOL_STR
875 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
876 index_path));
877
878 *wrote_header = TRUE;
879
880 /* ### Print git diff headers. */
881
882 if (dwi->use_git_diff_format)
883 {
884 svn_stream_t *left_stream;
885 svn_stream_t *right_stream;
886 const char *repos_relpath1;
887 const char *repos_relpath2;
888 const char *copyfrom_repos_relpath = NULL;
889
890 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
891 dwi->ddi.orig_path_1,
892 dwi->ddi.session_relpath,
893 dwi->wc_ctx,
894 dwi->ddi.anchor,
895 scratch_pool, scratch_pool));
896 SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
897 dwi->ddi.orig_path_2,
898 dwi->ddi.session_relpath,
899 dwi->wc_ctx,
900 dwi->ddi.anchor,
901 scratch_pool, scratch_pool));
902 if (copyfrom_path)
903 SVN_ERR(make_repos_relpath(©from_repos_relpath, copyfrom_path,
904 dwi->ddi.orig_path_2,
905 dwi->ddi.session_relpath,
906 dwi->wc_ctx,
907 dwi->ddi.anchor,
908 scratch_pool, scratch_pool));
909 SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
910 operation,
911 repos_relpath1, repos_relpath2,
912 rev1, rev2,
913 copyfrom_repos_relpath,
914 copyfrom_rev,
915 left_props,
916 right_props,
917 index_shas,
918 dwi->header_encoding,
919 scratch_pool));
920
921 SVN_ERR(svn_stream_open_readonly(&left_stream, tmpfile1,
922 scratch_pool, scratch_pool));
923 SVN_ERR(svn_stream_open_readonly(&right_stream, tmpfile2,
924 scratch_pool, scratch_pool));
925 SVN_ERR(svn_diff_output_binary(outstream,
926 left_stream, right_stream,
927 dwi->cancel_func, dwi->cancel_baton,
928 scratch_pool));
929 }
930 else
931 {
932 SVN_ERR(svn_stream_printf_from_utf8(outstream,
933 dwi->header_encoding, scratch_pool,
934 _("Cannot display: file marked as a binary type.%s"),
935 APR_EOL_STR));
936
937 if (mt1_binary && !mt2_binary)
938 SVN_ERR(svn_stream_printf_from_utf8(outstream,
939 dwi->header_encoding, scratch_pool,
940 "svn:mime-type = %s" APR_EOL_STR, mimetype1));
941 else if (mt2_binary && !mt1_binary)
942 SVN_ERR(svn_stream_printf_from_utf8(outstream,
943 dwi->header_encoding, scratch_pool,
944 "svn:mime-type = %s" APR_EOL_STR, mimetype2));
945 else if (mt1_binary && mt2_binary)
946 {
947 if (strcmp(mimetype1, mimetype2) == 0)
948 SVN_ERR(svn_stream_printf_from_utf8(outstream,
949 dwi->header_encoding, scratch_pool,
950 "svn:mime-type = %s" APR_EOL_STR,
951 mimetype1));
952 else
953 SVN_ERR(svn_stream_printf_from_utf8(outstream,
954 dwi->header_encoding, scratch_pool,
955 "svn:mime-type = (%s, %s)" APR_EOL_STR,
956 mimetype1, mimetype2));
957 }
958 }
959
960 /* Exit early. */
961 return SVN_NO_ERROR;
962 }
963
964
965 if (dwi->diff_cmd)
966 {
967 svn_stream_t *errstream = dwi->errstream;
968 apr_file_t *outfile;
969 apr_file_t *errfile;
970 const char *outfilename;
971 const char *errfilename;
972 svn_stream_t *stream;
973 int exitcode;
974
975 /* Print out the diff header. */
976 SVN_ERR(svn_stream_printf_from_utf8(outstream,
977 dwi->header_encoding, scratch_pool,
978 "Index: %s" APR_EOL_STR
979 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
980 index_path));
981
982 /* ### Do we want to add git diff headers here too? I'd say no. The
983 * ### 'Index' and '===' line is something subversion has added. The rest
984 * ### is up to the external diff application. We may be dealing with
985 * ### a non-git compatible diff application.*/
986
987 /* We deal in streams, but svn_io_run_diff2() deals in file handles,
988 so we may need to make temporary files and then copy the contents
989 to our stream. */
990 outfile = svn_stream__aprfile(outstream);
991 if (outfile)
992 outfilename = NULL;
993 else
994 SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
995 svn_io_file_del_on_pool_cleanup,
996 scratch_pool, scratch_pool));
997
998 errfile = svn_stream__aprfile(errstream);
999 if (errfile)
1000 errfilename = NULL;
1001 else
1002 SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
1003 svn_io_file_del_on_pool_cleanup,
1004 scratch_pool, scratch_pool));
1005
1006 SVN_ERR(svn_io_run_diff2(".",
1007 dwi->options.for_external.argv,
1008 dwi->options.for_external.argc,
1009 label1, label2,
1010 tmpfile1, tmpfile2,
1011 &exitcode, outfile, errfile,
1012 dwi->diff_cmd, scratch_pool));
1013
1014 /* Now, open and copy our files to our output streams. */
1015 if (outfilename)
1016 {
1017 SVN_ERR(svn_io_file_close(outfile, scratch_pool));
1018 SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
1019 scratch_pool, scratch_pool));
1020 SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(outstream,
1021 scratch_pool),
1022 NULL, NULL, scratch_pool));
1023 }
1024 if (errfilename)
1025 {
1026 SVN_ERR(svn_io_file_close(errfile, scratch_pool));
1027 SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
1028 scratch_pool, scratch_pool));
1029 SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(errstream,
1030 scratch_pool),
1031 NULL, NULL, scratch_pool));
1032 }
1033
1034 /* If we have printed a diff for this path, mark it as visited. */
1035 if (exitcode == 1)
1036 *wrote_header = TRUE;
1037 }
1038 else /* use libsvn_diff to generate the diff */
1039 {
1040 svn_diff_t *diff;
1041
1042 SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2,
1043 dwi->options.for_internal,
1044 scratch_pool));
1045
1046 if (force_diff
1047 || dwi->use_git_diff_format
1048 || svn_diff_contains_diffs(diff))
1049 {
1050 /* Print out the diff header. */
1051 SVN_ERR(svn_stream_printf_from_utf8(outstream,
1052 dwi->header_encoding, scratch_pool,
1053 "Index: %s" APR_EOL_STR
1054 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
1055 index_path));
1056
1057 if (dwi->use_git_diff_format)
1058 {
1059 const char *repos_relpath1;
1060 const char *repos_relpath2;
1061 const char *copyfrom_repos_relpath = NULL;
1062
1063 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
1064 dwi->ddi.orig_path_1,
1065 dwi->ddi.session_relpath,
1066 dwi->wc_ctx,
1067 dwi->ddi.anchor,
1068 scratch_pool, scratch_pool));
1069 SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
1070 dwi->ddi.orig_path_2,
1071 dwi->ddi.session_relpath,
1072 dwi->wc_ctx,
1073 dwi->ddi.anchor,
1074 scratch_pool, scratch_pool));
1075 if (copyfrom_path)
1076 SVN_ERR(make_repos_relpath(©from_repos_relpath,
1077 copyfrom_path,
1078 dwi->ddi.orig_path_2,
1079 dwi->ddi.session_relpath,
1080 dwi->wc_ctx,
1081 dwi->ddi.anchor,
1082 scratch_pool, scratch_pool));
1083 SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
1084 operation,
1085 repos_relpath1, repos_relpath2,
1086 rev1, rev2,
1087 copyfrom_repos_relpath,
1088 copyfrom_rev,
1089 left_props,
1090 right_props,
1091 index_shas,
1092 dwi->header_encoding,
1093 scratch_pool));
1094 }
1095
1096 /* Output the actual diff */
1097 if (force_diff || svn_diff_contains_diffs(diff))
1098 SVN_ERR(svn_diff_file_output_unified4(outstream, diff,
1099 tmpfile1, tmpfile2, label1, label2,
1100 dwi->header_encoding, rel_to_dir,
1101 dwi->options.for_internal->show_c_function,
1102 dwi->options.for_internal->context_size,
1103 dwi->cancel_func, dwi->cancel_baton,
1104 scratch_pool));
1105
1106 /* If we have printed a diff for this path, mark it as visited. */
1107 if (dwi->use_git_diff_format || svn_diff_contains_diffs(diff))
1108 *wrote_header = TRUE;
1109 }
1110 }
1111
1112 return SVN_NO_ERROR;
1113 }
1114
1115 /* An svn_diff_tree_processor_t callback. */
1116 static svn_error_t *
diff_file_changed(const char * relpath,const svn_diff_source_t * left_source,const svn_diff_source_t * right_source,const char * left_file,const char * right_file,apr_hash_t * left_props,apr_hash_t * right_props,svn_boolean_t file_modified,const apr_array_header_t * prop_changes,void * file_baton,const struct svn_diff_tree_processor_t * processor,apr_pool_t * scratch_pool)1117 diff_file_changed(const char *relpath,
1118 const svn_diff_source_t *left_source,
1119 const svn_diff_source_t *right_source,
1120 const char *left_file,
1121 const char *right_file,
1122 /*const*/ apr_hash_t *left_props,
1123 /*const*/ apr_hash_t *right_props,
1124 svn_boolean_t file_modified,
1125 const apr_array_header_t *prop_changes,
1126 void *file_baton,
1127 const struct svn_diff_tree_processor_t *processor,
1128 apr_pool_t *scratch_pool)
1129 {
1130 diff_writer_info_t *dwi = processor->baton;
1131 svn_boolean_t wrote_header = FALSE;
1132
1133 if (file_modified)
1134 SVN_ERR(diff_content_changed(&wrote_header, relpath,
1135 left_file, right_file,
1136 left_source->revision,
1137 right_source->revision,
1138 left_props, right_props,
1139 svn_diff_op_modified, FALSE,
1140 NULL,
1141 SVN_INVALID_REVNUM, dwi,
1142 scratch_pool));
1143 if (prop_changes->nelts > 0)
1144 SVN_ERR(diff_props_changed(relpath,
1145 left_source->revision,
1146 right_source->revision, prop_changes,
1147 left_props, right_props, !wrote_header,
1148 dwi, scratch_pool));
1149 return SVN_NO_ERROR;
1150 }
1151
1152 /* Because the repos-diff editor passes at least one empty file to
1153 each of these next two functions, they can be dumb wrappers around
1154 the main workhorse routine. */
1155
1156 /* An svn_diff_tree_processor_t callback. */
1157 static svn_error_t *
diff_file_added(const char * relpath,const svn_diff_source_t * copyfrom_source,const svn_diff_source_t * right_source,const char * copyfrom_file,const char * right_file,apr_hash_t * copyfrom_props,apr_hash_t * right_props,void * file_baton,const struct svn_diff_tree_processor_t * processor,apr_pool_t * scratch_pool)1158 diff_file_added(const char *relpath,
1159 const svn_diff_source_t *copyfrom_source,
1160 const svn_diff_source_t *right_source,
1161 const char *copyfrom_file,
1162 const char *right_file,
1163 /*const*/ apr_hash_t *copyfrom_props,
1164 /*const*/ apr_hash_t *right_props,
1165 void *file_baton,
1166 const struct svn_diff_tree_processor_t *processor,
1167 apr_pool_t *scratch_pool)
1168 {
1169 diff_writer_info_t *dwi = processor->baton;
1170 svn_boolean_t wrote_header = FALSE;
1171 const char *left_file;
1172 apr_hash_t *left_props;
1173 apr_array_header_t *prop_changes;
1174
1175 if (dwi->no_diff_added)
1176 {
1177 const char *index_path = relpath;
1178
1179 if (dwi->ddi.anchor)
1180 index_path = svn_dirent_join(dwi->ddi.anchor, relpath,
1181 scratch_pool);
1182
1183 SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream,
1184 dwi->header_encoding, scratch_pool,
1185 "Index: %s (added)" APR_EOL_STR
1186 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
1187 index_path));
1188 wrote_header = TRUE;
1189 return SVN_NO_ERROR;
1190 }
1191
1192 /* During repos->wc diff of a copy revision numbers obtained
1193 * from the working copy are always SVN_INVALID_REVNUM. */
1194 if (copyfrom_source && !dwi->show_copies_as_adds)
1195 {
1196 left_file = copyfrom_file;
1197 left_props = copyfrom_props ? copyfrom_props : apr_hash_make(scratch_pool);
1198 }
1199 else
1200 {
1201 if (!dwi->empty_file)
1202 SVN_ERR(svn_io_open_unique_file3(NULL, &dwi->empty_file,
1203 NULL, svn_io_file_del_on_pool_cleanup,
1204 dwi->pool, scratch_pool));
1205
1206 left_file = dwi->empty_file;
1207 left_props = apr_hash_make(scratch_pool);
1208
1209 copyfrom_source = NULL;
1210 copyfrom_file = NULL;
1211 }
1212
1213 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool));
1214
1215 if (copyfrom_source && right_file)
1216 SVN_ERR(diff_content_changed(&wrote_header, relpath,
1217 left_file, right_file,
1218 copyfrom_source->revision,
1219 right_source->revision,
1220 left_props, right_props,
1221 copyfrom_source->moved_from_relpath
1222 ? svn_diff_op_moved
1223 : svn_diff_op_copied,
1224 TRUE /* force diff output */,
1225 copyfrom_source->moved_from_relpath
1226 ? copyfrom_source->moved_from_relpath
1227 : copyfrom_source->repos_relpath,
1228 copyfrom_source->revision,
1229 dwi, scratch_pool));
1230 else if (right_file)
1231 SVN_ERR(diff_content_changed(&wrote_header, relpath,
1232 left_file, right_file,
1233 DIFF_REVNUM_NONEXISTENT,
1234 right_source->revision,
1235 left_props, right_props,
1236 svn_diff_op_added,
1237 TRUE /* force diff output */,
1238 NULL, SVN_INVALID_REVNUM,
1239 dwi, scratch_pool));
1240
1241 if (prop_changes->nelts > 0)
1242 SVN_ERR(diff_props_changed(relpath,
1243 copyfrom_source ? copyfrom_source->revision
1244 : DIFF_REVNUM_NONEXISTENT,
1245 right_source->revision,
1246 prop_changes,
1247 left_props, right_props,
1248 ! wrote_header, dwi, scratch_pool));
1249
1250 return SVN_NO_ERROR;
1251 }
1252
1253 /* An svn_diff_tree_processor_t callback. */
1254 static svn_error_t *
diff_file_deleted(const char * relpath,const svn_diff_source_t * left_source,const char * left_file,apr_hash_t * left_props,void * file_baton,const struct svn_diff_tree_processor_t * processor,apr_pool_t * scratch_pool)1255 diff_file_deleted(const char *relpath,
1256 const svn_diff_source_t *left_source,
1257 const char *left_file,
1258 /*const*/ apr_hash_t *left_props,
1259 void *file_baton,
1260 const struct svn_diff_tree_processor_t *processor,
1261 apr_pool_t *scratch_pool)
1262 {
1263 diff_writer_info_t *dwi = processor->baton;
1264
1265 if (dwi->no_diff_deleted)
1266 {
1267 const char *index_path = relpath;
1268
1269 if (dwi->ddi.anchor)
1270 index_path = svn_dirent_join(dwi->ddi.anchor, relpath,
1271 scratch_pool);
1272
1273 SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream,
1274 dwi->header_encoding, scratch_pool,
1275 "Index: %s (deleted)" APR_EOL_STR
1276 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
1277 index_path));
1278 }
1279 else
1280 {
1281 svn_boolean_t wrote_header = FALSE;
1282
1283 if (!dwi->empty_file)
1284 SVN_ERR(svn_io_open_unique_file3(NULL, &dwi->empty_file,
1285 NULL, svn_io_file_del_on_pool_cleanup,
1286 dwi->pool, scratch_pool));
1287
1288 if (left_file)
1289 SVN_ERR(diff_content_changed(&wrote_header, relpath,
1290 left_file, dwi->empty_file,
1291 left_source->revision,
1292 DIFF_REVNUM_NONEXISTENT,
1293 left_props,
1294 NULL,
1295 svn_diff_op_deleted, FALSE,
1296 NULL, SVN_INVALID_REVNUM,
1297 dwi,
1298 scratch_pool));
1299
1300 if (left_props && apr_hash_count(left_props))
1301 {
1302 apr_array_header_t *prop_changes;
1303
1304 SVN_ERR(svn_prop_diffs(&prop_changes, apr_hash_make(scratch_pool),
1305 left_props, scratch_pool));
1306
1307 SVN_ERR(diff_props_changed(relpath,
1308 left_source->revision,
1309 DIFF_REVNUM_NONEXISTENT,
1310 prop_changes,
1311 left_props, NULL,
1312 ! wrote_header, dwi, scratch_pool));
1313 }
1314 }
1315
1316 return SVN_NO_ERROR;
1317 }
1318
1319 /* An svn_wc_diff_callbacks4_t function. */
1320 static svn_error_t *
diff_dir_changed(const char * relpath,const svn_diff_source_t * left_source,const svn_diff_source_t * right_source,apr_hash_t * left_props,apr_hash_t * right_props,const apr_array_header_t * prop_changes,void * dir_baton,const struct svn_diff_tree_processor_t * processor,apr_pool_t * scratch_pool)1321 diff_dir_changed(const char *relpath,
1322 const svn_diff_source_t *left_source,
1323 const svn_diff_source_t *right_source,
1324 /*const*/ apr_hash_t *left_props,
1325 /*const*/ apr_hash_t *right_props,
1326 const apr_array_header_t *prop_changes,
1327 void *dir_baton,
1328 const struct svn_diff_tree_processor_t *processor,
1329 apr_pool_t *scratch_pool)
1330 {
1331 diff_writer_info_t *dwi = processor->baton;
1332
1333 SVN_ERR(diff_props_changed(relpath,
1334 left_source->revision,
1335 right_source->revision,
1336 prop_changes,
1337 left_props, right_props,
1338 TRUE /* show_diff_header */,
1339 dwi,
1340 scratch_pool));
1341
1342 return SVN_NO_ERROR;
1343 }
1344
1345 /* An svn_diff_tree_processor_t callback. */
1346 static svn_error_t *
diff_dir_added(const char * relpath,const svn_diff_source_t * copyfrom_source,const svn_diff_source_t * right_source,apr_hash_t * copyfrom_props,apr_hash_t * right_props,void * dir_baton,const struct svn_diff_tree_processor_t * processor,apr_pool_t * scratch_pool)1347 diff_dir_added(const char *relpath,
1348 const svn_diff_source_t *copyfrom_source,
1349 const svn_diff_source_t *right_source,
1350 /*const*/ apr_hash_t *copyfrom_props,
1351 /*const*/ apr_hash_t *right_props,
1352 void *dir_baton,
1353 const struct svn_diff_tree_processor_t *processor,
1354 apr_pool_t *scratch_pool)
1355 {
1356 diff_writer_info_t *dwi = processor->baton;
1357 apr_hash_t *left_props;
1358 apr_array_header_t *prop_changes;
1359
1360 if (dwi->no_diff_added)
1361 return SVN_NO_ERROR;
1362
1363 if (copyfrom_source && !dwi->show_copies_as_adds)
1364 {
1365 left_props = copyfrom_props ? copyfrom_props
1366 : apr_hash_make(scratch_pool);
1367 }
1368 else
1369 {
1370 left_props = apr_hash_make(scratch_pool);
1371 copyfrom_source = NULL;
1372 }
1373
1374 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props,
1375 scratch_pool));
1376
1377 return svn_error_trace(diff_props_changed(relpath,
1378 copyfrom_source ? copyfrom_source->revision
1379 : DIFF_REVNUM_NONEXISTENT,
1380 right_source->revision,
1381 prop_changes,
1382 left_props, right_props,
1383 TRUE /* show_diff_header */,
1384 dwi,
1385 scratch_pool));
1386 }
1387
1388 /* An svn_diff_tree_processor_t callback. */
1389 static svn_error_t *
diff_dir_deleted(const char * relpath,const svn_diff_source_t * left_source,apr_hash_t * left_props,void * dir_baton,const struct svn_diff_tree_processor_t * processor,apr_pool_t * scratch_pool)1390 diff_dir_deleted(const char *relpath,
1391 const svn_diff_source_t *left_source,
1392 /*const*/ apr_hash_t *left_props,
1393 void *dir_baton,
1394 const struct svn_diff_tree_processor_t *processor,
1395 apr_pool_t *scratch_pool)
1396 {
1397 diff_writer_info_t *dwi = processor->baton;
1398 apr_array_header_t *prop_changes;
1399 apr_hash_t *right_props;
1400
1401 if (dwi->no_diff_deleted)
1402 return SVN_NO_ERROR;
1403
1404 right_props = apr_hash_make(scratch_pool);
1405 SVN_ERR(svn_prop_diffs(&prop_changes, right_props,
1406 left_props, scratch_pool));
1407
1408 SVN_ERR(diff_props_changed(relpath,
1409 left_source->revision,
1410 DIFF_REVNUM_NONEXISTENT,
1411 prop_changes,
1412 left_props, right_props,
1413 TRUE /* show_diff_header */,
1414 dwi,
1415 scratch_pool));
1416
1417 return SVN_NO_ERROR;
1418 }
1419
1420 /*-----------------------------------------------------------------*/
1421
1422 /** The logic behind 'svn diff' and 'svn merge'. */
1423
1424
1425 /* Hi! This is a comment left behind by Karl, and Ben is too afraid
1426 to erase it at this time, because he's not fully confident that all
1427 this knowledge has been grokked yet.
1428
1429 There are five cases:
1430 1. path is not a URL and start_revision != end_revision
1431 2. path is not a URL and start_revision == end_revision
1432 3. path is a URL and start_revision != end_revision
1433 4. path is a URL and start_revision == end_revision
1434 5. path is not a URL and no revisions given
1435
1436 With only one distinct revision the working copy provides the
1437 other. When path is a URL there is no working copy. Thus
1438
1439 1: compare repository versions for URL coresponding to working copy
1440 2: compare working copy against repository version
1441 3: compare repository versions for URL
1442 4: nothing to do.
1443 5: compare working copy against text-base
1444
1445 Case 4 is not as stupid as it looks, for example it may occur if
1446 the user specifies two dates that resolve to the same revision. */
1447
1448
1449 /** Check if paths PATH_OR_URL1 and PATH_OR_URL2 are urls and if the
1450 * revisions REVISION1 and REVISION2 are local. If PEG_REVISION is not
1451 * unspecified, ensure that at least one of the two revisions is not
1452 * BASE or WORKING.
1453 * If PATH_OR_URL1 can only be found in the repository, set *IS_REPOS1
1454 * to TRUE. If PATH_OR_URL2 can only be found in the repository, set
1455 * *IS_REPOS2 to TRUE. */
1456 static svn_error_t *
check_paths(svn_boolean_t * is_repos1,svn_boolean_t * is_repos2,const char * path_or_url1,const char * path_or_url2,const svn_opt_revision_t * revision1,const svn_opt_revision_t * revision2,const svn_opt_revision_t * peg_revision)1457 check_paths(svn_boolean_t *is_repos1,
1458 svn_boolean_t *is_repos2,
1459 const char *path_or_url1,
1460 const char *path_or_url2,
1461 const svn_opt_revision_t *revision1,
1462 const svn_opt_revision_t *revision2,
1463 const svn_opt_revision_t *peg_revision)
1464 {
1465 svn_boolean_t is_local_rev1, is_local_rev2;
1466
1467 /* Verify our revision arguments in light of the paths. */
1468 if ((revision1->kind == svn_opt_revision_unspecified)
1469 || (revision2->kind == svn_opt_revision_unspecified))
1470 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
1471 _("Not all required revisions are specified"));
1472
1473 /* Revisions can be said to be local or remote.
1474 * BASE and WORKING are local revisions. */
1475 is_local_rev1 =
1476 ((revision1->kind == svn_opt_revision_base)
1477 || (revision1->kind == svn_opt_revision_working));
1478 is_local_rev2 =
1479 ((revision2->kind == svn_opt_revision_base)
1480 || (revision2->kind == svn_opt_revision_working));
1481
1482 if (peg_revision->kind != svn_opt_revision_unspecified &&
1483 is_local_rev1 && is_local_rev2)
1484 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
1485 _("At least one revision must be something other "
1486 "than BASE or WORKING when diffing a URL"));
1487
1488 /* Working copy paths with non-local revisions get turned into
1489 URLs. We don't do that here, though. We simply record that it
1490 needs to be done, which is information that helps us choose our
1491 diff helper function. */
1492 *is_repos1 = ! is_local_rev1 || svn_path_is_url(path_or_url1);
1493 *is_repos2 = ! is_local_rev2 || svn_path_is_url(path_or_url2);
1494
1495 return SVN_NO_ERROR;
1496 }
1497
1498 /* Raise an error if the diff target URL does not exist at REVISION.
1499 * If REVISION does not equal OTHER_REVISION, mention both revisions in
1500 * the error message. Use RA_SESSION to contact the repository.
1501 * Use POOL for temporary allocations. */
1502 static svn_error_t *
check_diff_target_exists(const char * url,svn_revnum_t revision,svn_revnum_t other_revision,svn_ra_session_t * ra_session,apr_pool_t * pool)1503 check_diff_target_exists(const char *url,
1504 svn_revnum_t revision,
1505 svn_revnum_t other_revision,
1506 svn_ra_session_t *ra_session,
1507 apr_pool_t *pool)
1508 {
1509 svn_node_kind_t kind;
1510 const char *session_url;
1511
1512 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
1513
1514 if (strcmp(url, session_url) != 0)
1515 SVN_ERR(svn_ra_reparent(ra_session, url, pool));
1516
1517 SVN_ERR(svn_ra_check_path(ra_session, "", revision, &kind, pool));
1518 if (kind == svn_node_none)
1519 {
1520 if (revision == other_revision)
1521 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1522 _("Diff target '%s' was not found in the "
1523 "repository at revision '%ld'"),
1524 url, revision);
1525 else
1526 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1527 _("Diff target '%s' was not found in the "
1528 "repository at revision '%ld' or '%ld'"),
1529 url, revision, other_revision);
1530 }
1531
1532 if (strcmp(url, session_url) != 0)
1533 SVN_ERR(svn_ra_reparent(ra_session, session_url, pool));
1534
1535 return SVN_NO_ERROR;
1536 }
1537
1538 /** Prepare a repos repos diff between PATH_OR_URL1 and
1539 * PATH_OR_URL2@PEG_REVISION, in the revision range REVISION1:REVISION2.
1540 * Return URLs and peg revisions in *URL1, *REV1 and in *URL2, *REV2.
1541 * Return suitable anchors in *ANCHOR1 and *ANCHOR2, and targets in
1542 * *TARGET1 and *TARGET2, based on *URL1 and *URL2.
1543 * Indicate the corresponding node kinds in *KIND1 and *KIND2, and verify
1544 * that at least one of the diff targets exists.
1545 * Use client context CTX. Do all allocations in POOL. */
1546 static svn_error_t *
diff_prepare_repos_repos(const char ** url1,const char ** url2,svn_revnum_t * rev1,svn_revnum_t * rev2,const char ** anchor1,const char ** anchor2,const char ** target1,const char ** target2,svn_node_kind_t * kind1,svn_node_kind_t * kind2,svn_ra_session_t ** ra_session,svn_client_ctx_t * ctx,const char * path_or_url1,const char * path_or_url2,const svn_opt_revision_t * revision1,const svn_opt_revision_t * revision2,const svn_opt_revision_t * peg_revision,apr_pool_t * pool)1547 diff_prepare_repos_repos(const char **url1,
1548 const char **url2,
1549 svn_revnum_t *rev1,
1550 svn_revnum_t *rev2,
1551 const char **anchor1,
1552 const char **anchor2,
1553 const char **target1,
1554 const char **target2,
1555 svn_node_kind_t *kind1,
1556 svn_node_kind_t *kind2,
1557 svn_ra_session_t **ra_session,
1558 svn_client_ctx_t *ctx,
1559 const char *path_or_url1,
1560 const char *path_or_url2,
1561 const svn_opt_revision_t *revision1,
1562 const svn_opt_revision_t *revision2,
1563 const svn_opt_revision_t *peg_revision,
1564 apr_pool_t *pool)
1565 {
1566 const char *local_abspath1 = NULL;
1567 const char *local_abspath2 = NULL;
1568 const char *repos_root_url;
1569 const char *wri_abspath = NULL;
1570 svn_client__pathrev_t *resolved1;
1571 svn_client__pathrev_t *resolved2 = NULL;
1572 enum svn_opt_revision_kind peg_kind = peg_revision->kind;
1573
1574 if (!svn_path_is_url(path_or_url2))
1575 {
1576 SVN_ERR(svn_dirent_get_absolute(&local_abspath2, path_or_url2, pool));
1577 SVN_ERR(svn_wc__node_get_url(url2, ctx->wc_ctx, local_abspath2,
1578 pool, pool));
1579 wri_abspath = local_abspath2;
1580 }
1581 else
1582 *url2 = apr_pstrdup(pool, path_or_url2);
1583
1584 if (!svn_path_is_url(path_or_url1))
1585 {
1586 SVN_ERR(svn_dirent_get_absolute(&local_abspath1, path_or_url1, pool));
1587 wri_abspath = local_abspath1;
1588 }
1589
1590 SVN_ERR(svn_client_open_ra_session2(ra_session, *url2, wri_abspath,
1591 ctx, pool, pool));
1592
1593 /* If we are performing a pegged diff, we need to find out what our
1594 actual URLs will be. */
1595 if (peg_kind != svn_opt_revision_unspecified
1596 || path_or_url1 == path_or_url2
1597 || local_abspath2)
1598 {
1599 svn_error_t *err;
1600
1601 err = svn_client__resolve_rev_and_url(&resolved2,
1602 *ra_session, path_or_url2,
1603 peg_revision, revision2,
1604 ctx, pool);
1605 if (err)
1606 {
1607 if (err->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES
1608 && err->apr_err != SVN_ERR_FS_NOT_FOUND)
1609 return svn_error_trace(err);
1610
1611 svn_error_clear(err);
1612 resolved2 = NULL;
1613 }
1614 }
1615 else
1616 resolved2 = NULL;
1617
1618 if (peg_kind != svn_opt_revision_unspecified
1619 || path_or_url1 == path_or_url2
1620 || local_abspath1)
1621 {
1622 svn_error_t *err;
1623
1624 err = svn_client__resolve_rev_and_url(&resolved1,
1625 *ra_session, path_or_url1,
1626 peg_revision, revision1,
1627 ctx, pool);
1628 if (err)
1629 {
1630 if (err->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES
1631 && err->apr_err != SVN_ERR_FS_NOT_FOUND)
1632 return svn_error_trace(err);
1633
1634 svn_error_clear(err);
1635 resolved1 = NULL;
1636 }
1637 }
1638 else
1639 resolved1 = NULL;
1640
1641 if (resolved1)
1642 {
1643 *url1 = resolved1->url;
1644 *rev1 = resolved1->rev;
1645 }
1646 else
1647 {
1648 /* It would be nice if we could just return an error when resolving a
1649 location fails... But in many such cases we prefer diffing against
1650 an not existing location to show adds od removes (see issue #4153) */
1651
1652 if (resolved2
1653 && (peg_kind != svn_opt_revision_unspecified
1654 || path_or_url1 == path_or_url2))
1655 *url1 = resolved2->url;
1656 else if (! local_abspath1)
1657 *url1 = path_or_url1;
1658 else
1659 SVN_ERR(svn_wc__node_get_url(url1, ctx->wc_ctx, local_abspath1,
1660 pool, pool));
1661
1662 SVN_ERR(svn_client__get_revision_number(rev1, NULL, ctx->wc_ctx,
1663 local_abspath1 /* may be NULL */,
1664 *ra_session, revision1, pool));
1665 }
1666
1667 if (resolved2)
1668 {
1669 *url2 = resolved2->url;
1670 *rev2 = resolved2->rev;
1671 }
1672 else
1673 {
1674 /* It would be nice if we could just return an error when resolving a
1675 location fails... But in many such cases we prefer diffing against
1676 an not existing location to show adds od removes (see issue #4153) */
1677
1678 if (resolved1
1679 && (peg_kind != svn_opt_revision_unspecified
1680 || path_or_url1 == path_or_url2))
1681 *url2 = resolved1->url;
1682 /* else keep url2 */
1683
1684 SVN_ERR(svn_client__get_revision_number(rev2, NULL, ctx->wc_ctx,
1685 local_abspath2 /* may be NULL */,
1686 *ra_session, revision2, pool));
1687 }
1688
1689 /* Resolve revision and get path kind for the second target. */
1690 SVN_ERR(svn_ra_reparent(*ra_session, *url2, pool));
1691 SVN_ERR(svn_ra_check_path(*ra_session, "", *rev2, kind2, pool));
1692
1693 /* Do the same for the first target. */
1694 SVN_ERR(svn_ra_reparent(*ra_session, *url1, pool));
1695 SVN_ERR(svn_ra_check_path(*ra_session, "", *rev1, kind1, pool));
1696
1697 /* Either both URLs must exist at their respective revisions,
1698 * or one of them may be missing from one side of the diff. */
1699 if (*kind1 == svn_node_none && *kind2 == svn_node_none)
1700 {
1701 if (strcmp(*url1, *url2) == 0)
1702 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1703 _("Diff target '%s' was not found in the "
1704 "repository at revisions '%ld' and '%ld'"),
1705 *url1, *rev1, *rev2);
1706 else
1707 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1708 _("Diff targets '%s' and '%s' were not found "
1709 "in the repository at revisions '%ld' and "
1710 "'%ld'"),
1711 *url1, *url2, *rev1, *rev2);
1712 }
1713 else if (*kind1 == svn_node_none)
1714 SVN_ERR(check_diff_target_exists(*url1, *rev2, *rev1, *ra_session, pool));
1715 else if (*kind2 == svn_node_none)
1716 SVN_ERR(check_diff_target_exists(*url2, *rev1, *rev2, *ra_session, pool));
1717
1718 SVN_ERR(svn_ra_get_repos_root2(*ra_session, &repos_root_url, pool));
1719
1720 /* Choose useful anchors and targets for our two URLs. */
1721 *anchor1 = *url1;
1722 *anchor2 = *url2;
1723 *target1 = "";
1724 *target2 = "";
1725
1726 /* If none of the targets is the repository root open the parent directory
1727 to allow describing replacement of the target itself */
1728 if (strcmp(*url1, repos_root_url) != 0
1729 && strcmp(*url2, repos_root_url) != 0)
1730 {
1731 svn_node_kind_t ignored_kind;
1732 svn_error_t *err;
1733
1734 svn_uri_split(anchor1, target1, *url1, pool);
1735 svn_uri_split(anchor2, target2, *url2, pool);
1736
1737 SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool));
1738
1739 /* We might not have the necessary rights to read the root now.
1740 (It is ok to pass a revision here where the node doesn't exist) */
1741 err = svn_ra_check_path(*ra_session, "", *rev1, &ignored_kind, pool);
1742
1743 if (err && (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN
1744 || err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED))
1745 {
1746 svn_error_clear(err);
1747
1748 /* Ok, lets undo the reparent...
1749
1750 We can't report replacements this way, but at least we can
1751 report changes on the descendants */
1752
1753 *anchor1 = svn_path_url_add_component2(*anchor1, *target1, pool);
1754 *anchor2 = svn_path_url_add_component2(*anchor2, *target2, pool);
1755 *target1 = "";
1756 *target2 = "";
1757
1758 SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool));
1759 }
1760 else
1761 SVN_ERR(err);
1762 }
1763
1764 return SVN_NO_ERROR;
1765 }
1766
1767 /* A Theoretical Note From Ben, regarding do_diff().
1768
1769 This function is really svn_client_diff6(). If you read the public
1770 API description for svn_client_diff6(), it sounds quite Grand. It
1771 sounds really generalized and abstract and beautiful: that it will
1772 diff any two paths, be they working-copy paths or URLs, at any two
1773 revisions.
1774
1775 Now, the *reality* is that we have exactly three 'tools' for doing
1776 diffing, and thus this routine is built around the use of the three
1777 tools. Here they are, for clarity:
1778
1779 - svn_wc_diff: assumes both paths are the same wcpath.
1780 compares wcpath@BASE vs. wcpath@WORKING
1781
1782 - svn_wc_get_diff_editor: compares some URL@REV vs. wcpath@WORKING
1783
1784 - svn_client__get_diff_editor: compares some URL1@REV1 vs. URL2@REV2
1785
1786 Since Subversion 1.8 we also have a variant of svn_wc_diff called
1787 svn_client__arbitrary_nodes_diff, that allows handling WORKING-WORKING
1788 comparisons between nodes in the working copy.
1789
1790 So the truth of the matter is, if the caller's arguments can't be
1791 pigeonholed into one of these use-cases, we currently bail with a
1792 friendly apology.
1793
1794 Perhaps someday a brave soul will truly make svn_client_diff6()
1795 perfectly general. For now, we live with the 90% case. Certainly,
1796 the commandline client only calls this function in legal ways.
1797 When there are other users of svn_client.h, maybe this will become
1798 a more pressing issue.
1799 */
1800
1801 /* Return a "you can't do that" error, optionally wrapping another
1802 error CHILD_ERR. */
1803 static svn_error_t *
unsupported_diff_error(svn_error_t * child_err)1804 unsupported_diff_error(svn_error_t *child_err)
1805 {
1806 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err,
1807 _("Sorry, svn_client_diff6 was called in a way "
1808 "that is not yet supported"));
1809 }
1810
1811 /* Perform a diff between two working-copy paths.
1812
1813 PATH1 and PATH2 are both working copy paths. REVISION1 and
1814 REVISION2 are their respective revisions.
1815
1816 All other options are the same as those passed to svn_client_diff6(). */
1817 static svn_error_t *
diff_wc_wc(const char ** root_relpath,svn_boolean_t * root_is_dir,struct diff_driver_info_t * ddi,const char * path1,const svn_opt_revision_t * revision1,const char * path2,const svn_opt_revision_t * revision2,svn_depth_t depth,svn_boolean_t ignore_ancestry,const apr_array_header_t * changelists,const svn_diff_tree_processor_t * diff_processor,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1818 diff_wc_wc(const char **root_relpath,
1819 svn_boolean_t *root_is_dir,
1820 struct diff_driver_info_t *ddi,
1821 const char *path1,
1822 const svn_opt_revision_t *revision1,
1823 const char *path2,
1824 const svn_opt_revision_t *revision2,
1825 svn_depth_t depth,
1826 svn_boolean_t ignore_ancestry,
1827 const apr_array_header_t *changelists,
1828 const svn_diff_tree_processor_t *diff_processor,
1829 svn_client_ctx_t *ctx,
1830 apr_pool_t *result_pool,
1831 apr_pool_t *scratch_pool)
1832 {
1833 const char *abspath1;
1834
1835 SVN_ERR_ASSERT(! svn_path_is_url(path1));
1836 SVN_ERR_ASSERT(! svn_path_is_url(path2));
1837
1838 SVN_ERR(svn_dirent_get_absolute(&abspath1, path1, scratch_pool));
1839
1840 /* Currently we support only the case where path1 and path2 are the
1841 same path. */
1842 if ((strcmp(path1, path2) != 0)
1843 || (! ((revision1->kind == svn_opt_revision_base)
1844 && (revision2->kind == svn_opt_revision_working))))
1845 return unsupported_diff_error(
1846 svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1847 _("Only diffs between a path's text-base "
1848 "and its working files are supported at this time"
1849 )));
1850
1851 if (ddi)
1852 {
1853 svn_node_kind_t kind;
1854
1855 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1,
1856 TRUE, FALSE, scratch_pool));
1857
1858 if (kind != svn_node_dir)
1859 ddi->anchor = svn_dirent_dirname(path1, scratch_pool);
1860 else
1861 ddi->anchor = path1;
1862 }
1863
1864 SVN_ERR(svn_wc__diff7(root_relpath, root_is_dir,
1865 ctx->wc_ctx, abspath1, depth,
1866 ignore_ancestry, changelists,
1867 diff_processor,
1868 ctx->cancel_func, ctx->cancel_baton,
1869 result_pool, scratch_pool));
1870 return SVN_NO_ERROR;
1871 }
1872
1873 /* Perform a diff between two repository paths.
1874
1875 PATH_OR_URL1 and PATH_OR_URL2 may be either URLs or the working copy paths.
1876 REVISION1 and REVISION2 are their respective revisions.
1877 If PEG_REVISION is specified, PATH_OR_URL2 is the path at the peg revision,
1878 and the actual two paths compared are determined by following copy
1879 history from PATH_OR_URL2.
1880
1881 All other options are the same as those passed to svn_client_diff6(). */
1882 static svn_error_t *
diff_repos_repos(const char ** root_relpath,svn_boolean_t * root_is_dir,struct diff_driver_info_t * ddi,const char * path_or_url1,const char * path_or_url2,const svn_opt_revision_t * revision1,const svn_opt_revision_t * revision2,const svn_opt_revision_t * peg_revision,svn_depth_t depth,svn_boolean_t ignore_ancestry,svn_boolean_t text_deltas,const svn_diff_tree_processor_t * diff_processor,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1883 diff_repos_repos(const char **root_relpath,
1884 svn_boolean_t *root_is_dir,
1885 struct diff_driver_info_t *ddi,
1886 const char *path_or_url1,
1887 const char *path_or_url2,
1888 const svn_opt_revision_t *revision1,
1889 const svn_opt_revision_t *revision2,
1890 const svn_opt_revision_t *peg_revision,
1891 svn_depth_t depth,
1892 svn_boolean_t ignore_ancestry,
1893 svn_boolean_t text_deltas,
1894 const svn_diff_tree_processor_t *diff_processor,
1895 svn_client_ctx_t *ctx,
1896 apr_pool_t *result_pool,
1897 apr_pool_t *scratch_pool)
1898 {
1899 svn_ra_session_t *extra_ra_session;
1900
1901 const svn_ra_reporter3_t *reporter;
1902 void *reporter_baton;
1903
1904 const svn_delta_editor_t *diff_editor;
1905 void *diff_edit_baton;
1906
1907 const char *url1;
1908 const char *url2;
1909 svn_revnum_t rev1;
1910 svn_revnum_t rev2;
1911 svn_node_kind_t kind1;
1912 svn_node_kind_t kind2;
1913 const char *anchor1;
1914 const char *anchor2;
1915 const char *target1;
1916 const char *target2;
1917 svn_ra_session_t *ra_session;
1918
1919 /* Prepare info for the repos repos diff. */
1920 SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &rev1, &rev2,
1921 &anchor1, &anchor2, &target1, &target2,
1922 &kind1, &kind2, &ra_session,
1923 ctx, path_or_url1, path_or_url2,
1924 revision1, revision2, peg_revision,
1925 scratch_pool));
1926
1927 /* Set up the repos_diff editor on BASE_PATH, if available.
1928 Otherwise, we just use "". */
1929
1930 if (ddi)
1931 {
1932 /* Get actual URLs. */
1933 ddi->orig_path_1 = url1;
1934 ddi->orig_path_2 = url2;
1935
1936 /* This should be moved to the diff writer
1937 - path_or_url are provided by the caller
1938 - target1 is available as *root_relpath
1939 - (kind1 != svn_node_dir || kind2 != svn_node_dir) = !*root_is_dir */
1940
1941 if (!svn_path_is_url(path_or_url2))
1942 ddi->anchor = path_or_url2;
1943 else if (!svn_path_is_url(path_or_url1))
1944 ddi->anchor = path_or_url1;
1945 else
1946 ddi->anchor = NULL;
1947
1948 if (*target1 && ddi->anchor
1949 && (kind1 != svn_node_dir || kind2 != svn_node_dir))
1950 ddi->anchor = svn_dirent_dirname(ddi->anchor, result_pool);
1951 }
1952
1953 /* The repository can bring in a new working copy, but not delete
1954 everything. Luckily our new diff handler can just be reversed. */
1955 if (kind2 == svn_node_none)
1956 {
1957 const char *str_tmp;
1958 svn_revnum_t rev_tmp;
1959
1960 str_tmp = url2;
1961 url2 = url1;
1962 url1 = str_tmp;
1963
1964 rev_tmp = rev2;
1965 rev2 = rev1;
1966 rev1 = rev_tmp;
1967
1968 str_tmp = anchor2;
1969 anchor2 = anchor1;
1970 anchor1 = str_tmp;
1971
1972 str_tmp = target2;
1973 target2 = target1;
1974 target1 = str_tmp;
1975
1976 diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
1977 NULL,
1978 scratch_pool);
1979 }
1980
1981 /* Filter the first path component using a filter processor, until we fixed
1982 the diff processing to handle this directly */
1983 if (root_relpath)
1984 *root_relpath = apr_pstrdup(result_pool, target1);
1985 else if ((kind1 != svn_node_file && kind2 != svn_node_file)
1986 && target1[0] != '\0')
1987 {
1988 diff_processor = svn_diff__tree_processor_filter_create(
1989 diff_processor, target1, scratch_pool);
1990 }
1991
1992 /* Now, we open an extra RA session to the correct anchor
1993 location for URL1. This is used during the editor calls to fetch file
1994 contents. */
1995 SVN_ERR(svn_ra__dup_session(&extra_ra_session, ra_session, anchor1,
1996 scratch_pool, scratch_pool));
1997
1998 if (ddi)
1999 {
2000 const char *repos_root_url;
2001 const char *session_url;
2002
2003 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
2004 scratch_pool));
2005 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url,
2006 scratch_pool));
2007
2008 ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url,
2009 session_url,
2010 result_pool);
2011 }
2012
2013 SVN_ERR(svn_client__get_diff_editor2(
2014 &diff_editor, &diff_edit_baton,
2015 extra_ra_session, depth,
2016 rev1,
2017 text_deltas,
2018 diff_processor,
2019 ctx->cancel_func, ctx->cancel_baton,
2020 scratch_pool));
2021
2022 /* We want to switch our txn into URL2 */
2023 SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton,
2024 rev2, target1,
2025 depth, ignore_ancestry, text_deltas,
2026 url2, diff_editor, diff_edit_baton, scratch_pool));
2027
2028 /* Drive the reporter; do the diff. */
2029 SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
2030 svn_depth_infinity,
2031 FALSE, NULL,
2032 scratch_pool));
2033
2034 return svn_error_trace(
2035 reporter->finish_report(reporter_baton, scratch_pool));
2036 }
2037
2038 /* Perform a diff between a repository path and a working-copy path.
2039
2040 PATH_OR_URL1 may be either a URL or a working copy path. PATH2 is a
2041 working copy path. REVISION1 is the revision of URL1. If PEG_REVISION1
2042 is specified, then PATH_OR_URL1 is the path in the peg revision, and the
2043 actual repository path to be compared is determined by following copy
2044 history.
2045
2046 REVISION_KIND2 specifies which revision should be reported from the
2047 working copy (BASE or WORKING)
2048
2049 If REVERSE is TRUE, the diff will be reported in reverse.
2050
2051 All other options are the same as those passed to svn_client_diff6(). */
2052 static svn_error_t *
diff_repos_wc(const char ** root_relpath,svn_boolean_t * root_is_dir,struct diff_driver_info_t * ddi,const char * path_or_url1,const svn_opt_revision_t * revision1,const svn_opt_revision_t * peg_revision1,const char * path2,enum svn_opt_revision_kind revision2_kind,svn_boolean_t reverse,svn_depth_t depth,svn_boolean_t ignore_ancestry,const apr_array_header_t * changelists,const svn_diff_tree_processor_t * diff_processor,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2053 diff_repos_wc(const char **root_relpath,
2054 svn_boolean_t *root_is_dir,
2055 struct diff_driver_info_t *ddi,
2056 const char *path_or_url1,
2057 const svn_opt_revision_t *revision1,
2058 const svn_opt_revision_t *peg_revision1,
2059 const char *path2,
2060 enum svn_opt_revision_kind revision2_kind,
2061 svn_boolean_t reverse,
2062 svn_depth_t depth,
2063 svn_boolean_t ignore_ancestry,
2064 const apr_array_header_t *changelists,
2065 const svn_diff_tree_processor_t *diff_processor,
2066 svn_client_ctx_t *ctx,
2067 apr_pool_t *result_pool,
2068 apr_pool_t *scratch_pool)
2069 {
2070 const char *anchor, *anchor_url, *target;
2071 svn_ra_session_t *ra_session;
2072 svn_depth_t diff_depth;
2073 const svn_ra_reporter3_t *reporter;
2074 void *reporter_baton;
2075 const svn_delta_editor_t *diff_editor;
2076 void *diff_edit_baton;
2077 svn_boolean_t rev2_is_base = (revision2_kind == svn_opt_revision_base);
2078 svn_boolean_t server_supports_depth;
2079 const char *abspath_or_url1;
2080 const char *abspath2;
2081 const char *anchor_abspath;
2082 svn_boolean_t is_copy;
2083 svn_revnum_t cf_revision;
2084 const char *cf_repos_relpath;
2085 const char *cf_repos_root_url;
2086 svn_depth_t cf_depth;
2087 const char *copy_root_abspath;
2088 const char *target_url;
2089 svn_client__pathrev_t *loc1;
2090
2091 SVN_ERR_ASSERT(! svn_path_is_url(path2));
2092
2093 if (!svn_path_is_url(path_or_url1))
2094 {
2095 SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1,
2096 scratch_pool));
2097 }
2098 else
2099 {
2100 abspath_or_url1 = path_or_url1;
2101 }
2102
2103 SVN_ERR(svn_dirent_get_absolute(&abspath2, path2, scratch_pool));
2104
2105 /* Check if our diff target is a copied node. */
2106 SVN_ERR(svn_wc__node_get_origin(&is_copy,
2107 &cf_revision,
2108 &cf_repos_relpath,
2109 &cf_repos_root_url,
2110 NULL, &cf_depth,
2111 ©_root_abspath,
2112 ctx->wc_ctx, abspath2,
2113 FALSE, scratch_pool, scratch_pool));
2114
2115 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc1,
2116 path_or_url1, abspath2,
2117 peg_revision1, revision1,
2118 ctx, scratch_pool));
2119
2120 if (revision2_kind == svn_opt_revision_base || !is_copy)
2121 {
2122 /* Convert path_or_url1 to a URL to feed to do_diff. */
2123 SVN_ERR(svn_wc_get_actual_target2(&anchor, &target, ctx->wc_ctx, path2,
2124 scratch_pool, scratch_pool));
2125
2126 /* Handle the ugly case where target is ".." */
2127 if (*target && !svn_path_is_single_path_component(target))
2128 {
2129 anchor = svn_dirent_join(anchor, target, scratch_pool);
2130 target = "";
2131 }
2132
2133 if (root_relpath)
2134 *root_relpath = apr_pstrdup(result_pool, target);
2135 if (root_is_dir)
2136 *root_is_dir = (*target == '\0');
2137
2138 /* Fetch the URL of the anchor directory. */
2139 SVN_ERR(svn_dirent_get_absolute(&anchor_abspath, anchor, scratch_pool));
2140 SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath,
2141 scratch_pool, scratch_pool));
2142 SVN_ERR_ASSERT(anchor_url != NULL);
2143
2144 target_url = NULL;
2145 }
2146 else /* is_copy && revision2->kind == svn_opt_revision_base */
2147 {
2148 #if 0
2149 svn_node_kind_t kind;
2150 #endif
2151 /* ### Ugly hack ahead ###
2152 *
2153 * We're diffing a locally copied/moved node.
2154 * Describe the copy source to the reporter instead of the copy itself.
2155 * Doing the latter would generate a single add_directory() call to the
2156 * diff editor which results in an unexpected diff (the copy would
2157 * be shown as deleted).
2158 *
2159 * ### But if we will receive any real changes from the repositor we
2160 * will most likely fail to apply them as the wc diff editor assumes
2161 * that we have the data to which the change applies in BASE...
2162 */
2163
2164 target_url = svn_path_url_add_component2(cf_repos_root_url,
2165 cf_repos_relpath,
2166 scratch_pool);
2167
2168 #if 0
2169 /*SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath2, FALSE, FALSE,
2170 scratch_pool));
2171
2172 if (kind != svn_node_dir
2173 || strcmp(copy_root_abspath, abspath2) != 0) */
2174 #endif
2175 {
2176 /* We are looking at a subdirectory of the repository,
2177 We can describe the parent directory as the anchor..
2178
2179 ### This 'appears to work', but that is really dumb luck
2180 ### for the simple cases in the test suite */
2181 anchor_abspath = svn_dirent_dirname(abspath2, scratch_pool);
2182 anchor_url = svn_path_url_add_component2(cf_repos_root_url,
2183 svn_relpath_dirname(
2184 cf_repos_relpath,
2185 scratch_pool),
2186 scratch_pool);
2187 target = svn_dirent_basename(abspath2, NULL);
2188 anchor = svn_dirent_dirname(path2, scratch_pool);
2189 }
2190 #if 0
2191 else
2192 {
2193 /* This code, while ok can't be enabled without causing test
2194 * failures. The repository will send some changes against
2195 * BASE for nodes that don't have BASE...
2196 */
2197 anchor_abspath = abspath2;
2198 anchor_url = svn_path_url_add_component2(cf_repos_root_url,
2199 cf_repos_relpath,
2200 scratch_pool);
2201 anchor = path2;
2202 target = "";
2203 }
2204 #endif
2205 }
2206
2207 SVN_ERR(svn_ra_reparent(ra_session, anchor_url, scratch_pool));
2208
2209 if (ddi)
2210 {
2211 const char *repos_root_url;
2212
2213 ddi->anchor = anchor;
2214
2215 if (!reverse)
2216 {
2217 ddi->orig_path_1 = apr_pstrdup(result_pool, loc1->url);
2218 ddi->orig_path_2 =
2219 svn_path_url_add_component2(anchor_url, target, result_pool);
2220 }
2221 else
2222 {
2223 ddi->orig_path_1 =
2224 svn_path_url_add_component2(anchor_url, target, result_pool);
2225 ddi->orig_path_2 = apr_pstrdup(result_pool, loc1->url);
2226 }
2227
2228 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
2229 scratch_pool));
2230
2231 ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url,
2232 anchor_url,
2233 result_pool);
2234 }
2235
2236 if (reverse)
2237 diff_processor = svn_diff__tree_processor_reverse_create(
2238 diff_processor, NULL, scratch_pool);
2239
2240 /* Use the diff editor to generate the diff. */
2241 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
2242 SVN_RA_CAPABILITY_DEPTH, scratch_pool));
2243 SVN_ERR(svn_wc__get_diff_editor(&diff_editor, &diff_edit_baton,
2244 ctx->wc_ctx,
2245 anchor_abspath,
2246 target,
2247 depth,
2248 ignore_ancestry,
2249 rev2_is_base,
2250 reverse,
2251 server_supports_depth,
2252 changelists,
2253 diff_processor,
2254 ctx->cancel_func, ctx->cancel_baton,
2255 scratch_pool, scratch_pool));
2256
2257 if (depth != svn_depth_infinity)
2258 diff_depth = depth;
2259 else
2260 diff_depth = svn_depth_unknown;
2261
2262 /* Tell the RA layer we want a delta to change our txn to URL1 */
2263 SVN_ERR(svn_ra_do_diff3(ra_session,
2264 &reporter, &reporter_baton,
2265 loc1->rev,
2266 target,
2267 diff_depth,
2268 ignore_ancestry,
2269 TRUE, /* text_deltas */
2270 loc1->url,
2271 diff_editor, diff_edit_baton,
2272 scratch_pool));
2273
2274 if (is_copy && revision2_kind != svn_opt_revision_base)
2275 {
2276 /* Report the copy source. */
2277 if (cf_depth == svn_depth_unknown)
2278 cf_depth = svn_depth_infinity;
2279
2280 /* Reporting the in-wc revision as r0, makes the repository send
2281 everything as added, which avoids using BASE for pristine information,
2282 which is not there (or unrelated) for a copy */
2283
2284 SVN_ERR(reporter->set_path(reporter_baton, "",
2285 ignore_ancestry ? 0 : cf_revision,
2286 cf_depth, FALSE, NULL, scratch_pool));
2287
2288 if (*target)
2289 SVN_ERR(reporter->link_path(reporter_baton, target,
2290 target_url,
2291 ignore_ancestry ? 0 : cf_revision,
2292 cf_depth, FALSE, NULL, scratch_pool));
2293
2294 /* Finish the report to generate the diff. */
2295 SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
2296 }
2297 else
2298 {
2299 /* Create a txn mirror of path2; the diff editor will print
2300 diffs in reverse. :-) */
2301 SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, abspath2,
2302 reporter, reporter_baton,
2303 FALSE, depth, TRUE,
2304 (! server_supports_depth),
2305 FALSE,
2306 ctx->cancel_func, ctx->cancel_baton,
2307 NULL, NULL, /* notification is N/A */
2308 scratch_pool));
2309 }
2310
2311 return SVN_NO_ERROR;
2312 }
2313
2314
2315 /* This is basically just the guts of svn_client_diff[_summarize][_peg]6(). */
2316 static svn_error_t *
do_diff(const char ** root_relpath,svn_boolean_t * root_is_dir,diff_driver_info_t * ddi,const char * path_or_url1,const char * path_or_url2,const svn_opt_revision_t * revision1,const svn_opt_revision_t * revision2,const svn_opt_revision_t * peg_revision,svn_boolean_t no_peg_revision,svn_depth_t depth,svn_boolean_t ignore_ancestry,const apr_array_header_t * changelists,svn_boolean_t text_deltas,const svn_diff_tree_processor_t * diff_processor,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2317 do_diff(const char **root_relpath,
2318 svn_boolean_t *root_is_dir,
2319 diff_driver_info_t *ddi,
2320 const char *path_or_url1,
2321 const char *path_or_url2,
2322 const svn_opt_revision_t *revision1,
2323 const svn_opt_revision_t *revision2,
2324 const svn_opt_revision_t *peg_revision,
2325 svn_boolean_t no_peg_revision,
2326 svn_depth_t depth,
2327 svn_boolean_t ignore_ancestry,
2328 const apr_array_header_t *changelists,
2329 svn_boolean_t text_deltas,
2330 const svn_diff_tree_processor_t *diff_processor,
2331 svn_client_ctx_t *ctx,
2332 apr_pool_t *result_pool,
2333 apr_pool_t *scratch_pool)
2334 {
2335 svn_boolean_t is_repos1;
2336 svn_boolean_t is_repos2;
2337
2338 /* Check if paths/revisions are urls/local. */
2339 SVN_ERR(check_paths(&is_repos1, &is_repos2, path_or_url1, path_or_url2,
2340 revision1, revision2, peg_revision));
2341
2342 if (is_repos1)
2343 {
2344 if (is_repos2)
2345 {
2346 /* Ignores changelists. */
2347 SVN_ERR(diff_repos_repos(root_relpath, root_is_dir,
2348 ddi,
2349 path_or_url1, path_or_url2,
2350 revision1, revision2,
2351 peg_revision, depth, ignore_ancestry,
2352 text_deltas,
2353 diff_processor, ctx,
2354 result_pool, scratch_pool));
2355 }
2356 else /* path_or_url2 is a working copy path */
2357 {
2358 SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi,
2359 path_or_url1, revision1,
2360 no_peg_revision ? revision1
2361 : peg_revision,
2362 path_or_url2, revision2->kind,
2363 FALSE, depth,
2364 ignore_ancestry, changelists,
2365 diff_processor, ctx,
2366 result_pool, scratch_pool));
2367 }
2368 }
2369 else /* path_or_url1 is a working copy path */
2370 {
2371 if (is_repos2)
2372 {
2373 SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi,
2374 path_or_url2, revision2,
2375 no_peg_revision ? revision2
2376 : peg_revision,
2377 path_or_url1,
2378 revision1->kind,
2379 TRUE, depth,
2380 ignore_ancestry, changelists,
2381 diff_processor, ctx,
2382 result_pool, scratch_pool));
2383 }
2384 else /* path_or_url2 is a working copy path */
2385 {
2386 if (revision1->kind == svn_opt_revision_working
2387 && revision2->kind == svn_opt_revision_working)
2388 {
2389 const char *abspath1;
2390 const char *abspath2;
2391
2392 SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1,
2393 scratch_pool));
2394 SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2,
2395 scratch_pool));
2396
2397 /* ### What about ddi? */
2398 /* Ignores changelists, ignore_ancestry */
2399 SVN_ERR(svn_client__arbitrary_nodes_diff(root_relpath, root_is_dir,
2400 abspath1, abspath2,
2401 depth,
2402 diff_processor,
2403 ctx,
2404 result_pool, scratch_pool));
2405 }
2406 else
2407 {
2408 SVN_ERR(diff_wc_wc(root_relpath, root_is_dir, ddi,
2409 path_or_url1, revision1,
2410 path_or_url2, revision2,
2411 depth, ignore_ancestry, changelists,
2412 diff_processor, ctx,
2413 result_pool, scratch_pool));
2414 }
2415 }
2416 }
2417
2418 return SVN_NO_ERROR;
2419 }
2420
2421 /* Initialize DWI.diff_cmd and DWI.options,
2422 * according to OPTIONS and CONFIG. CONFIG and OPTIONS may be null.
2423 * Allocate the fields in RESULT_POOL, which should be at least as long-lived
2424 * as the pool DWI itself is allocated in.
2425 */
2426 static svn_error_t *
create_diff_writer_info(diff_writer_info_t * dwi,const apr_array_header_t * options,apr_hash_t * config,apr_pool_t * result_pool)2427 create_diff_writer_info(diff_writer_info_t *dwi,
2428 const apr_array_header_t *options,
2429 apr_hash_t *config, apr_pool_t *result_pool)
2430 {
2431 const char *diff_cmd = NULL;
2432
2433 /* See if there is a diff command and/or diff arguments. */
2434 if (config)
2435 {
2436 svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
2437 svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
2438 SVN_CONFIG_OPTION_DIFF_CMD, NULL);
2439 if (options == NULL)
2440 {
2441 const char *diff_extensions;
2442 svn_config_get(cfg, &diff_extensions, SVN_CONFIG_SECTION_HELPERS,
2443 SVN_CONFIG_OPTION_DIFF_EXTENSIONS, NULL);
2444 if (diff_extensions)
2445 options = svn_cstring_split(diff_extensions, " \t\n\r", TRUE,
2446 result_pool);
2447 }
2448 }
2449
2450 if (options == NULL)
2451 options = apr_array_make(result_pool, 0, sizeof(const char *));
2452
2453 if (diff_cmd)
2454 SVN_ERR(svn_path_cstring_to_utf8(&dwi->diff_cmd, diff_cmd,
2455 result_pool));
2456 else
2457 dwi->diff_cmd = NULL;
2458
2459 /* If there was a command, arrange options to pass to it. */
2460 if (dwi->diff_cmd)
2461 {
2462 const char **argv = NULL;
2463 int argc = options->nelts;
2464 if (argc)
2465 {
2466 int i;
2467 argv = apr_palloc(result_pool, argc * sizeof(char *));
2468 for (i = 0; i < argc; i++)
2469 SVN_ERR(svn_utf_cstring_to_utf8(&argv[i],
2470 APR_ARRAY_IDX(options, i, const char *), result_pool));
2471 }
2472 dwi->options.for_external.argv = argv;
2473 dwi->options.for_external.argc = argc;
2474 }
2475 else /* No command, so arrange options for internal invocation instead. */
2476 {
2477 dwi->options.for_internal = svn_diff_file_options_create(result_pool);
2478 SVN_ERR(svn_diff_file_options_parse(dwi->options.for_internal,
2479 options, result_pool));
2480 }
2481
2482 return SVN_NO_ERROR;
2483 }
2484
2485 /*----------------------------------------------------------------------- */
2486
2487 /*** Public Interfaces. ***/
2488
2489 /* Display context diffs between two PATH/REVISION pairs. Each of
2490 these inputs will be one of the following:
2491
2492 - a repository URL at a given revision.
2493 - a working copy path, ignoring local mods.
2494 - a working copy path, including local mods.
2495
2496 We can establish a matrix that shows the nine possible types of
2497 diffs we expect to support.
2498
2499
2500 ` . DST || URL:rev | WC:base | WC:working |
2501 ` . || | | |
2502 SRC ` . || | | |
2503 ============++============+============+============+
2504 URL:rev || (*) | (*) | (*) |
2505 || | | |
2506 || | | |
2507 || | | |
2508 ------------++------------+------------+------------+
2509 WC:base || (*) | |
2510 || | New svn_wc_diff which |
2511 || | is smart enough to |
2512 || | handle two WC paths |
2513 ------------++------------+ and their related +
2514 WC:working || (*) | text-bases and working |
2515 || | files. This operation |
2516 || | is entirely local. |
2517 || | |
2518 ------------++------------+------------+------------+
2519 * These cases require server communication.
2520 */
2521 svn_error_t *
svn_client_diff6(const apr_array_header_t * options,const char * path_or_url1,const svn_opt_revision_t * revision1,const char * path_or_url2,const svn_opt_revision_t * revision2,const char * relative_to_dir,svn_depth_t depth,svn_boolean_t ignore_ancestry,svn_boolean_t no_diff_added,svn_boolean_t no_diff_deleted,svn_boolean_t show_copies_as_adds,svn_boolean_t ignore_content_type,svn_boolean_t ignore_properties,svn_boolean_t properties_only,svn_boolean_t use_git_diff_format,const char * header_encoding,svn_stream_t * outstream,svn_stream_t * errstream,const apr_array_header_t * changelists,svn_client_ctx_t * ctx,apr_pool_t * pool)2522 svn_client_diff6(const apr_array_header_t *options,
2523 const char *path_or_url1,
2524 const svn_opt_revision_t *revision1,
2525 const char *path_or_url2,
2526 const svn_opt_revision_t *revision2,
2527 const char *relative_to_dir,
2528 svn_depth_t depth,
2529 svn_boolean_t ignore_ancestry,
2530 svn_boolean_t no_diff_added,
2531 svn_boolean_t no_diff_deleted,
2532 svn_boolean_t show_copies_as_adds,
2533 svn_boolean_t ignore_content_type,
2534 svn_boolean_t ignore_properties,
2535 svn_boolean_t properties_only,
2536 svn_boolean_t use_git_diff_format,
2537 const char *header_encoding,
2538 svn_stream_t *outstream,
2539 svn_stream_t *errstream,
2540 const apr_array_header_t *changelists,
2541 svn_client_ctx_t *ctx,
2542 apr_pool_t *pool)
2543 {
2544 diff_writer_info_t dwi = { 0 };
2545 svn_opt_revision_t peg_revision;
2546 const svn_diff_tree_processor_t *diff_processor;
2547 svn_diff_tree_processor_t *processor;
2548
2549 if (ignore_properties && properties_only)
2550 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2551 _("Cannot ignore properties and show only "
2552 "properties at the same time"));
2553
2554 /* We will never do a pegged diff from here. */
2555 peg_revision.kind = svn_opt_revision_unspecified;
2556
2557 /* setup callback and baton */
2558 dwi.ddi.orig_path_1 = path_or_url1;
2559 dwi.ddi.orig_path_2 = path_or_url2;
2560
2561 SVN_ERR(create_diff_writer_info(&dwi, options,
2562 ctx->config, pool));
2563 dwi.pool = pool;
2564 dwi.outstream = outstream;
2565 dwi.errstream = errstream;
2566 dwi.header_encoding = header_encoding;
2567
2568 dwi.force_binary = ignore_content_type;
2569 dwi.ignore_properties = ignore_properties;
2570 dwi.properties_only = properties_only;
2571 dwi.relative_to_dir = relative_to_dir;
2572 dwi.use_git_diff_format = use_git_diff_format;
2573 dwi.no_diff_added = no_diff_added;
2574 dwi.no_diff_deleted = no_diff_deleted;
2575 dwi.show_copies_as_adds = show_copies_as_adds;
2576
2577 dwi.cancel_func = ctx->cancel_func;
2578 dwi.cancel_baton = ctx->cancel_baton;
2579
2580 dwi.wc_ctx = ctx->wc_ctx;
2581 dwi.ddi.session_relpath = NULL;
2582 dwi.ddi.anchor = NULL;
2583
2584 processor = svn_diff__tree_processor_create(&dwi, pool);
2585
2586 processor->dir_added = diff_dir_added;
2587 processor->dir_changed = diff_dir_changed;
2588 processor->dir_deleted = diff_dir_deleted;
2589
2590 processor->file_added = diff_file_added;
2591 processor->file_changed = diff_file_changed;
2592 processor->file_deleted = diff_file_deleted;
2593
2594 diff_processor = processor;
2595
2596 /* --show-copies-as-adds and --git imply --notice-ancestry */
2597 if (show_copies_as_adds || use_git_diff_format)
2598 ignore_ancestry = FALSE;
2599
2600 return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi,
2601 path_or_url1, path_or_url2,
2602 revision1, revision2,
2603 &peg_revision, TRUE /* no_peg_revision */,
2604 depth, ignore_ancestry, changelists,
2605 TRUE /* text_deltas */,
2606 diff_processor, ctx, pool, pool));
2607 }
2608
2609 svn_error_t *
svn_client_diff_peg6(const apr_array_header_t * options,const char * path_or_url,const svn_opt_revision_t * peg_revision,const svn_opt_revision_t * start_revision,const svn_opt_revision_t * end_revision,const char * relative_to_dir,svn_depth_t depth,svn_boolean_t ignore_ancestry,svn_boolean_t no_diff_added,svn_boolean_t no_diff_deleted,svn_boolean_t show_copies_as_adds,svn_boolean_t ignore_content_type,svn_boolean_t ignore_properties,svn_boolean_t properties_only,svn_boolean_t use_git_diff_format,const char * header_encoding,svn_stream_t * outstream,svn_stream_t * errstream,const apr_array_header_t * changelists,svn_client_ctx_t * ctx,apr_pool_t * pool)2610 svn_client_diff_peg6(const apr_array_header_t *options,
2611 const char *path_or_url,
2612 const svn_opt_revision_t *peg_revision,
2613 const svn_opt_revision_t *start_revision,
2614 const svn_opt_revision_t *end_revision,
2615 const char *relative_to_dir,
2616 svn_depth_t depth,
2617 svn_boolean_t ignore_ancestry,
2618 svn_boolean_t no_diff_added,
2619 svn_boolean_t no_diff_deleted,
2620 svn_boolean_t show_copies_as_adds,
2621 svn_boolean_t ignore_content_type,
2622 svn_boolean_t ignore_properties,
2623 svn_boolean_t properties_only,
2624 svn_boolean_t use_git_diff_format,
2625 const char *header_encoding,
2626 svn_stream_t *outstream,
2627 svn_stream_t *errstream,
2628 const apr_array_header_t *changelists,
2629 svn_client_ctx_t *ctx,
2630 apr_pool_t *pool)
2631 {
2632 diff_writer_info_t dwi = { 0 };
2633 const svn_diff_tree_processor_t *diff_processor;
2634 svn_diff_tree_processor_t *processor;
2635
2636 if (ignore_properties && properties_only)
2637 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2638 _("Cannot ignore properties and show only "
2639 "properties at the same time"));
2640
2641 /* setup callback and baton */
2642 dwi.ddi.orig_path_1 = path_or_url;
2643 dwi.ddi.orig_path_2 = path_or_url;
2644
2645 SVN_ERR(create_diff_writer_info(&dwi, options,
2646 ctx->config, pool));
2647 dwi.pool = pool;
2648 dwi.outstream = outstream;
2649 dwi.errstream = errstream;
2650 dwi.header_encoding = header_encoding;
2651
2652 dwi.force_binary = ignore_content_type;
2653 dwi.ignore_properties = ignore_properties;
2654 dwi.properties_only = properties_only;
2655 dwi.relative_to_dir = relative_to_dir;
2656 dwi.use_git_diff_format = use_git_diff_format;
2657 dwi.no_diff_added = no_diff_added;
2658 dwi.no_diff_deleted = no_diff_deleted;
2659 dwi.show_copies_as_adds = show_copies_as_adds;
2660
2661 dwi.cancel_func = ctx->cancel_func;
2662 dwi.cancel_baton = ctx->cancel_baton;
2663
2664 dwi.wc_ctx = ctx->wc_ctx;
2665 dwi.ddi.session_relpath = NULL;
2666 dwi.ddi.anchor = NULL;
2667
2668 processor = svn_diff__tree_processor_create(&dwi, pool);
2669
2670 processor->dir_added = diff_dir_added;
2671 processor->dir_changed = diff_dir_changed;
2672 processor->dir_deleted = diff_dir_deleted;
2673
2674 processor->file_added = diff_file_added;
2675 processor->file_changed = diff_file_changed;
2676 processor->file_deleted = diff_file_deleted;
2677
2678 diff_processor = processor;
2679
2680 /* --show-copies-as-adds and --git imply --notice-ancestry */
2681 if (show_copies_as_adds || use_git_diff_format)
2682 ignore_ancestry = FALSE;
2683
2684 return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi,
2685 path_or_url, path_or_url,
2686 start_revision, end_revision,
2687 peg_revision, FALSE /* no_peg_revision */,
2688 depth, ignore_ancestry, changelists,
2689 TRUE /* text_deltas */,
2690 diff_processor, ctx, pool, pool));
2691 }
2692
2693 svn_error_t *
svn_client_diff_summarize2(const char * path_or_url1,const svn_opt_revision_t * revision1,const char * path_or_url2,const svn_opt_revision_t * revision2,svn_depth_t depth,svn_boolean_t ignore_ancestry,const apr_array_header_t * changelists,svn_client_diff_summarize_func_t summarize_func,void * summarize_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)2694 svn_client_diff_summarize2(const char *path_or_url1,
2695 const svn_opt_revision_t *revision1,
2696 const char *path_or_url2,
2697 const svn_opt_revision_t *revision2,
2698 svn_depth_t depth,
2699 svn_boolean_t ignore_ancestry,
2700 const apr_array_header_t *changelists,
2701 svn_client_diff_summarize_func_t summarize_func,
2702 void *summarize_baton,
2703 svn_client_ctx_t *ctx,
2704 apr_pool_t *pool)
2705 {
2706 const svn_diff_tree_processor_t *diff_processor;
2707 svn_opt_revision_t peg_revision;
2708 const char **p_root_relpath;
2709
2710 /* We will never do a pegged diff from here. */
2711 peg_revision.kind = svn_opt_revision_unspecified;
2712
2713 SVN_ERR(svn_client__get_diff_summarize_callbacks(
2714 &diff_processor, &p_root_relpath,
2715 summarize_func, summarize_baton,
2716 path_or_url1, pool, pool));
2717
2718 return svn_error_trace(do_diff(p_root_relpath, NULL, NULL,
2719 path_or_url1, path_or_url2,
2720 revision1, revision2,
2721 &peg_revision, TRUE /* no_peg_revision */,
2722 depth, ignore_ancestry, changelists,
2723 FALSE /* text_deltas */,
2724 diff_processor, ctx, pool, pool));
2725 }
2726
2727 svn_error_t *
svn_client_diff_summarize_peg2(const char * path_or_url,const svn_opt_revision_t * peg_revision,const svn_opt_revision_t * start_revision,const svn_opt_revision_t * end_revision,svn_depth_t depth,svn_boolean_t ignore_ancestry,const apr_array_header_t * changelists,svn_client_diff_summarize_func_t summarize_func,void * summarize_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)2728 svn_client_diff_summarize_peg2(const char *path_or_url,
2729 const svn_opt_revision_t *peg_revision,
2730 const svn_opt_revision_t *start_revision,
2731 const svn_opt_revision_t *end_revision,
2732 svn_depth_t depth,
2733 svn_boolean_t ignore_ancestry,
2734 const apr_array_header_t *changelists,
2735 svn_client_diff_summarize_func_t summarize_func,
2736 void *summarize_baton,
2737 svn_client_ctx_t *ctx,
2738 apr_pool_t *pool)
2739 {
2740 const svn_diff_tree_processor_t *diff_processor;
2741 const char **p_root_relpath;
2742
2743 SVN_ERR(svn_client__get_diff_summarize_callbacks(
2744 &diff_processor, &p_root_relpath,
2745 summarize_func, summarize_baton,
2746 path_or_url, pool, pool));
2747
2748 return svn_error_trace(do_diff(p_root_relpath, NULL, NULL,
2749 path_or_url, path_or_url,
2750 start_revision, end_revision,
2751 peg_revision, FALSE /* no_peg_revision */,
2752 depth, ignore_ancestry, changelists,
2753 FALSE /* text_deltas */,
2754 diff_processor, ctx, pool, pool));
2755 }
2756
2757