You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by ju...@apache.org on 2015/09/28 18:03:15 UTC

svn commit: r1705712 [2/3] - in /subversion/branches/move-tracking-2: ./ subversion/ subversion/include/ subversion/include/private/ subversion/libsvn_client/ subversion/libsvn_diff/ subversion/libsvn_fs_base/ subversion/libsvn_fs_fs/ subversion/libsvn...

Modified: subversion/branches/move-tracking-2/subversion/libsvn_diff/parse-diff.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_diff/parse-diff.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_diff/parse-diff.c (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_diff/parse-diff.c Mon Sep 28 16:03:14 2015
@@ -40,8 +40,13 @@
 
 #include "private/svn_eol_private.h"
 #include "private/svn_dep_compat.h"
+#include "private/svn_diff_private.h"
 #include "private/svn_sorts_private.h"
 
+#include "diff.h"
+
+#include "svn_private_config.h"
+
 /* Helper macro for readability */
 #define starts_with(str, start)  \
   (strncmp((str), (start), strlen(start)) == 0)
@@ -80,6 +85,10 @@ struct svn_diff_hunk_t {
   /* Number of lines of leading and trailing hunk context. */
   svn_linenum_t leading_context;
   svn_linenum_t trailing_context;
+
+  /* Did we see a 'file does not end with eol' marker in this hunk? */
+  svn_boolean_t original_no_final_eol;
+  svn_boolean_t modified_no_final_eol;
 };
 
 struct svn_diff_binary_patch_t {
@@ -100,6 +109,120 @@ struct svn_diff_binary_patch_t {
   svn_filesize_t dst_filesize; /* Expanded/final size */
 };
 
+/* Common guts of svn_diff_hunk__create_adds_single_line() and
+ * svn_diff_hunk__create_deletes_single_line().
+ *
+ * ADD is TRUE if adding and FALSE if deleting.
+ */
+static svn_error_t *
+add_or_delete_single_line(svn_diff_hunk_t **hunk_out,
+                          const char *line,
+                          svn_patch_t *patch,
+                          svn_boolean_t add,
+                          apr_pool_t *result_pool,
+                          apr_pool_t *scratch_pool)
+{
+  svn_diff_hunk_t *hunk = apr_palloc(result_pool, sizeof(*hunk));
+  static const char *hunk_header[] = { "@@ -1 +0,0 @@\n", "@@ -0,0 +1 @@\n" };
+  const apr_size_t header_len = strlen(hunk_header[add]);
+  const apr_size_t len = strlen(line);
+  const apr_size_t end = header_len + (1 + len); /* The +1 is for the \n. */
+  svn_stringbuf_t *buf = svn_stringbuf_create_ensure(end + 1, scratch_pool);
+
+  hunk->patch = patch;
+
+  /* hunk->apr_file is created below. */
+
+  hunk->diff_text_range.start = header_len;
+  hunk->diff_text_range.current = header_len;
+
+  if (add)
+    {
+      hunk->original_text_range.start = 0; /* There's no "original" text. */
+      hunk->original_text_range.current = 0;
+      hunk->original_text_range.end = 0;
+      hunk->original_no_final_eol = FALSE;
+
+      hunk->modified_text_range.start = header_len;
+      hunk->modified_text_range.current = header_len;
+      hunk->modified_text_range.end = end;
+      hunk->modified_no_final_eol = TRUE;
+
+      hunk->original_start = 0;
+      hunk->original_length = 0;
+
+      hunk->modified_start = 1;
+      hunk->modified_length = 1;
+    }
+  else /* delete */
+    {
+      hunk->original_text_range.start = header_len;
+      hunk->original_text_range.current = header_len;
+      hunk->original_text_range.end = end;
+      hunk->original_no_final_eol = TRUE;
+
+      hunk->modified_text_range.start = 0; /* There's no "original" text. */
+      hunk->modified_text_range.current = 0;
+      hunk->modified_text_range.end = 0;
+      hunk->modified_no_final_eol = FALSE;
+
+      hunk->original_start = 1;
+      hunk->original_length = 1;
+
+      hunk->modified_start = 0;
+      hunk->modified_length = 0; /* setting to '1' works too */
+    }
+
+  hunk->leading_context = 0;
+  hunk->trailing_context = 0;
+
+  /* Create APR_FILE and put just a hunk in it (without a diff header).
+   * Save the offset of the last byte of the diff line. */
+  svn_stringbuf_appendbytes(buf, hunk_header[add], header_len);
+  svn_stringbuf_appendbyte(buf, add ? '+' : '-');
+  svn_stringbuf_appendbytes(buf, line, len);
+  svn_stringbuf_appendbyte(buf, '\n');
+  svn_stringbuf_appendcstr(buf, "\\ No newline at end of hunk\n");
+
+  hunk->diff_text_range.end = buf->len;
+
+  SVN_ERR(svn_io_open_unique_file3(&hunk->apr_file, NULL /* filename */,
+                                   NULL /* system tempdir */,
+                                   svn_io_file_del_on_pool_cleanup,
+                                   result_pool, scratch_pool));
+  SVN_ERR(svn_io_file_write_full(hunk->apr_file,
+                                 buf->data, buf->len,
+                                 NULL, scratch_pool));
+  /* No need to seek. */
+
+  *hunk_out = hunk;
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_diff_hunk__create_adds_single_line(svn_diff_hunk_t **hunk_out,
+                                       const char *line,
+                                       svn_patch_t *patch,
+                                       apr_pool_t *result_pool,
+                                       apr_pool_t *scratch_pool)
+{
+  SVN_ERR(add_or_delete_single_line(hunk_out, line, patch, TRUE,
+                                    result_pool, scratch_pool));
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_diff_hunk__create_deletes_single_line(svn_diff_hunk_t **hunk_out,
+                                          const char *line,
+                                          svn_patch_t *patch,
+                                          apr_pool_t *result_pool,
+                                          apr_pool_t *scratch_pool)
+{
+  SVN_ERR(add_or_delete_single_line(hunk_out, line, patch, FALSE,
+                                    result_pool, scratch_pool));
+  return SVN_NO_ERROR;
+}
+
 void
 svn_diff_hunk_reset_diff_text(svn_diff_hunk_t *hunk)
 {
@@ -160,6 +283,217 @@ svn_diff_hunk_get_trailing_context(const
   return hunk->trailing_context;
 }
 
+/* Baton for the base85 stream implementation */
+struct base85_baton_t
+{
+  apr_file_t *file;
+  apr_pool_t *iterpool;
+  char buffer[52];        /* Bytes on current line */
+  apr_off_t next_pos;     /* Start position of next line */
+  apr_off_t end_pos;      /* Position after last line */
+  apr_size_t buf_size;    /* Bytes available (52 unless at eof) */
+  apr_size_t buf_pos;     /* Bytes in linebuffer */
+  svn_boolean_t done;     /* At eof? */
+};
+
+/* Implements svn_read_fn_t for the base85 read stream */
+static svn_error_t *
+read_handler_base85(void *baton, char *buffer, apr_size_t *len)
+{
+  struct base85_baton_t *b85b = baton;
+  apr_pool_t *iterpool = b85b->iterpool;
+  apr_size_t remaining = *len;
+  char *dest = buffer;
+
+  svn_pool_clear(iterpool);
+
+  if (b85b->done)
+    {
+      *len = 0;
+      return SVN_NO_ERROR;
+    }
+
+  while (remaining && (b85b->buf_size > b85b->buf_pos
+                       || b85b->next_pos < b85b->end_pos))
+    {
+      svn_stringbuf_t *line;
+      svn_boolean_t at_eof;
+
+      apr_size_t available = b85b->buf_size - b85b->buf_pos;
+      if (available)
+        {
+          apr_size_t n = (remaining < available) ? remaining : available;
+
+          memcpy(dest, b85b->buffer + b85b->buf_pos, n);
+          dest += n;
+          remaining -= n;
+          b85b->buf_pos += n;
+
+          if (!remaining)
+            return SVN_NO_ERROR; /* *len = OK */
+        }
+
+      if (b85b->next_pos >= b85b->end_pos)
+        break; /* At EOF */
+      SVN_ERR(svn_io_file_seek(b85b->file, APR_SET, &b85b->next_pos,
+                               iterpool));
+      SVN_ERR(svn_io_file_readline(b85b->file, &line, NULL, &at_eof,
+                                   APR_SIZE_MAX, iterpool, iterpool));
+      if (at_eof)
+        b85b->next_pos = b85b->end_pos;
+      else
+        {
+          b85b->next_pos = 0;
+          SVN_ERR(svn_io_file_seek(b85b->file, APR_CUR, &b85b->next_pos,
+                                   iterpool));
+        }
+
+      if (line->len && line->data[0] >= 'A' && line->data[0] <= 'Z')
+        b85b->buf_size = line->data[0] - 'A' + 1;
+      else if (line->len && line->data[0] >= 'a' && line->data[0] <= 'z')
+        b85b->buf_size = line->data[0] - 'a' + 26 + 1;
+      else
+        return svn_error_create(SVN_ERR_DIFF_UNEXPECTED_DATA, NULL,
+                                _("Unexpected data in base85 section"));
+
+      if (b85b->buf_size < 52)
+        b85b->next_pos = b85b->end_pos; /* Handle as EOF */
+
+      SVN_ERR(svn_diff__base85_decode_line(b85b->buffer, b85b->buf_size,
+                                           line->data + 1, line->len - 1,
+                                           iterpool));
+      b85b->buf_pos = 0;
+    }
+
+  *len -= remaining;
+  b85b->done = TRUE;
+
+  return SVN_NO_ERROR;
+}
+
+/* Implements svn_close_fn_t for the base85 read stream */
+static svn_error_t *
+close_handler_base85(void *baton)
+{
+  struct base85_baton_t *b85b = baton;
+
+  svn_pool_destroy(b85b->iterpool);
+
+  return SVN_NO_ERROR;
+}
+
+/* Gets a stream that reads decoded base85 data from a segment of a file.
+   The current implementation might assume that both start_pos and end_pos
+   are located at line boundaries. */
+static svn_stream_t *
+get_base85_data_stream(apr_file_t *file,
+                       apr_off_t start_pos,
+                       apr_off_t end_pos,
+                       apr_pool_t *result_pool)
+{
+  struct base85_baton_t *b85b = apr_pcalloc(result_pool, sizeof(*b85b));
+  svn_stream_t *base85s = svn_stream_create(b85b, result_pool);
+
+  b85b->file = file;
+  b85b->iterpool = svn_pool_create(result_pool);
+  b85b->next_pos = start_pos;
+  b85b->end_pos = end_pos;
+
+  svn_stream_set_read2(base85s, NULL /* only full read support */,
+                       read_handler_base85);
+  svn_stream_set_close(base85s, close_handler_base85);
+  return base85s;
+}
+
+/* Baton for the length verification stream functions */
+struct length_verify_baton_t
+{
+  svn_stream_t *inner;
+  svn_filesize_t remaining;
+};
+
+/* Implements svn_read_fn_t for the length verification stream */
+static svn_error_t *
+read_handler_length_verify(void *baton, char *buffer, apr_size_t *len)
+{
+  struct length_verify_baton_t *lvb = baton;
+  apr_size_t requested_len = *len;
+
+  SVN_ERR(svn_stream_read_full(lvb->inner, buffer, len));
+
+  if (*len > lvb->remaining)
+    return svn_error_create(SVN_ERR_DIFF_UNEXPECTED_DATA, NULL,
+                            _("Base85 data expands to longer than declared "
+                              "filesize"));
+  else if (requested_len > *len && *len != lvb->remaining)
+    return svn_error_create(SVN_ERR_DIFF_UNEXPECTED_DATA, NULL,
+                            _("Base85 data expands to smaller than declared "
+                              "filesize"));
+
+  lvb->remaining -= *len;
+
+  return SVN_NO_ERROR;
+}
+
+/* Implements svn_close_fn_t for the length verification stream */
+static svn_error_t *
+close_handler_length_verify(void *baton)
+{
+  struct length_verify_baton_t *lvb = baton;
+
+  return svn_error_trace(svn_stream_close(lvb->inner));
+}
+
+/* Gets a stream that verifies on reads that the inner stream is exactly
+   of the specified length */
+static svn_stream_t *
+get_verify_length_stream(svn_stream_t *inner,
+                         svn_filesize_t expected_size,
+                         apr_pool_t *result_pool)
+{
+  struct length_verify_baton_t *lvb = apr_palloc(result_pool, sizeof(*lvb));
+  svn_stream_t *len_stream = svn_stream_create(lvb, result_pool);
+
+  lvb->inner = inner;
+  lvb->remaining = expected_size;
+
+  svn_stream_set_read2(len_stream, NULL /* only full read support */,
+                       read_handler_length_verify);
+  svn_stream_set_close(len_stream, close_handler_length_verify);
+
+  return len_stream;
+}
+
+svn_stream_t *
+svn_diff_get_binary_diff_original_stream(const svn_diff_binary_patch_t *bpatch,
+                                         apr_pool_t *result_pool)
+{
+  svn_stream_t *s = get_base85_data_stream(bpatch->apr_file, bpatch->src_start,
+                                           bpatch->src_end, result_pool);
+
+  s = svn_stream_compressed(s, result_pool);
+
+  /* ### If we (ever) want to support the DELTA format, then we should hook the
+         undelta handling here */
+
+  return get_verify_length_stream(s, bpatch->src_filesize, result_pool);
+}
+
+svn_stream_t *
+svn_diff_get_binary_diff_result_stream(const svn_diff_binary_patch_t *bpatch,
+                                       apr_pool_t *result_pool)
+{
+  svn_stream_t *s = get_base85_data_stream(bpatch->apr_file, bpatch->dst_start,
+                                           bpatch->dst_end, result_pool);
+
+  s = svn_stream_compressed(s, result_pool);
+
+  /* ### If we (ever) want to support the DELTA format, then we should hook the
+  undelta handling here */
+
+  return get_verify_length_stream(s, bpatch->dst_filesize, result_pool);
+}
+
 /* Try to parse a positive number from a decimal number encoded
  * in the string NUMBER. Return parsed number in OFFSET, and return
  * TRUE if parsing was successful. */
@@ -297,7 +631,8 @@ parse_hunk_header(const char *header, sv
  * Leading unidiff symbols ('+', '-', and ' ') are removed from the line,
  * Any lines commencing with the VERBOTEN character are discarded.
  * VERBOTEN should be '+' or '-', depending on which form of hunk text
- * is being read.
+ * is being read. NO_FINAL_EOL declares if the hunk contains a no final
+ * EOL marker.
  *
  * All other parameters are as in svn_diff_hunk_readline_original_text()
  * and svn_diff_hunk_readline_modified_text().
@@ -309,6 +644,7 @@ hunk_readline_original_or_modified(apr_f
                                    const char **eol,
                                    svn_boolean_t *eof,
                                    char verboten,
+                                   svn_boolean_t no_final_eol,
                                    apr_pool_t *result_pool,
                                    apr_pool_t *scratch_pool)
 {
@@ -316,13 +652,16 @@ hunk_readline_original_or_modified(apr_f
   svn_boolean_t filtered;
   apr_off_t pos;
   svn_stringbuf_t *str;
+  const char *eol_p;
+
+  if (!eol)
+    eol = &eol_p;
 
   if (range->current >= range->end)
     {
       /* We're past the range. Indicate that no bytes can be read. */
       *eof = TRUE;
-      if (eol)
-        *eol = NULL;
+      *eol = NULL;
       *stringbuf = svn_stringbuf_create_empty(result_pool);
       return SVN_NO_ERROR;
     }
@@ -345,6 +684,7 @@ hunk_readline_original_or_modified(apr_f
     {
       /* EOF, return an empty string. */
       *stringbuf = svn_stringbuf_create_ensure(0, result_pool);
+      *eol = NULL;
     }
   else if (str->data[0] == '+' || str->data[0] == '-' || str->data[0] == ' ')
     {
@@ -353,10 +693,34 @@ hunk_readline_original_or_modified(apr_f
     }
   else
     {
-      /* Return the line as-is. */
+      /* Return the line as-is. Handle as a chopped leading spaces */
       *stringbuf = svn_stringbuf_dup(str, result_pool);
     }
 
+  if (!filtered && *eof && !*eol && !no_final_eol && *str->data)
+    {
+      /* Ok, we miss a final EOL in the patch file, but didn't see a
+         no eol marker line.
+
+         We should report that we had an EOL or the patch code will
+         misbehave (and it knows nothing about no eol markers) */
+
+      if (eol != &eol_p)
+        {
+          apr_off_t start = 0;
+
+          SVN_ERR(svn_io_file_seek(file, APR_SET, &start, scratch_pool));
+
+          SVN_ERR(svn_io_file_readline(file, &str, eol, NULL, APR_SIZE_MAX,
+                                       scratch_pool, scratch_pool));
+
+          /* Every patch file that has hunks has at least one EOL*/
+          SVN_ERR_ASSERT(*eol != NULL);
+        }
+
+      *eof = FALSE;
+      /* Fall through to seek back to the right location */
+    }
   SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
 
   return SVN_NO_ERROR;
@@ -377,6 +741,9 @@ svn_diff_hunk_readline_original_text(svn
                                          &hunk->original_text_range,
                                        stringbuf, eol, eof,
                                        hunk->patch->reverse ? '-' : '+',
+                                       hunk->patch->reverse
+                                          ? hunk->modified_no_final_eol
+                                          : hunk->original_no_final_eol,
                                        result_pool, scratch_pool));
 }
 
@@ -395,6 +762,9 @@ svn_diff_hunk_readline_modified_text(svn
                                          &hunk->modified_text_range,
                                        stringbuf, eol, eof,
                                        hunk->patch->reverse ? '+' : '-',
+                                       hunk->patch->reverse
+                                          ? hunk->original_no_final_eol
+                                          : hunk->modified_no_final_eol,
                                        result_pool, scratch_pool));
 }
 
@@ -409,13 +779,16 @@ svn_diff_hunk_readline_diff_text(svn_dif
   svn_stringbuf_t *line;
   apr_size_t max_len;
   apr_off_t pos;
+  const char *eol_p;
+
+  if (!eol)
+    eol = &eol_p;
 
   if (hunk->diff_text_range.current >= hunk->diff_text_range.end)
     {
       /* We're past the range. Indicate that no bytes can be read. */
       *eof = TRUE;
-      if (eol)
-        *eol = NULL;
+      *eol = NULL;
       *stringbuf = svn_stringbuf_create_empty(result_pool);
       return SVN_NO_ERROR;
     }
@@ -431,6 +804,37 @@ svn_diff_hunk_readline_diff_text(svn_dif
   hunk->diff_text_range.current = 0;
   SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_CUR,
                            &hunk->diff_text_range.current, scratch_pool));
+
+  if (*eof && !*eol && *line->data)
+    {
+      /* Ok, we miss a final EOL in the patch file, but didn't see a
+          no eol marker line.
+
+          We should report that we had an EOL or the patch code will
+          misbehave (and it knows nothing about no eol markers) */
+
+      if (eol != &eol_p)
+        {
+          /* Lets pick the first eol we find in our patch file */
+          apr_off_t start = 0;
+          svn_stringbuf_t *str;
+
+          SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET, &start,
+                                   scratch_pool));
+
+          SVN_ERR(svn_io_file_readline(hunk->apr_file, &str, eol, NULL,
+                                       APR_SIZE_MAX,
+                                       scratch_pool, scratch_pool));
+
+          /* Every patch file that has hunks has at least one EOL*/
+          SVN_ERR_ASSERT(*eol != NULL);
+        }
+
+      *eof = FALSE;
+
+      /* Fall through to seek back to the right location */
+    }
+
   SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET, &pos, scratch_pool));
 
   if (hunk->patch->reverse)
@@ -637,6 +1041,8 @@ parse_next_hunk(svn_diff_hunk_t **hunk,
   apr_off_t start, end;
   apr_off_t original_end;
   apr_off_t modified_end;
+  svn_boolean_t original_no_final_eol = FALSE;
+  svn_boolean_t modified_no_final_eol = FALSE;
   svn_linenum_t original_lines;
   svn_linenum_t modified_lines;
   svn_linenum_t leading_context;
@@ -734,6 +1140,11 @@ parse_next_hunk(svn_diff_hunk_t **hunk,
 
               SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &pos, iterpool));
             }
+          /* Set for the type and context by using != the other type */
+          if (last_line_type != modified_line)
+            original_no_final_eol = TRUE;
+          if (last_line_type != original_line)
+            modified_no_final_eol = TRUE;
 
           continue;
         }
@@ -861,14 +1272,16 @@ parse_next_hunk(svn_diff_hunk_t **hunk,
               SVN_ERR(parse_prop_name(prop_name, line->data, "Added: ",
                                       result_pool));
               if (*prop_name)
-                *prop_operation = svn_diff_op_added;
+                *prop_operation = (patch->reverse ? svn_diff_op_deleted
+                                                  : svn_diff_op_added);
             }
           else if (starts_with(line->data, "Deleted: "))
             {
               SVN_ERR(parse_prop_name(prop_name, line->data, "Deleted: ",
                                       result_pool));
               if (*prop_name)
-                *prop_operation = svn_diff_op_deleted;
+                *prop_operation = (patch->reverse ? svn_diff_op_added
+                                                  : svn_diff_op_deleted);
             }
           else if (starts_with(line->data, "Modified: "))
             {
@@ -909,6 +1322,8 @@ parse_next_hunk(svn_diff_hunk_t **hunk,
       (*hunk)->modified_text_range.start = start;
       (*hunk)->modified_text_range.current = start;
       (*hunk)->modified_text_range.end = modified_end;
+      (*hunk)->original_no_final_eol = original_no_final_eol;
+      (*hunk)->modified_no_final_eol = modified_no_final_eol;
     }
   else
     /* Something went wrong, just discard the result. */
@@ -940,6 +1355,8 @@ enum parse_state
    state_git_tree_seen,     /* a tree operation, rather than content change */
    state_git_minus_seen,    /* --- /dev/null; or --- a/ */
    state_git_plus_seen,     /* +++ /dev/null; or +++ a/ */
+   state_old_mode_seen,     /* old mode 100644 */
+   state_git_mode_seen,     /* new mode 100644 */
    state_move_from_seen,    /* rename from foo.c */
    state_copy_from_seen,    /* copy from foo.c */
    state_minus_seen,        /* --- foo.c */
@@ -1172,6 +1589,85 @@ git_plus(enum parse_state *new_state, ch
   return SVN_NO_ERROR;
 }
 
+/* Helper for git_old_mode() and git_new_mode().  Translate the git
+ * file mode MODE_STR into a binary "executable?" notion EXECUTABLE_P. */
+static svn_error_t *
+parse_bits_into_executability(svn_tristate_t *executable_p,
+                              const char *mode_str)
+{
+  apr_uint64_t mode;
+  SVN_ERR(svn_cstring_strtoui64(&mode, mode_str,
+                                0 /* min */,
+                                0777777 /* max: six octal digits */,
+                                010 /* radix (octal) */));
+
+  /* Note: 0644 and 0755 are the only modes that can occur for plain files.
+   * We deliberately choose to parse only those values: we are strict in what
+   * we accept _and_ in what we produce.
+   *
+   * (Having said that, though, we could consider relaxing the parser to also
+   * map
+   *     (mode & 0111) == 0000 -> svn_tristate_false
+   *     (mode & 0111) == 0111 -> svn_tristate_true
+   *        [anything else]    -> svn_tristate_unknown
+   * .)
+   */
+
+  switch (mode & 0777)
+    {
+      case 0644:
+        *executable_p = svn_tristate_false;
+        break;
+
+      case 0755:
+        *executable_p = svn_tristate_true;
+        break;
+
+      default:
+        /* Ignore unknown values. */
+        *executable_p = svn_tristate_unknown;
+        break;
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Parse the 'old mode ' line of a git extended unidiff. */
+static svn_error_t *
+git_old_mode(enum parse_state *new_state, char *line, svn_patch_t *patch,
+             apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+  SVN_ERR(parse_bits_into_executability(&patch->old_executable_p,
+                                        line + STRLEN_LITERAL("old mode ")));
+
+#ifdef SVN_DEBUG
+  /* If this assert trips, the "old mode" is neither ...644 nor ...755 . */
+  SVN_ERR_ASSERT(patch->old_executable_p != svn_tristate_unknown);
+#endif
+
+  *new_state = state_old_mode_seen;
+  return SVN_NO_ERROR;
+}
+
+/* Parse the 'new mode ' line of a git extended unidiff. */
+static svn_error_t *
+git_new_mode(enum parse_state *new_state, char *line, svn_patch_t *patch,
+             apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+  SVN_ERR(parse_bits_into_executability(&patch->new_executable_p,
+                                        line + STRLEN_LITERAL("new mode ")));
+
+#ifdef SVN_DEBUG
+  /* If this assert trips, the "old mode" is neither ...644 nor ...755 . */
+  SVN_ERR_ASSERT(patch->new_executable_p != svn_tristate_unknown);
+#endif
+
+  /* Don't touch patch->operation. */
+
+  *new_state = state_git_mode_seen;
+  return SVN_NO_ERROR;
+}
+
 /* Parse the 'rename from ' line of a git extended unidiff. */
 static svn_error_t *
 git_move_from(enum parse_state *new_state, char *line, svn_patch_t *patch,
@@ -1232,6 +1728,10 @@ static svn_error_t *
 git_new_file(enum parse_state *new_state, char *line, svn_patch_t *patch,
              apr_pool_t *result_pool, apr_pool_t *scratch_pool)
 {
+  SVN_ERR(
+    parse_bits_into_executability(&patch->new_executable_p,
+                                  line + STRLEN_LITERAL("new file mode ")));
+
   patch->operation = svn_diff_op_added;
 
   /* Filename already retrieved from diff --git header. */
@@ -1245,6 +1745,10 @@ static svn_error_t *
 git_deleted_file(enum parse_state *new_state, char *line, svn_patch_t *patch,
                  apr_pool_t *result_pool, apr_pool_t *scratch_pool)
 {
+  SVN_ERR(
+    parse_bits_into_executability(&patch->old_executable_p,
+                                  line + STRLEN_LITERAL("deleted file mode ")));
+
   patch->operation = svn_diff_op_deleted;
 
   /* Filename already retrieved from diff --git header. */
@@ -1379,6 +1883,7 @@ parse_hunks(svn_patch_t *patch, apr_file
 
 static svn_error_t *
 parse_binary_patch(svn_patch_t *patch, apr_file_t *apr_file,
+                   svn_boolean_t reverse,
                    apr_pool_t *result_pool, apr_pool_t *scratch_pool)
 {
   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
@@ -1387,7 +1892,9 @@ parse_binary_patch(svn_patch_t *patch, a
   svn_boolean_t eof = FALSE;
   svn_diff_binary_patch_t *bpatch = apr_pcalloc(result_pool, sizeof(*bpatch));
   svn_boolean_t in_blob = FALSE;
-  svn_boolean_t in_dst = FALSE;
+  svn_boolean_t in_src = FALSE;
+
+  bpatch->apr_file = apr_file;
 
   patch->operation = svn_diff_op_modified;
   patch->prop_patches = apr_hash_make(result_pool);
@@ -1410,23 +1917,23 @@ parse_binary_patch(svn_patch_t *patch, a
           char c = line->data[0];
 
           /* 66 = len byte + (52/4*5) chars */
-          if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') 
+          if (((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
               && line->len <= 66
               && !strchr(line->data, ':')
               && !strchr(line->data, ' '))
             {
               /* One more blop line */
-              if (in_dst)
-                bpatch->dst_end = pos;
-              else
+              if (in_src)
                 bpatch->src_end = pos;
+              else
+                bpatch->dst_end = pos;
             }
           else if (svn_stringbuf_first_non_whitespace(line) < line->len
-                   && !(in_dst && bpatch->dst_start < last_line))
+                   && !(in_src && bpatch->src_start < last_line))
             {
               break; /* Bad patch */
             }
-          else if (in_dst)
+          else if (in_src)
             {
               patch->binary_patch = bpatch; /* SUCCESS! */
               break; 
@@ -1434,7 +1941,7 @@ parse_binary_patch(svn_patch_t *patch, a
           else
             {
               in_blob = FALSE;
-              in_dst = TRUE;
+              in_src = TRUE;
             }
         }
       else if (starts_with(line->data, "literal "))
@@ -1450,15 +1957,15 @@ parse_binary_patch(svn_patch_t *patch, a
               break;
             }
 
-          if (in_dst)
+          if (in_src)
             {
-              bpatch->dst_start = pos;
-              bpatch->dst_filesize = expanded_size;
+              bpatch->src_start = pos;
+              bpatch->src_filesize = expanded_size;
             }
           else
             {
-              bpatch->src_start = pos;
-              bpatch->src_filesize = expanded_size;
+              bpatch->dst_start = pos;
+              bpatch->dst_filesize = expanded_size;
             }
           in_blob = TRUE;
         }
@@ -1471,12 +1978,28 @@ parse_binary_patch(svn_patch_t *patch, a
     /* Rewind to the start of the line just read, so subsequent calls
      * don't end up skipping the line. It may contain a patch or hunk header.*/
     SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &last_line, scratch_pool));
-  else if (in_dst
-           && ((bpatch->dst_end > bpatch->dst_start) || !bpatch->dst_filesize))
+  else if (in_src
+           && ((bpatch->src_end > bpatch->src_start) || !bpatch->src_filesize))
     {
       patch->binary_patch = bpatch; /* SUCCESS */
     }
 
+  /* Reverse patch if requested */
+  if (reverse && patch->binary_patch)
+    {
+      apr_off_t tmp_start = bpatch->src_start;
+      apr_off_t tmp_end = bpatch->src_end;
+      svn_filesize_t tmp_filesize = bpatch->src_filesize;
+
+      bpatch->src_start = bpatch->dst_start;
+      bpatch->src_end = bpatch->dst_end;
+      bpatch->src_filesize = bpatch->dst_filesize;
+
+      bpatch->dst_start = tmp_start;
+      bpatch->dst_end = tmp_end;
+      bpatch->dst_filesize = tmp_filesize;
+    }
+
   return SVN_NO_ERROR;
 }
 
@@ -1489,15 +2012,22 @@ static struct transition transitions[] =
 
   {"diff --git",        state_start,            git_start},
   {"--- a/",            state_git_diff_seen,    git_minus},
+  {"--- a/",            state_git_mode_seen,    git_minus},
   {"--- a/",            state_git_tree_seen,    git_minus},
+  {"--- /dev/null",     state_git_mode_seen,    git_minus},
   {"--- /dev/null",     state_git_tree_seen,    git_minus},
   {"+++ b/",            state_git_minus_seen,   git_plus},
   {"+++ /dev/null",     state_git_minus_seen,   git_plus},
 
+  {"old mode ",         state_git_diff_seen,    git_old_mode},
+  {"new mode ",         state_old_mode_seen,    git_new_mode},
+
   {"rename from ",      state_git_diff_seen,    git_move_from},
+  {"rename from ",      state_git_mode_seen,    git_move_from},
   {"rename to ",        state_move_from_seen,   git_move_to},
 
   {"copy from ",        state_git_diff_seen,    git_copy_from},
+  {"copy from ",        state_git_mode_seen,    git_copy_from},
   {"copy to ",          state_copy_from_seen,   git_copy_to},
 
   {"new file ",         state_git_diff_seen,    git_new_file},
@@ -1505,6 +2035,7 @@ static struct transition transitions[] =
   {"deleted file ",     state_git_diff_seen,    git_deleted_file},
 
   {"GIT binary patch",  state_git_diff_seen,    binary_patch_start},
+  {"GIT binary patch",  state_git_tree_seen,    binary_patch_start},
 };
 
 svn_error_t *
@@ -1530,6 +2061,8 @@ svn_diff_parse_next_patch(svn_patch_t **
     }
 
   patch = apr_pcalloc(result_pool, sizeof(*patch));
+  patch->old_executable_p = svn_tristate_unknown;
+  patch->new_executable_p = svn_tristate_unknown;
 
   pos = patch_file->next_patch_offset;
   SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &pos, scratch_pool));
@@ -1576,7 +2109,9 @@ svn_diff_parse_next_patch(svn_patch_t **
           /* We have a valid diff header, yay! */
           break;
         }
-      else if (state == state_git_tree_seen && line_after_tree_header_read)
+      else if ((state == state_git_tree_seen || state == state_git_mode_seen)
+               && line_after_tree_header_read
+               && !valid_header_line)
         {
           /* git patches can contain an index line after the file mode line */
           if (!starts_with(line->data, "index "))
@@ -1590,7 +2125,8 @@ svn_diff_parse_next_patch(svn_patch_t **
             break;
           }
         }
-      else if (state == state_git_tree_seen)
+      else if (state == state_git_tree_seen
+               || state == state_git_mode_seen)
         {
           line_after_tree_header_read = TRUE;
         }
@@ -1614,9 +2150,31 @@ svn_diff_parse_next_patch(svn_patch_t **
   if (reverse)
     {
       const char *temp;
+      svn_tristate_t ts_tmp;
+
       temp = patch->old_filename;
       patch->old_filename = patch->new_filename;
       patch->new_filename = temp;
+
+      switch (patch->operation)
+        {
+          case svn_diff_op_added:
+            patch->operation = svn_diff_op_deleted;
+            break;
+          case svn_diff_op_deleted:
+            patch->operation = svn_diff_op_added;
+            break;
+
+          /* ### case svn_diff_op_copied:
+             ### case svn_diff_op_moved:*/
+
+          case svn_diff_op_modified:
+            break; /* Stays modify */
+        }
+
+      ts_tmp = patch->old_executable_p;
+      patch->old_executable_p = patch->new_executable_p;
+      patch->new_executable_p = ts_tmp;
     }
 
   if (patch->old_filename == NULL || patch->new_filename == NULL)
@@ -1628,7 +2186,7 @@ svn_diff_parse_next_patch(svn_patch_t **
     {
       if (state == state_binary_patch_found)
         {
-          SVN_ERR(parse_binary_patch(patch, patch_file->apr_file,
+          SVN_ERR(parse_binary_patch(patch, patch_file->apr_file, reverse,
                                      result_pool, iterpool));
           /* And fall through in property parsing */
         }

Modified: subversion/branches/move-tracking-2/subversion/libsvn_fs_base/lock.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_fs_base/lock.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_fs_base/lock.c (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_fs_base/lock.c Mon Sep 28 16:03:14 2015
@@ -108,7 +108,7 @@ txn_body_lock(void *baton, trail_t *trai
   SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool));
 
   /* Until we implement directory locks someday, we only allow locks
-     on files or non-existent paths. */
+     on files. */
   if (kind == svn_node_dir)
     return SVN_FS__ERR_NOT_FILE(trail->fs, args->path);
 

Modified: subversion/branches/move-tracking-2/subversion/libsvn_fs_fs/fs_fs.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_fs_fs/fs_fs.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_fs_fs/fs_fs.c (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_fs_fs/fs_fs.c Mon Sep 28 16:03:14 2015
@@ -1485,14 +1485,27 @@ svn_fs_fs__prop_rep_equal(svn_boolean_t
       && !svn_fs_fs__id_txn_used(&rep_a->txn_id)
       && !svn_fs_fs__id_txn_used(&rep_b->txn_id))
     {
-      /* MD5 must be given. Having the same checksum is good enough for
-         accepting the prop lists as equal. */
-      *equal = memcmp(rep_a->md5_digest, rep_b->md5_digest,
-                      sizeof(rep_a->md5_digest)) == 0;
-      return SVN_NO_ERROR;
+      /* Same representation? */
+      if (   (rep_a->revision == rep_b->revision)
+          && (rep_a->item_index == rep_b->item_index))
+        {
+          *equal = TRUE;
+          return SVN_NO_ERROR;
+        }
+
+      /* Known different content? MD5 must be given. */
+      if (memcmp(rep_a->md5_digest, rep_b->md5_digest,
+                 sizeof(rep_a->md5_digest)))
+        {
+          *equal = FALSE;
+          return SVN_NO_ERROR;
+        }
     }
 
-  /* Same path in same txn? */
+  /* Same path in same txn?
+   *
+   * For committed reps, IDs cannot be the same here b/c we already know
+   * that they point to different representations. */
   if (svn_fs_fs__id_eq(a->id, b->id))
     {
       *equal = TRUE;

Modified: subversion/branches/move-tracking-2/subversion/libsvn_fs_fs/lock.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_fs_fs/lock.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_fs_fs/lock.c (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_fs_fs/lock.c Mon Sep 28 16:03:14 2015
@@ -847,7 +847,7 @@ lock_body(void *baton, apr_pool_t *pool)
   apr_pool_t *iterpool = svn_pool_create(pool);
 
   /* Until we implement directory locks someday, we only allow locks
-     on files or non-existent paths. */
+     on files. */
   /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
      library dependencies, which are not portable. */
   SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));

Modified: subversion/branches/move-tracking-2/subversion/libsvn_fs_fs/pack.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_fs_fs/pack.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_fs_fs/pack.c (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_fs_fs/pack.c Mon Sep 28 16:03:14 2015
@@ -610,9 +610,6 @@ compare_dir_entries_format7(const svn_so
   const svn_fs_dirent_t *lhs = (const svn_fs_dirent_t *) a->value;
   const svn_fs_dirent_t *rhs = (const svn_fs_dirent_t *) b->value;
 
-  if (lhs->kind != rhs->kind)
-    return lhs->kind == svn_node_dir ? -1 : 1;
-
   return strcmp(lhs->name, rhs->name);
 }
 
@@ -810,15 +807,6 @@ compare_ref_to_item(const reference_t *
   return svn_fs_fs__id_part_compare(&(*lhs_p)->from, rhs_p);
 }
 
-/* implements compare_fn_t.  Finds the DIR / FILE boundary.
- */
-static int
-compare_is_dir(const path_order_t * const * lhs_p,
-               const void *unused)
-{
-  return (*lhs_p)->is_dir ? -1 : 0;
-}
-
 /* Look for the least significant bit set in VALUE and return the smallest
  * number with the same property, i.e. the largest power of 2 that is a
  * factor in VALUE. */
@@ -966,7 +954,7 @@ sort_reps(pack_context_t *context)
 {
   apr_pool_t *temp_pool;
   const path_order_t **temp, **path_order;
-  int i, count, dir_count;
+  int i, count;
 
   /* We will later assume that there is at least one node / path.
    */
@@ -991,13 +979,8 @@ sort_reps(pack_context_t *context)
   temp = apr_pcalloc(temp_pool, count * sizeof(*temp));
   path_order = (void *)context->path_order->elts;
 
-  /* Find the boundary between DIR and FILE section. */
-  dir_count = svn_sort__bsearch_lower_bound(context->path_order, NULL,
-                     (int (*)(const void *, const void *))compare_is_dir);
-
   /* Sort those sub-sections separately. */
-  sort_reps_range(context, path_order, temp, 0, dir_count);
-  sort_reps_range(context, path_order, temp, dir_count, count);
+  sort_reps_range(context, path_order, temp, 0, count);
 
   /* We now know the final ordering. */
   for (i = 0; i < count; ++i)

Propchange: subversion/branches/move-tracking-2/subversion/libsvn_fs_x/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Mon Sep 28 16:03:14 2015
@@ -61,6 +61,7 @@
 /subversion/branches/multi-layer-moves/subversion/libsvn_fs_x:1239019-1300930
 /subversion/branches/nfc-nfd-aware-client/subversion/libsvn_fs_x:870276,870376
 /subversion/branches/node_pool/subversion/libsvn_fs_x:1304828-1305388
+/subversion/branches/patch-exec/subversion/libsvn_fs_x:1692717-1705390
 /subversion/branches/performance/subversion/libsvn_fs_x:979193,980118,981087,981090,981189,981194,981287,981684,981827,982043,982355,983398,983406,983430,983474,983488,983490,983760,983764,983766,983770,984927,984973,984984,985014,985037,985046,985472,985477,985482,985487-985488,985493,985497,985500,985514,985601,985603,985606,985669,985673,985695,985697,986453,986465,986485,986491-986492,986517,986521,986605,986608,986817,986832,987865,987868-987869,987872,987886-987888,987893,988319,988898,990330,990533,990535-990537,990541,990568,990572,990574-990575,990600,990759,992899,992904,992911,993127,993141,994956,995478,995507,995603,998012,998858,999098,1001413,1001417,1004291,1022668,1022670,1022676,1022715,1022719,1025660,1025672,1027193,1027203,1027206,1027214,1027227,1028077,1028092,1028094,1028104,1028107,1028111,1028354,1029038,1029042-1029043,1029054-1029055,1029062-1029063,1029078,1029080,1029090,1029092-1029093,1029111,1029151,1029158,1029229-1029230,1029232,1029335-1029336,102
 9339-1029340,1029342,1029344,1030763,1030827,1031203,1031235,1032285,1032333,1033040,1033057,1033294,1035869,1035882,1039511,1043705,1053735,1056015,1066452,1067683,1067697-1078365
 /subversion/branches/pin-externals/subversion/libsvn_fs_x:1643757-1659392
 /subversion/branches/py-tests-as-modules/subversion/libsvn_fs_x:956579-1033052
@@ -93,4 +94,4 @@
 /subversion/branches/verify-keep-going/subversion/libsvn_fs_x:1439280-1492639,1546002-1546110
 /subversion/branches/wc-collate-path/subversion/libsvn_fs_x:1402685-1480384
 /subversion/trunk/subversion/libsvn_fs_fs:1415133-1596500,1596567,1597414,1597989,1598273,1599140,1600872,1601633,1603485-1603487,1603499,1603605,1604128,1604188,1604413-1604414,1604416-1604417,1604421,1604442,1604700,1604717,1604720,1604726,1604755,1604794,1604802,1604824,1604836,1604844,1604902-1604903,1604911,1604925,1604933,1604947,1605059-1605060,1605064-1605065,1605068,1605071-1605073,1605075,1605123,1605188-1605189,1605191,1605197,1605444,1605633,1606132,1606142,1606144,1606514,1606526,1606528,1606551,1606554,1606564,1606598-1606599,1606656,1606658,1606662,1606744,1606840,1607085,1607572,1612407,1612810,1613339,1613872,1614611,1615348,1615351-1615352,1615356,1616338-1616339,1616613,1617586,1617688,1618138,1618151,1618153,1618226,1618641,1618653,1618662,1619068,1619358,1619413,1619769,1619774,1620602,1620909,1620912,1620928,1620930,1621275,1621635,1622931,1622937,1622942,1622946,1622959-1622960,1622963,1622987,1623007,1623368,1623373,1623377,1623379,1623381,1623398,1623402,162
 4011,1624265,1624512,1626246,1626871,1626873,1626886,1627497-1627498,1627502,1627947-1627949,1627966,1628083,1628093,1628158-1628159,1628161,1628392-1628393,1628415,1628427,1628676,1628738,1628762,1628764,1629854-1629855,1629857,1629865,1629873,1629875,1629879,1630067,1630070,1631049-1631051,1631075,1631115,1631171,1631180,1631185-1631186,1631196-1631197,1631239-1631240,1631548,1631550,1631563,1631567,1631588,1631598,1632646,1632776,1632849,1632851-1632853,1632856-1632857,1632868,1632908,1632926,1633232,1633617-1633618,1634872,1634875,1634879-1634880,1634920,1636478,1636483,1636629,1636644,1637184,1637186,1637330,1637358,1637363,1637393,1639319,1639322,1639335,1639348,1639352,1639355,1639358,1639414,1639419,1639426,1639430,1639436,1639440,1639549,1640061-1640062,1640197,1640915,1640966,1641013,1643139,1643233,1645567,1646021,1646712,1646716,1647537,1647540-1647541,1647820,1647905,1648230,1648238,1648241-1648243,1648253,1648272,1648532,1648537-1648539,1648542,1648591,1648612,1653608,
 1658482
-/subversion/trunk/subversion/libsvn_fs_x:1414756-1509914,1606692-1704317
+/subversion/trunk/subversion/libsvn_fs_x:1414756-1509914,1606692-1705711

Modified: subversion/branches/move-tracking-2/subversion/libsvn_fs_x/lock.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_fs_x/lock.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_fs_x/lock.c (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_fs_x/lock.c Mon Sep 28 16:03:14 2015
@@ -868,7 +868,7 @@ lock_body(void *baton,
   apr_pool_t *iterpool = svn_pool_create(pool);
 
   /* Until we implement directory locks someday, we only allow locks
-     on files or non-existent paths. */
+     on files. */
   /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
      library dependencies, which are not portable. */
   SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));

Modified: subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/options.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/options.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/options.c (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/options.c Mon Sep 28 16:03:14 2015
@@ -226,6 +226,12 @@ capabilities_headers_iterator_callback(v
         {
           session->supports_rev_rsrc_replay = TRUE;
         }
+      if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF1, vals))
+        {
+          /* Use compressed svndiff1 format for servers that properly
+             advertise this capability (Subversion 1.10 and greater). */
+          session->supports_svndiff1 = TRUE;
+        }
     }
 
   /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
@@ -243,7 +249,8 @@ capabilities_headers_iterator_callback(v
           apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
         }
 
-      /* Use compressed svndiff1 format for servers that speak HTTPv2.
+      /* Use compressed svndiff1 format for servers that speak HTTPv2,
+         in addition to servers that send SVN_DAV_NS_DAV_SVN_SVNDIFF1.
 
          Apache HTTPd + mod_dav_svn servers support svndiff1, beginning
          from Subversion 1.4, but they do not advertise this capability.

Modified: subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/ra_serf.h
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/ra_serf.h?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/ra_serf.h (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/ra_serf.h Mon Sep 28 16:03:14 2015
@@ -1548,6 +1548,17 @@ svn_ra_serf__create_bucket_with_eagain(c
                                        apr_size_t len,
                                        serf_bucket_alloc_t *allocator);
 
+/* Parse a given URL_STR, fill in all supplied fields of URI
+ * structure.
+ *
+ * This function is a compatibility wrapper around apr_uri_parse().
+ * Different apr-util versions set apr_uri_t.path to either NULL or ""
+ * for root paths, and serf expects to see "/". This function always
+ * sets URI.path to "/" for these paths. */
+svn_error_t *
+svn_ra_serf__uri_parse(apr_uri_t *uri,
+                       const char *url_str,
+                       apr_pool_t *result_pool);
 
 
 #if defined(SVN_DEBUG)

Modified: subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/serf.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/serf.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/serf.c (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/serf.c Mon Sep 28 16:03:14 2015
@@ -64,7 +64,7 @@ ra_serf_version(void)
 
 #define RA_SERF_DESCRIPTION_VER \
     N_("Module for accessing a repository via WebDAV protocol using serf.\n" \
-       "  - using serf %d.%d.%d")
+       "  - using serf %d.%d.%d (compiled with %d.%d.%d)")
 
 /* Implements svn_ra__vtable_t.get_description(). */
 static const char *
@@ -73,7 +73,12 @@ ra_serf_get_description(apr_pool_t *pool
   int major, minor, patch;
 
   serf_lib_version(&major, &minor, &patch);
-  return apr_psprintf(pool, _(RA_SERF_DESCRIPTION_VER), major, minor, patch);
+  return apr_psprintf(pool, _(RA_SERF_DESCRIPTION_VER),
+                      major, minor, patch,
+                      SERF_MAJOR_VERSION,
+                      SERF_MINOR_VERSION,
+                      SERF_PATCH_VERSION
+                      );
 }
 
 /* Implements svn_ra__vtable_t.get_schemes(). */
@@ -508,19 +513,8 @@ svn_ra_serf__open(svn_ra_session_t *sess
                                        serf_sess->pool));
 
 
-  status = apr_uri_parse(serf_sess->pool, session_URL, &url);
-  if (status)
-    {
-      return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
-                               _("Illegal URL '%s'"),
-                               session_URL);
-    }
-  /* Depending the version of apr-util in use, for root paths url.path
-     will be NULL or "", where serf requires "/". */
-  if (url.path == NULL || url.path[0] == '\0')
-    {
-      url.path = apr_pstrdup(serf_sess->pool, "/");
-    }
+  SVN_ERR(svn_ra_serf__uri_parse(&url, session_URL, serf_sess->pool));
+
   if (!url.port)
     {
       url.port = apr_uri_port_of_scheme(url.scheme);
@@ -740,18 +734,15 @@ ra_serf_dup_session(svn_ra_session_t *ne
 
   new_sess->repos_root_str = apr_pstrdup(result_pool,
                                          new_sess->repos_root_str);
-  status = apr_uri_parse(result_pool, new_sess->repos_root_str,
-                         &new_sess->repos_root);
-  if (status)
-    return svn_ra_serf__wrap_err(status, NULL);
+  SVN_ERR(svn_ra_serf__uri_parse(&new_sess->repos_root,
+                                 new_sess->repos_root_str,
+                                 result_pool));
 
   new_sess->session_url_str = apr_pstrdup(result_pool, new_session_url);
 
-  status = apr_uri_parse(result_pool, new_sess->session_url_str,
-                         &new_sess->session_url);
-
-  if (status)
-    return svn_ra_serf__wrap_err(status, NULL);
+  SVN_ERR(svn_ra_serf__uri_parse(&new_sess->session_url,
+                                 new_sess->session_url_str,
+                                 result_pool));
 
   /* svn_boolean_t supports_inline_props */
   /* supports_rev_rsrc_replay */
@@ -800,7 +791,6 @@ svn_ra_serf__reparent(svn_ra_session_t *
 {
   svn_ra_serf__session_t *session = ra_session->priv;
   apr_uri_t new_url;
-  apr_status_t status;
 
   /* If it's the URL we already have, wave our hands and do nothing. */
   if (strcmp(session->session_url_str, url) == 0)
@@ -822,25 +812,11 @@ svn_ra_serf__reparent(svn_ra_session_t *
             "URL '%s'"), url, session->repos_root_str);
     }
 
-  status = apr_uri_parse(pool, url, &new_url);
-  if (status)
-    {
-      return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
-                               _("Illegal repository URL '%s'"), url);
-    }
+  SVN_ERR(svn_ra_serf__uri_parse(&new_url, url, pool));
 
-  /* Depending the version of apr-util in use, for root paths url.path
-     will be NULL or "", where serf requires "/". */
   /* ### Maybe we should use a string buffer for these strings so we
      ### don't allocate memory in the session on every reparent? */
-  if (new_url.path == NULL || new_url.path[0] == '\0')
-    {
-      session->session_url.path = apr_pstrdup(session->pool, "/");
-    }
-  else
-    {
-      session->session_url.path = apr_pstrdup(session->pool, new_url.path);
-    }
+  session->session_url.path = apr_pstrdup(session->pool, new_url.path);
   session->session_url_str = apr_pstrdup(session->pool, url);
 
   return SVN_NO_ERROR;

Modified: subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/util.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/util.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/util.c (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_ra_serf/util.c Mon Sep 28 16:03:14 2015
@@ -1951,3 +1951,29 @@ svn_ra_serf__create_handler(svn_ra_serf_
   return handler;
 }
 
+svn_error_t *
+svn_ra_serf__uri_parse(apr_uri_t *uri,
+                       const char *url_str,
+                       apr_pool_t *result_pool)
+{
+  apr_status_t status;
+
+  status = apr_uri_parse(result_pool, url_str, uri);
+  if (status)
+    {
+      /* Do not use returned error status in error message because currently
+         apr_uri_parse() returns APR_EGENERAL for all parsing errors. */
+      return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+                               _("Illegal URL '%s'"),
+                               url_str);
+    }
+
+  /* Depending the version of apr-util in use, for root paths uri.path
+     will be NULL or "", where serf requires "/". */
+  if (uri->path == NULL || uri->path[0] == '\0')
+    {
+      uri->path = apr_pstrdup(result_pool, "/");
+    }
+
+  return SVN_NO_ERROR;
+}

Modified: subversion/branches/move-tracking-2/subversion/libsvn_subr/auth.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_subr/auth.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_subr/auth.c (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_subr/auth.c Mon Sep 28 16:03:14 2015
@@ -370,7 +370,9 @@ svn_auth_next_credentials(void **credent
       if (creds != NULL)
         {
           /* Put the creds in the cache */
-          svn_hash_sets(auth_baton->creds_cache, state->cache_key, creds);
+          svn_hash_sets(auth_baton->creds_cache,
+                        apr_pstrdup(auth_baton->pool, state->cache_key),
+                        creds);
           break;
         }
 

Modified: subversion/branches/move-tracking-2/subversion/libsvn_subr/win32_crashrpt.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_subr/win32_crashrpt.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_subr/win32_crashrpt.c (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_subr/win32_crashrpt.c Mon Sep 28 16:03:14 2015
@@ -53,9 +53,9 @@ HANDLE dbghelp_dll = INVALID_HANDLE_VALU
 #define LOGFILE_PREFIX "svn-crash-log"
 
 #if defined(_M_IX86)
-#define FORMAT_PTR "0x%08x"
+#define FORMAT_PTR "0x%08Ix"
 #elif defined(_M_X64)
-#define FORMAT_PTR "0x%016I64x"
+#define FORMAT_PTR "0x%016Ix"
 #endif
 
 /*** Code. ***/
@@ -171,7 +171,7 @@ write_module_info_callback(void *data,
       MINIDUMP_MODULE_CALLBACK module = callback_input->Module;
 
       char *buf = convert_wbcs_to_ansi(module.FullPath);
-      fprintf(log_file, FORMAT_PTR, module.BaseOfImage);
+      fprintf(log_file, FORMAT_PTR, (INT_PTR)module.BaseOfImage);
       fprintf(log_file, "  %s", buf);
       free(buf);
 
@@ -302,7 +302,7 @@ format_basic_type(char *buf, DWORD basic
         break;
       default:
         sprintf(buf, "[unhandled type 0x%08x of length " FORMAT_PTR "]",
-                     basic_type, length);
+                     basic_type, (INT_PTR)length);
         break;
     }
 }
@@ -341,7 +341,7 @@ format_value(char *value_str, DWORD64 mo
 
               if (ptr == 0)
                 sprintf(value_str, "(%s) " FORMAT_PTR,
-                        type_name, (DWORD_PTR *)value_addr);
+                        type_name, (INT_PTR)(DWORD_PTR *)value_addr);
               else if (ptr == 1)
                 sprintf(value_str, "(%s *) " FORMAT_PTR,
                         type_name, *(DWORD_PTR *)value_addr);

Modified: subversion/branches/move-tracking-2/subversion/mod_dav_svn/version.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/mod_dav_svn/version.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/mod_dav_svn/version.c (original)
+++ subversion/branches/move-tracking-2/subversion/mod_dav_svn/version.c Mon Sep 28 16:03:14 2015
@@ -152,6 +152,7 @@ get_vsn_options(apr_pool_t *p, apr_text_
   apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_INHERITED_PROPS);
   apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_INLINE_PROPS);
   apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS);
+  apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_SVNDIFF1);
   /* Mergeinfo is a special case: here we merely say that the server
    * knows how to handle mergeinfo -- whether the repository does too
    * is a separate matter.

Modified: subversion/branches/move-tracking-2/subversion/svn/notify.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/svn/notify.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/svn/notify.c (original)
+++ subversion/branches/move-tracking-2/subversion/svn/notify.c Mon Sep 28 16:03:14 2015
@@ -415,8 +415,10 @@ notify_body(struct notify_baton *nb,
             store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
             statchar_buf[1] = 'C';
           }
+        else if (n->prop_state == svn_wc_notify_state_merged)
+          statchar_buf[1] = 'G';
         else if (n->prop_state == svn_wc_notify_state_changed)
-              statchar_buf[1] = 'U';
+          statchar_buf[1] = 'U';
 
         if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ')
           {

Modified: subversion/branches/move-tracking-2/subversion/tests/cmdline/patch_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/tests/cmdline/patch_tests.py?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/tests/cmdline/patch_tests.py (original)
+++ subversion/branches/move-tracking-2/subversion/tests/cmdline/patch_tests.py Mon Sep 28 16:03:14 2015
@@ -2534,6 +2534,7 @@ def patch_dir_properties(sbox):
   expected_output = [
     ' U        %s\n' % wc_dir,
     ' C        %s\n' % sbox.ospath('A/B'),
+    '>         rejected hunk ## -0,0 +1,1 ## (svn:executable)\n',
   ] + svntest.main.summary_of_conflicts(prop_conflicts=1)
 
   expected_disk = svntest.main.greek_state.copy()
@@ -3451,6 +3452,7 @@ def patch_add_symlink(sbox):
     "Added: svn:special\n",
     "## -0,0 +1 ##\n",
     "+*\n",
+    "+\\ No newline at end of property\n"
   ]
 
   svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))
@@ -3647,7 +3649,7 @@ def patch_lacking_trailing_eol(sbox):
 
   # Expect a newline to be appended
   expected_disk = svntest.main.greek_state.copy()
-  expected_disk.tweak('iota', contents=iota_contents + "Some more bytes")
+  expected_disk.tweak('iota', contents=iota_contents + "Some more bytes\n")
 
   expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
   expected_status.tweak('iota', status='M ')
@@ -4559,7 +4561,6 @@ def patch_apply_no_fuz(sbox):
   if not filecmp.cmp(sbox.ospath('test.txt'), sbox.ospath('test_v2.txt')):
     raise svntest.Failure("Patch result not identical")
 
-@XFail()
 def patch_lacking_trailing_eol_on_context(sbox):
   "patch file lacking trailing eol on context"
 
@@ -5620,7 +5621,6 @@ def patch_obstructing_symlink_traversal(
                                        expected_output, expected_disk,
                                        expected_status, expected_skip)
 
-@XFail()
 def patch_binary_file(sbox):
   "patch a binary file"
 
@@ -5637,13 +5637,13 @@ def patch_binary_file(sbox):
     '===================================================================\n',
     'diff --git a/iota b/iota\n',
     'GIT binary patch\n',
-    'literal 25\n',
-    'ec$^E#$ShU>qLPeMg|y6^R0Z|S{E|d<JuU!m{s;*G\n',
-    '\n',
     'literal 48\n',
     'zc$^E#$ShU>qLPeMg|y6^R0Z|S{E|d<JuZf(=9bpB_PpZ!+|-hc%)E52)STkf{{Wp*\n',
     'B5)uFa\n',
     '\n',
+    'literal 25\n',
+    'ec$^E#$ShU>qLPeMg|y6^R0Z|S{E|d<JuU!m{s;*G\n',
+    '\n',
     'Property changes on: iota\n',
     '___________________________________________________________________\n',
     'Added: svn:mime-type\n',
@@ -5664,16 +5664,694 @@ def patch_binary_file(sbox):
   expected_output = wc.State(wc_dir, {
     'iota'              : Item(status='UU'),
   })
-  expected_disk = None
+  expected_disk = svntest.main.greek_state.copy()
+  expected_disk.tweak('iota',
+                      props={'svn:mime-type':'application/binary'},
+                      contents =
+                      'This is the file \'iota\'.\n'
+                      '\0\202\203\204\205\206\207nsomething\nelse\xFF')
   expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
   expected_status.tweak('iota', status='MM')
   expected_skip = wc.State('', { })
 
   svntest.actions.run_and_verify_patch(wc_dir, tmp,
                                        expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], True, True)
+
+  # Ok, now try applying it backwards
+  expected_output.tweak('iota', status='GU')
+  expected_disk = svntest.main.greek_state.copy()
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  svntest.actions.run_and_verify_patch(wc_dir, tmp,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], False, True, '--reverse-diff')
+
+def patch_delete_nodes(sbox):
+  "apply deletes via patch"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+
+  sbox.simple_propset('A', 'B', 'A/B/E/alpha')
+  sbox.simple_append('A/mu', '\0')
+  sbox.simple_propset('svn:mime-type', 'application/nonsense', 'A/mu')
+
+  sbox.simple_commit() # r2
+  sbox.simple_update()
+
+  expected_skip = wc.State('', { })
+
+  original_status = svntest.actions.get_virginal_state(wc_dir, 2)
+  original_disk = svntest.main.greek_state.copy()
+  original_disk.tweak('A/mu',
+                      props={'svn:mime-type':'application/nonsense'},
+                      contents = 'This is the file \'mu\'.\n\0')
+  original_disk.tweak('A/B/E/alpha', props={'A':'B'})
+  svntest.actions.run_and_verify_status(wc_dir, original_status)
+  svntest.actions.verify_disk(wc_dir, original_disk, True)
+
+  sbox.simple_rm('A/B/E/alpha', 'A/B/E/beta', 'A/mu')
+
+  _, diff, _ = svntest.actions.run_and_verify_svn(None, [],
+                                                  'diff', '--git', wc_dir)
+
+  patch = sbox.get_tempname('patch')
+  svntest.main.file_write(patch, ''.join(diff))
+
+  deleted_status = original_status.copy()
+  deleted_disk = original_disk.copy()
+  deleted_disk.remove('A/B/E/alpha', 'A/B/E/beta', 'A/mu')
+  deleted_status.tweak('A/B/E/alpha', 'A/B/E/beta', 'A/mu', status='D ')
+
+
+  svntest.actions.run_and_verify_status(wc_dir, deleted_status)
+  svntest.actions.verify_disk(wc_dir, deleted_disk, True)
+
+  # And now apply the patch from the clean state
+  sbox.simple_revert('A/B/E/alpha', 'A/B/E/beta', 'A/mu')
+
+  # Expect that the hint 'empty dir? -> delete dir' deletes 'E'
+  # ### A smarter diff format might change this in a future version
+  deleted_disk.remove('A/B/E')
+  deleted_status.tweak('A/B/E', status='D ')
+  expected_output = wc.State(wc_dir, {
+    'A/mu'              : Item(status='D '),
+    'A/B/E'             : Item(status='D '),
+    'A/B/E/beta'        : Item(status='D '),
+    'A/B/E/alpha'       : Item(status='D '),
+  })
+
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, deleted_disk,
+                                       deleted_status, expected_skip,
+                                       [], False, True)
+
+  # And let's see if we can apply the reverse version of the patch
+  expected_output = wc.State(wc_dir, {
+    'A/mu'              : Item(status='A '),
+    'A/B/E'             : Item(status='A '),
+    'A/B/E/beta'        : Item(status='A '),
+    'A/B/E/alpha'       : Item(status='A '),
+  })
+  original_status.tweak('A/mu', status='RM') # New file
+  original_status.tweak('A/B/E', status='R ') # New dir
+  original_status.tweak('A/B/E/alpha', 'A/B/E/beta',
+                        status='A ', wc_rev='-',
+                        entry_status='R ', entry_rev='2')
+
+
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, original_disk,
+                                       original_status, expected_skip,
+                                       [], True, True, '--reverse-diff')
+
+def patch_delete_missing_eol(sbox):
+  "apply a delete missing an eol"
+
+  sbox.build(read_only = True)
+  wc_dir = sbox.wc_dir
+
+  delete_patch = [
+    "Index: A/B/E/beta\n",
+    "===================================================================\n",
+    "--- A/B/E/beta	(revision 1)\n",
+    "+++ /dev/null\n",
+    "@@ -1 +0,0 @@\n",
+    "-This is the file 'beta'." # No final EOL
+  ]
+
+  patch = sbox.get_tempname('patch')
+  svntest.main.file_write(patch, ''.join(delete_patch))
+
+  expected_output = wc.State(wc_dir, {
+    'A/B/E/beta'        : Item(status='D '),
+  })
+  expected_skip = wc.State(wc_dir, {
+  })
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('A/B/E/beta', status='D ')
+  expected_disk = svntest.main.greek_state.copy()
+  expected_disk.remove('A/B/E/beta')
+
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], False, True)
+
+  # Try again? -> Skip... Why not some already applied notification?
+  #                       -> There is nothing to compare to
+  expected_output = wc.State(wc_dir, {
+  })
+  expected_skip = wc.State(wc_dir, {
+    'A/B/E/beta'        : Item(verb='Skipped'),
+  })
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], False, True)
+
+  # Reverse
+  expected_output = wc.State(wc_dir, {
+    'A/B/E/beta'        : Item(status='A '),
+  })
+  expected_skip = wc.State(wc_dir, {
+  })
+  expected_disk = svntest.main.greek_state.copy()
+  expected_status.tweak('A/B/E/beta', status='R ')
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], False, True, '--reverse-diff')
+
+  # Try again? -> Already applied
+  expected_output = wc.State(wc_dir, {
+    'A/B/E/beta'        : Item(status='G '),
+  })
+  expected_skip = wc.State(wc_dir, {
+  })
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], False, True, '--reverse-diff')
+
+def patch_final_eol(sbox):
+  "patch the final eol"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+
+  delete_patch = [
+   'Index: A/mu\n',
+   '===================================================================\n',
+   '--- A/mu\t(revision 1)\n',
+   '+++ A/mu\t(working copy)\n',
+   '@@ -1 +1 @@\n',
+   '-This is the file \'mu\'.\n',
+   '+This is the file \'mu\'.\n',
+   '\ No newline at end of file\n',
+   'Index: iota\n',
+   '===================================================================\n',
+   '--- iota\t(revision 1)\n',
+   '+++ iota\t(working copy)\n',
+   '@@ -1 +1 @@\n',
+   '-This is the file \'iota\'.\n',
+   '+This is the file \'iota\'.\n',
+   '\ No newline at end of file' # Missing EOL
+  ]
+
+  patch = sbox.get_tempname('patch')
+  # We explicitly use wb here as this is the eol type added later in the test
+  svntest.main.file_write(patch, ''.join(delete_patch), mode='wb')
+
+  expected_output = wc.State(wc_dir, {
+    'A/mu'        : Item(status='U '),
+    'iota'        : Item(status='U '),
+  })
+  expected_skip = wc.State(wc_dir, {})
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('iota', 'A/mu', status='M ')
+  expected_disk = svntest.main.greek_state.copy()
+  expected_disk.tweak('iota', contents="This is the file 'iota'.")
+  expected_disk.tweak('A/mu', contents="This is the file 'mu'.")
+
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], False, True)
+
+  # And again
+  expected_output.tweak('iota', 'A/mu', status='G ')
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], False, True)
+
+  # Reverse
+  expected_disk.tweak('iota', contents="This is the file 'iota'.\n")
+  expected_disk.tweak('A/mu', contents="This is the file 'mu'.\n")
+  expected_status.tweak('iota', 'A/mu', status='  ')
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], False, True, '--reverse-diff')
+
+  # And once more
+  expected_output.tweak('iota', 'A/mu', status='U ')
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], False, True, '--reverse-diff')
+
+  # Change the unmodified form
+  sbox.simple_append('iota', 'This is the file \'iota\'.', truncate=True)
+  sbox.simple_append('A/mu', 'This is the file \'mu\'.', truncate=True)
+  sbox.simple_commit()
+  expected_status.tweak('iota', 'A/mu', wc_rev='2')
+
+  add_patch = [
+    'Index: A/mu\n',
+    '===================================================================\n',
+    '--- A/mu\t(revision 2)\n',
+    '+++ A/mu\t(working copy)\n',
+    '@@ -1 +1 @@\n',
+    '-This is the file \'mu\'.\n',
+    '\ No newline at end of file\n',
+    '+This is the file \'mu\'.\n',
+    'Index: iota\n',
+    '===================================================================\n',
+    '--- iota\t(revision 2)\n',
+    '+++ iota\t(working copy)\n',
+    '@@ -1 +1 @@\n',
+    '-This is the file \'iota\'.\n',
+    '\ No newline at end of file\n',
+    '+This is the file \'iota\'.' # Missing eol
+  ]
+
+  svntest.main.file_write(patch, ''.join(add_patch), mode='wb')
+
+  # Apply the patch
+  expected_output.tweak('iota', 'A/mu', status='U ')
+  expected_disk.tweak('iota', contents="This is the file 'iota'.\n")
+  expected_disk.tweak('A/mu', contents="This is the file 'mu'.\n")
+  expected_status.tweak('iota', 'A/mu', status='M ')
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], False, True)
+
+  # And again
+  expected_output.tweak('iota', 'A/mu', status='G ')
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], False, True)
+
+  # And in reverse
+  expected_disk.tweak('iota', contents="This is the file 'iota'.")
+  expected_disk.tweak('A/mu', contents="This is the file 'mu'.")
+  expected_status.tweak('iota', 'A/mu', status='  ')
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], False, True, '--reverse-diff')
+
+  # And again
+  expected_output.tweak('iota', 'A/mu', status='U ')
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], False, True, '--reverse-diff')
+
+def patch_adds_executability_nocontents(sbox):
+  """patch adds svn:executable, without contents"""
+
+  sbox.build(read_only=True)
+  wc_dir = sbox.wc_dir
+
+  unidiff_patch = (
+    "diff --git a/iota b/iota\n"
+    "old mode 100644\n"
+    "new mode 100755\n"
+    )
+  patch_file_path = make_patch_path(sbox)
+  svntest.main.file_write(patch_file_path, unidiff_patch)
+
+  expected_output = wc.State(wc_dir, {
+    'iota' : Item(status=' U')
+  })
+  expected_disk = svntest.main.greek_state.copy()
+  # "*" is SVN_PROP_EXECUTABLE_VALUE aka SVN_PROP_BOOLEAN_TRUE
+  expected_disk.tweak('iota', props={'svn:executable': '*'})
+
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('iota', status=' M')
+
+  expected_skip = wc.State(wc_dir, { })
+
+  svntest.actions.run_and_verify_patch(wc_dir, os.path.abspath(patch_file_path),
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       check_props=True)
+
+  # And try it again
+  # This may produce different output but must have the same result
+  svntest.actions.run_and_verify_patch(wc_dir, os.path.abspath(patch_file_path),
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       check_props=True)
+
+  # And then try it in reverse
+  expected_disk.tweak('iota', props={})
+  expected_status.tweak('iota', status='  ')
+  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], True, True, '--reverse-diff')
+
+  # And try it again
+  # This may produce different output but must have the same result
+  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], True, True, '--reverse-diff')
+
+@XFail()
+def patch_adds_executability_nocontents2(sbox):
+  "patch adds svn:executable, without contents 2"
+
+  sbox.build(read_only=True)
+  wc_dir = sbox.wc_dir
+
+  unidiff_patch = (
+    "diff --git a/new b/new\n"
+    "old mode 100644\n"
+    "new mode 100755\n"
+    )
+  patch_file_path = make_patch_path(sbox)
+  svntest.main.file_write(patch_file_path, unidiff_patch)
+
+  expected_output = wc.State(wc_dir, {
+  })
+  expected_disk = svntest.main.greek_state.copy()
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('iota', status=' M')
+
+  expected_skip = wc.State(wc_dir, {
+    'new' : Item(verb='Skipped')
+  })
+
+  # This creates 'new', while a skip or reject is expected
+  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
+                                       expected_output, expected_disk,
                                        expected_status, expected_skip)
 
 
+def patch_adds_executability_yescontents(sbox):
+  """patch adds svn:executable, with contents"""
+
+  sbox.build(read_only=True)
+  wc_dir = sbox.wc_dir
+
+  mu_new_contents = (
+    "This is the file 'mu'.\n"
+    "with text mods too\n"
+    )
+
+  unidiff_patch = (
+    "diff --git a/A/mu b/A/mu\n"
+    "old mode 100644\n"
+    "new mode 100755\n"
+    "index 8a0f01c..dfad3ac\n"
+    "--- a/A/mu\n"
+    "+++ b/A/mu\n"
+    "@@ -1 +1,2 @@\n"
+    " This is the file 'mu'.\n"
+    "+with text mods too\n"
+    )
+  patch_file_path = make_patch_path(sbox)
+  svntest.main.file_write(patch_file_path, unidiff_patch)
+
+  expected_output = [
+    'UU        %s\n' % sbox.ospath('A/mu'),
+  ]
+  expected_disk = svntest.main.greek_state.copy()
+  # "*" is SVN_PROP_EXECUTABLE_VALUE aka SVN_PROP_BOOLEAN_TRUE
+  expected_disk.tweak('A/mu', props={'svn:executable': '*'})
+  expected_disk.tweak('A/mu', contents=mu_new_contents)
+
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('A/mu', status='MM')
+
+  expected_skip = wc.State('', { })
+
+  svntest.actions.run_and_verify_patch(wc_dir, os.path.abspath(patch_file_path),
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       check_props=True)
+
+def patch_deletes_executability(sbox):
+  """patch deletes svn:executable"""
+
+  sbox.build(read_only=True)
+  wc_dir = sbox.wc_dir
+
+  ## Set up the basic state.
+  sbox.simple_propset('svn:executable', 'yes', 'iota')
+  #sbox.simple_commit(target='iota', message="Make 'iota' executable.")
+
+  unidiff_patch = (
+    "diff --git a/iota b/iota\n"
+    "old mode 100755\n"
+    "new mode 100644\n"
+    )
+  patch_file_path = make_patch_path(sbox)
+  svntest.main.file_write(patch_file_path, unidiff_patch)
+
+  expected_output = [
+    ' U        %s\n' % sbox.ospath('iota'),
+  ]
+  expected_disk = svntest.main.greek_state.copy()
+  expected_disk.tweak('iota') # props=None by default
+
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('iota', status='  ')
+
+  expected_skip = wc.State('', { })
+
+  svntest.actions.run_and_verify_patch(wc_dir, os.path.abspath(patch_file_path),
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       check_props=True)
+
+def patch_ambiguous_executability_contradiction(sbox):
+  """patch ambiguous svn:executable, bad"""
+
+  sbox.build(read_only=True)
+  wc_dir = sbox.wc_dir
+
+  unidiff_patch = (
+    "Index: iota\n"
+    "===================================================================\n"
+    "diff --git a/iota b/iota\n"
+    "old mode 100755\n"
+    "new mode 100644\n"
+    "Property changes on: iota\n"
+    "-------------------------------------------------------------------\n"
+    "Added: svn:executable\n"
+    "## -0,0 +1 ##\n"
+    "+*\n"
+    )
+  patch_file_path = make_patch_path(sbox)
+  svntest.main.file_write(patch_file_path, unidiff_patch)
+
+  expected_output = []
+
+  expected_disk = svntest.main.greek_state.copy()
+
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+
+  expected_skip = wc.State('', { })
+
+  error_re_string = r'.*Invalid patch:.*contradicting.*mode.*svn:executable'
+  svntest.actions.run_and_verify_patch(wc_dir, os.path.abspath(patch_file_path),
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       error_re_string=error_re_string,
+                                       check_props=True)
+
+def patch_ambiguous_executability_consistent(sbox):
+  """patch ambiguous svn:executable, good"""
+
+  sbox.build(read_only=True)
+  wc_dir = sbox.wc_dir
+
+  unidiff_patch = (
+    "Index: iota\n"
+    "===================================================================\n"
+    "diff --git a/iota b/iota\n"
+    "old mode 100644\n"
+    "new mode 100755\n"
+    "Property changes on: iota\n"
+    "-------------------------------------------------------------------\n"
+    "Added: svn:executable\n"
+    "## -0,0 +1 ##\n"
+    "+*\n"
+    )
+  patch_file_path = make_patch_path(sbox)
+  svntest.main.file_write(patch_file_path, unidiff_patch)
+
+  expected_output = [
+    ' U        %s\n' % sbox.ospath('iota'),
+  ]
+
+  expected_disk = svntest.main.greek_state.copy()
+  expected_disk.tweak('iota', props={'svn:executable': '*'})
+
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('iota', status=' M')
+
+  expected_skip = wc.State('', { })
+
+  svntest.actions.run_and_verify_patch(wc_dir, os.path.abspath(patch_file_path),
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       error_re_string=None,
+                                       check_props=True)
+
+def patch_prop_madness(sbox):
+  "patch property madness"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+
+  sbox.simple_propset('mod_s', 'value\n',
+                      'iota', 'A/mu')
+
+  sbox.simple_propset('mod_s_n', 'no-eol',
+                      'iota', 'A/mu')
+
+  sbox.simple_propset('mod_l', 'this\nis\na\nvery\nvery\nlong\nvalue.\n',
+                      'iota', 'A/mu')
+
+  sbox.simple_propset('mod_l_n', 'this\nis\na\nvery\nvery\nlong\nvalue.\n'
+                      'without\neol', # No eol at end
+                      'iota', 'A/mu')
+
+  sbox.simple_propset('del', 'value\n',
+                      'iota', 'A/mu')
+
+  sbox.simple_propset('del_n', 'no-eol',
+                      'iota', 'A/mu')
+
+  sbox.simple_commit()
+
+  sbox.simple_propset('mod_s', 'other\n',
+                      'iota', 'A/mu')
+
+  sbox.simple_propset('mod_s_n', 'still no eol',
+                      'iota', 'A/mu')
+
+  sbox.simple_propset('mod_l', 'this\nis\na\nsomewhat\nlong\nvalue.\n',
+                      'iota', 'A/mu')
+
+  sbox.simple_propset('mod_l_n', 'this\nis\na\nanother\n..\nlong\nvalue.\n'
+                      'without\neol', # No eol at end
+                      'iota', 'A/mu')
+
+  sbox.simple_propdel('del', 'iota', 'A/mu')
+
+  sbox.simple_propdel('del_n', 'iota', 'A/mu')
+
+  sbox.simple_propset('add_s', 'new-value\n',
+                      'iota', 'A/mu')
+
+  sbox.simple_propset('add_s_n', 'new other no eol',
+                      'iota', 'A/mu')
+
+  sbox.simple_propset('add_l', 'this\nis\nsomething\n',
+                      'iota', 'A/mu')
+
+  sbox.simple_propset('add_l_n', 'this\nhas\nno\neol', # No eol at end
+                      'iota', 'A/mu')
+
+  _, output, _ = svntest.actions.run_and_verify_svn(None, [],
+                                                    'diff', wc_dir)
+
+  expected_disk = svntest.main.greek_state.copy()
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+
+  new_props = {
+    'mod_s'       : 'other\n',
+    'mod_s_n'     : 'still no eol',
+    'mod_l'       : 'this\nis\na\nsomewhat\nlong\nvalue.\n',
+    'mod_l_n'     : 'this\nis\na\nanother\n..\nlong\nvalue.\nwithout\neol',
+    'add_s'       : 'new-value\n',
+    'add_s_n'     : 'new other no eol',
+    'add_l'       : 'this\nis\nsomething\n',
+    'add_l_n'     : 'this\nhas\nno\neol'
+  }
+
+  expected_status.tweak('iota', 'A/mu', status=' M', wc_rev='2')
+  expected_disk.tweak('iota', 'A/mu', props=new_props)
+
+  svntest.actions.verify_disk(wc_dir, expected_disk, True)
+  #svntest.actions.run_and_verify_status(wc_dir, expected_status)
+
+  svntest.actions.run_and_verify_svn(None, [],
+                                     'revert', wc_dir, '-R')
+
+  patch = sbox.get_tempname('patch')
+  svntest.main.file_write(patch, ''.join(output), mode='wb')
+
+  expected_output = wc.State(wc_dir, {
+    'A/mu' : Item(status=' U'),
+    'iota' : Item(status=' U'),
+  })
+  expected_skip= wc.State(wc_dir, {
+  })
+
+  strip_count = wc_dir.count(os.path.sep)+1
+
+  # Patch once
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], True, True,
+                                       '--strip', strip_count)
+
+  # Patch again
+  expected_output.tweak('A/mu', 'iota', status=' G')
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], True, True,
+                                       '--strip', strip_count)
+
+  # Reverse
+  expected_output.tweak('A/mu', 'iota', status=' U')
+  props = {
+   'mod_l_n' : 'this\nis\na\nvery\nvery\nlong\nvalue.\nwithout\neol',
+   'mod_l'   : 'this\nis\na\nvery\nvery\nlong\nvalue.\n',
+   'mod_s'   : 'value\n',
+   'mod_s_n' : 'no-eol',
+   'del'     : 'value\n',
+   'del_n'   : 'no-eol',
+  }
+  expected_disk.tweak('A/mu', 'iota', props=props)
+  expected_status.tweak('A/mu', 'iota', status='  ')
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], True, True,
+                                       '--reverse-diff',
+                                       '--strip', strip_count)
+
+  # And repeat
+  expected_output.tweak('A/mu', 'iota', status=' G')
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], True, True,
+                                       '--reverse-diff',
+                                       '--strip', strip_count)
+
+  # Ok, and now introduce some conflicts
+
+  sbox.simple_propset('del', 'value', 'iota') # Wrong EOL
+  sbox.simple_propset('del', 'waarde', 'A/mu') # Wrong EOL+value
+
+  sbox.simple_propset('del_n', 'no-eol\n', 'iota') # Wrong EOL
+  sbox.simple_propset('del_n', 'regeleinde\n', 'iota') # Wrong EOL+value
+
+  expected_output.tweak('A/mu', 'iota', status=' C')
+  svntest.actions.run_and_verify_patch(wc_dir, patch,
+                                       expected_output, expected_disk,
+                                       expected_status, expected_skip,
+                                       [], True, True,
+                                       '--strip', strip_count)
+
 ########################################################################
 #Run the tests
 
@@ -5736,6 +6414,16 @@ test_list = [ None,
               patch_symlink_traversal,
               patch_obstructing_symlink_traversal,
               patch_binary_file,
+              patch_delete_nodes,
+              patch_delete_missing_eol,
+              patch_final_eol,
+              patch_adds_executability_nocontents,
+              patch_adds_executability_nocontents2,
+              patch_adds_executability_yescontents,
+              patch_deletes_executability,
+              patch_ambiguous_executability_contradiction,
+              patch_ambiguous_executability_consistent,
+              patch_prop_madness,
             ]
 
 if __name__ == '__main__':

Modified: subversion/branches/move-tracking-2/subversion/tests/cmdline/svntest/tree.py
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/tests/cmdline/svntest/tree.py?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/tests/cmdline/svntest/tree.py (original)
+++ subversion/branches/move-tracking-2/subversion/tests/cmdline/svntest/tree.py Mon Sep 28 16:03:14 2015
@@ -285,16 +285,7 @@ class SVNTreeNode:
     if self.props:
       if comma:
         line += ", "
-      line += "props={"
-      comma = False
-
-      for name in self.props:
-        if comma:
-          line += ", "
-        line += "'%s':'%s'" % (name, self.props[name])
-        comma = True
-
-      line += "}"
+      line += ("props=%s" % self.props)
       comma = True
 
     for name in self.atts:

Modified: subversion/branches/move-tracking-2/subversion/tests/libsvn_client/client-test.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/tests/libsvn_client/client-test.c?rev=1705712&r1=1705711&r2=1705712&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/tests/libsvn_client/client-test.c (original)
+++ subversion/branches/move-tracking-2/subversion/tests/libsvn_client/client-test.c Mon Sep 28 16:03:14 2015
@@ -740,14 +740,14 @@ test_foreign_repos_copy(const svn_test_o
 
   wc_path = svn_test_data_path("test-foreign-repos-copy", pool);
 
-  wc_path = svn_dirent_join(wc_path, "foreign-wc", pool);
-
   /* Remove old test data from the previous run */
   SVN_ERR(svn_io_remove_dir2(wc_path, TRUE, NULL, NULL, pool));
 
   SVN_ERR(svn_io_make_dir_recursively(wc_path, pool));
   svn_test_add_dir_cleanup(wc_path);
 
+  wc_path = svn_dirent_join(wc_path, "foreign-wc", pool);
+
   rev.kind = svn_opt_revision_head;
   peg_rev.kind = svn_opt_revision_unspecified;
   SVN_ERR(svn_client_create_context(&ctx, pool));
@@ -953,7 +953,7 @@ test_remote_only_status(const svn_test_o
 
   /* Check out a sparse root @r1 of the repository */
   wc_path = svn_test_data_path("test-remote-only-status-wc", pool);
-  /*svn_test_add_dir_cleanup(wc_path);*/
+  svn_test_add_dir_cleanup(wc_path);
   SVN_ERR(svn_io_remove_dir2(wc_path, TRUE, NULL, NULL, pool));
 
   rev.kind = svn_opt_revision_number;