You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2010/01/27 16:40:23 UTC

svn commit: r903691 - in /subversion/trunk/subversion: include/svn_subst.h libsvn_client/patch.c libsvn_subr/subst.c tests/cmdline/patch_tests.py

Author: stsp
Date: Wed Jan 27 15:40:23 2010
New Revision: 903691

URL: http://svn.apache.org/viewvc?rev=903691&view=rev
Log:
Make svn patch contract keywords on input and expand them on output.
Keywords do not interfere with the patching process anymore, and are
expanded in the patched result. Also, write the correct EOL-style
to the patched result if an svn:eol-style property is set on the target.

* subversion/include/svn_subst.h
  (svn_subst_stream_translated): Document support for mark/seek feature.

* subversion/libsvn_client/patch.c
  (): Include svn_props.h.
  (patch_target_t): Fix error in comment. New fields KEYWORDS and EOL_STYLE.
  (init_patch_target): Evaluate svn:keywords and svn:eol-style. Correctly
   create the translation streams for patched result and reject file.
   Move everything which needs to be done if the target already exists
   into the same if-block.
  (read_line): Return a plain const char * rather than a stringbuf.
   Contract keywords after reading the line (wrapping a translation stream
   around a stream reading from a range of a file interferes with
   EOF-detection in svn_stream_readline(), so we need to contract keywords
   manually -- I know this sounds really wrong but it's not trivial for me
   to fix).
  (seek_to_line, copy_lines_to_target): Track read_line() parameter change.
  (match_hunk): As previous, but also contract keywords after reading
   a line from the patch file in case the patch contains expanded keywords.
  (scan_for_match): Constify a pointer. Don't allow rejected hunks to
   block another hunk from matching (not that they did, but it's nice to
   be explicit about it).
  (get_hunk_info): Improve a comment.

* subversion/libsvn_subr/subst.c
  (translated_stream_mark, translated_stream_seek): New.
  (svn_subst_stream_translated): Set mark and seek handlers.

* subversion/tests/cmdline/patch_tests.py
  (patch_unidiff_keywords): New test. Tests patch application where the
   target contains expanded keywords but the patch does not ('svn diff'
   contracts keywords so this should be the common case).
  (test_list): Add new test.

Modified:
    subversion/trunk/subversion/include/svn_subst.h
    subversion/trunk/subversion/libsvn_client/patch.c
    subversion/trunk/subversion/libsvn_subr/subst.c
    subversion/trunk/subversion/tests/cmdline/patch_tests.py

Modified: subversion/trunk/subversion/include/svn_subst.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/include/svn_subst.h?rev=903691&r1=903690&r2=903691&view=diff
==============================================================================
--- subversion/trunk/subversion/include/svn_subst.h (original)
+++ subversion/trunk/subversion/include/svn_subst.h Wed Jan 27 15:40:23 2010
@@ -295,6 +295,7 @@
  * The stream returned is allocated in @a pool.
  *
  * If the inner stream implements resetting via svn_stream_reset(),
+ * or marking and seeking via svn_stream_mark() and svn_stream_seek(),
  * the translated stream will too.
  *
  * @since New in 1.4.

Modified: subversion/trunk/subversion/libsvn_client/patch.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/patch.c?rev=903691&r1=903690&r2=903691&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/patch.c (original)
+++ subversion/trunk/subversion/libsvn_client/patch.c Wed Jan 27 15:40:23 2010
@@ -33,6 +33,7 @@
 #include "svn_io.h"
 #include "svn_path.h"
 #include "svn_pools.h"
+#include "svn_props.h"
 #include "svn_subst.h"
 #include "svn_wc.h"
 #include "client.h"
@@ -86,7 +87,7 @@
    * so EOL transformation and keyword contraction is done transparently. */
   svn_stream_t *patched;
 
-  /* The patched stream, without EOL transformation and keyword contraction. */
+  /* The patched stream, without EOL transformation and keyword expansion. */
   svn_stream_t *patched_raw;
 
   /* Path to the temporary file underlying the result stream. */
@@ -153,6 +154,12 @@
   /* True if the target's parent directory exists. */
   svn_boolean_t parent_dir_exists;
 
+  /* The keywords of the target. */
+  apr_hash_t *keywords;
+
+  /* The EOL-style of the target. */
+  svn_subst_eol_style_t eol_style;
+
   /* The pool the target is allocated in. */
   apr_pool_t *pool;
 } patch_target_t;
@@ -366,64 +373,123 @@
                               base_dir, strip_count, ctx->wc_ctx,
                               result_pool, scratch_pool));
 
-  if (new_target->kind == svn_node_file && ! new_target->skipped)
-    {
-      /* Try to open the target file */
-      SVN_ERR(svn_io_file_open(&new_target->file, new_target->abs_path,
-                               APR_READ | APR_BINARY | APR_BUFFERED,
-                               APR_OS_DEFAULT, result_pool));
-      SVN_ERR(svn_eol__detect_file_eol(&new_target->eol_str, new_target->file,
-                                        scratch_pool));
-      new_target->stream = svn_stream_from_aprfile2(new_target->file, FALSE,
-                                                    result_pool);
-    }
-
-  if (new_target->eol_str == NULL)
-    {
-      /* Either we couldn't figure out the target files's EOL scheme,
-       * or the target file doesn't exist. Just use native EOL makers. */
-      new_target->eol_str = APR_EOL_STR;
-    }
-
   new_target->local_mods = FALSE;
   new_target->executable = FALSE;
 
   if (! new_target->skipped)
     {
-      apr_hash_t *keywords;
+      apr_hash_t *props;
+      svn_string_t *keywords_val;
+      svn_string_t *eol_style_val;
       const char *diff_header;
       apr_size_t len;
 
-      /* TODO: Get keywords from patch target. How?
-       * Can't call svn_wc__get_keywords() here? */
-      keywords = apr_hash_make(result_pool);
+      new_target->eol_str = APR_EOL_STR;
+      new_target->keywords = NULL;
+      new_target->eol_style = svn_subst_eol_style_unknown;
+
+      if (new_target->kind == svn_node_file)
+        {
+          /* Open the file. */ 
+          SVN_ERR(svn_io_file_open(&new_target->file, new_target->abs_path,
+                                   APR_READ | APR_BINARY | APR_BUFFERED,
+                                   APR_OS_DEFAULT, result_pool));
+
+          /* Handle svn:keyword and svn:eol-style properties. */
+          SVN_ERR(svn_wc_prop_list2(&props, ctx->wc_ctx, new_target->abs_path,
+                                    scratch_pool, scratch_pool));
+          keywords_val = apr_hash_get(props, SVN_PROP_KEYWORDS,
+                                      APR_HASH_KEY_STRING);
+          if (keywords_val)
+            {
+              svn_revnum_t changed_rev;
+              apr_time_t changed_date;
+              const char *rev_str;
+              const char *author;
+              const char *url;
+
+              SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
+                                                    &changed_date,
+                                                    &author, ctx->wc_ctx,
+                                                    new_target->abs_path,
+                                                    scratch_pool,
+                                                    scratch_pool));
+              rev_str = apr_psprintf(scratch_pool, "%"SVN_REVNUM_T_FMT,
+                                     changed_rev);
+              SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx,
+                                           new_target->abs_path,
+                                           scratch_pool, scratch_pool));
+              SVN_ERR(svn_subst_build_keywords2(&new_target->keywords,
+                                                keywords_val->data,
+                                                rev_str, url, changed_date,
+                                                author, result_pool));
+            }
+
+          eol_style_val = apr_hash_get(props, SVN_PROP_EOL_STYLE,
+                                       APR_HASH_KEY_STRING);
+          if (eol_style_val)
+            {
+              svn_subst_eol_style_from_value(&new_target->eol_style,
+                                             &new_target->eol_str,
+                                             eol_style_val->data);
+            }
+          else
+            {
+              /* Just use the first EOL sequence we can find in the file. */
+              SVN_ERR(svn_eol__detect_file_eol(&new_target->eol_str,
+                                               new_target->file, scratch_pool));
+              /* But don't enforce any particular EOL-style. */
+              new_target->eol_style = svn_subst_eol_style_none;
+            }
+
+          if (new_target->eol_str == NULL)
+            {
+              /* We couldn't figure out the target files's EOL scheme,
+               * just use native EOL makers. */
+              new_target->eol_str = APR_EOL_STR;
+              new_target->eol_style = svn_subst_eol_style_native;
+            }
+
+          /* Create a stream to read from the target. */
+          new_target->stream = svn_stream_from_aprfile2(new_target->file,
+                                                        FALSE, result_pool);
+
+          /* Also check the file for local mods and the Xbit. */
+          SVN_ERR(check_local_mods(&new_target->local_mods, ctx->wc_ctx,
+                                   new_target->abs_path, scratch_pool));
+          SVN_ERR(svn_io_is_file_executable(&new_target->executable,
+                                            new_target->abs_path,
+                                            scratch_pool));
+        }
 
-      /* Create a temporary file to write the patched result to. */
+      /* Create a temporary file to write the patched result to.
+       * Expand keywords in the patched file. */
       SVN_ERR(svn_stream_open_unique(&new_target->patched_raw,
                                      &new_target->patched_path, NULL,
                                      svn_io_file_del_on_pool_cleanup,
                                      result_pool, scratch_pool));
-      new_target->patched = svn_subst_stream_translated(new_target->patched_raw,
-                                                        "\n", TRUE,
-                                                        keywords, FALSE,
-                                                        result_pool);
-
-      SVN_ERR(check_local_mods(&new_target->local_mods, ctx->wc_ctx,
-                               new_target->abs_path, scratch_pool));
-
-      if (new_target->kind == svn_node_file)
-        SVN_ERR(svn_io_is_file_executable(&new_target->executable,
-                                          new_target->abs_path,
-                                          scratch_pool));
+      new_target->patched = svn_subst_stream_translated(
+                              new_target->patched_raw,
+                              new_target->eol_str,
+                              new_target->eol_style ==
+                                svn_subst_eol_style_fixed,
+                              new_target->keywords, TRUE, result_pool);
 
-      /* We'll also need a stream to write rejected hunks to. */
+      /* We'll also need a stream to write rejected hunks to.
+       * We don't expand keywords in reject files. */
       SVN_ERR(svn_stream_open_unique(&new_target->reject_raw,
                                      &new_target->reject_path, NULL,
                                      svn_io_file_del_on_pool_cleanup,
                                      result_pool, scratch_pool));
       new_target->reject = svn_subst_stream_translated(
-                               new_target->reject_raw, "\n",
-                               TRUE, keywords, FALSE, result_pool);
+                               new_target->reject_raw,
+                               new_target->eol_str,
+                               new_target->eol_style ==
+                                 svn_subst_eol_style_fixed,
+                               new_target->keywords, FALSE,
+                               result_pool);
+
+      /* The reject stream needs a diff header. */
       diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s",
                                  new_target->canon_path_from_patchfile,
                                  new_target->eol_str,
@@ -447,20 +513,21 @@
   return SVN_NO_ERROR;
 }
 
-/* Read a line from TARGET. If this line has not been read before
- * mark the line in TARGET->LINES.
- * Return the line in *STRINGBUF, allocated in RESULT_POOL.
+/* Read a *LINE from TARGET. If the line has not been read before
+ * mark the line in TARGET->LINES. Allocate *LINE in RESULT_POOL.
  * Do temporary allocations in SCRATCH_POOL.
  */
 static svn_error_t *
 read_line(patch_target_t *target,
-          svn_stringbuf_t **stringbuf,
+          const char **line,
           apr_pool_t *scratch_pool,
           apr_pool_t *result_pool)
 {
+  svn_stringbuf_t *line_raw;
+
   if (target->eof)
     {
-      *stringbuf = svn_stringbuf_create_ensure(0, result_pool);
+      *line = "";
       return SVN_NO_ERROR;
     }
 
@@ -472,8 +539,13 @@
       APR_ARRAY_PUSH(target->lines, svn_stream_mark_t *) = mark;
     }
 
-  SVN_ERR(svn_stream_readline(target->stream, stringbuf, target->eol_str,
-                              &target->eof, result_pool));
+  SVN_ERR(svn_stream_readline(target->stream, &line_raw, target->eol_str,
+                              &target->eof, scratch_pool));
+  /* Contract keywords. */
+  SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
+                                       target->eol_str, FALSE,
+                                       target->keywords, FALSE,
+                                       result_pool));
   target->current_line++;
 
   return SVN_NO_ERROR;
@@ -502,7 +574,7 @@
     }
   else
     {
-      svn_stringbuf_t *dummy;
+      const char *dummy;
       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
       while (target->current_line < line)
@@ -516,17 +588,18 @@
   return SVN_NO_ERROR;
 }
 
-/* Indicate in *MATCHED whether the original text of HUNK matches
- * the patch TARGET at its current line.
+/* Indicate in *MATCHED whether the original text of HUNK
+ * matches the patch TARGET at its current line.
  * When this function returns, neither TARGET->CURRENT_LINE nor the
  * file offset in the target file will have changed.
- * HUNK->ORIGINAL_TEXT will be reset. Do temporary allocations in POOL. */
+ * HUNK->ORIGINAL_TEXT will be reset.
+ * Do temporary allocations in POOL. */
 static svn_error_t *
 match_hunk(svn_boolean_t *matched, patch_target_t *target,
            const svn_hunk_t *hunk, apr_pool_t *pool)
 {
   svn_stringbuf_t *hunk_line;
-  svn_stringbuf_t *target_line;
+  const char *target_line;
   svn_linenum_t saved_line;
   svn_boolean_t hunk_eof;
   svn_boolean_t lines_matched;
@@ -543,16 +616,23 @@
   iterpool = svn_pool_create(pool);
   do
     {
+      const char *hunk_line_translated;
+      const char *eol_str;
+
       svn_pool_clear(iterpool);
 
-      SVN_ERR(svn_stream_readline_detect_eol(hunk->original_text, &hunk_line,
-                                             NULL, &hunk_eof, iterpool));
+      SVN_ERR(svn_stream_readline_detect_eol(hunk->original_text,
+                                             &hunk_line, &eol_str,
+                                             &hunk_eof, iterpool));
+      /* Contract keywords, if any, before matching. */
+      SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
+                                           &hunk_line_translated,
+                                           eol_str, FALSE,
+                                           target->keywords, FALSE,
+                                           iterpool));
       SVN_ERR(read_line(target, &target_line, iterpool, iterpool));
       if (! hunk_eof)
-        {
-          lines_matched = (hunk_line->len == target_line->len &&
-                           ! strcmp(hunk_line->data, target_line->data));
-        }
+        lines_matched = ! strcmp(hunk_line_translated, target_line);
     }
   while (lines_matched && ! (hunk_eof || target->eof));
 
@@ -612,10 +692,11 @@
           /* Don't allow hunks to match at overlapping locations. */
           for (i = 0; i < target->hunks->nelts; i++)
             {
-              hunk_info_t *hi;
+              const hunk_info_t *hi;
               
-              hi = APR_ARRAY_IDX(target->hunks, i, hunk_info_t *);
-              taken = (target->current_line >= hi->matched_line &&
+              hi = APR_ARRAY_IDX(target->hunks, i, const hunk_info_t *);
+              taken = (! hi->rejected &&
+                       target->current_line >= hi->matched_line &&
                        target->current_line < hi->matched_line +
                                               hi->hunk->original_length);
               if (taken)
@@ -651,7 +732,9 @@
 {
   svn_linenum_t matched_line;
 
-  /* An original offset of zero means that this hunk wants to create
+  /* Now try to match the hunk.
+   *
+   * An original offset of zero means that this hunk wants to create
    * a new file, potentially overwriting all content of an existing
    * file in the WC. Don't bother matching hunks in that case, since
    * the hunk applies at line 1. */
@@ -731,16 +814,16 @@
   iterpool = svn_pool_create(pool);
   while ((target->current_line < line || line == 0) && ! target->eof)
     {
-      svn_stringbuf_t *target_line;
+      const char *target_line;
 
       svn_pool_clear(iterpool);
 
       SVN_ERR(read_line(target, &target_line, iterpool, iterpool));
       if (! target->eof)
-        svn_stringbuf_appendcstr(target_line, target->eol_str);
+        target_line = apr_pstrcat(iterpool, target_line, target->eol_str, NULL);
 
       SVN_ERR(try_stream_write(target->patched, target->patched_path,
-                               target_line->data, target_line->len, pool));
+                               target_line, strlen(target_line), iterpool));
     }
   svn_pool_destroy(iterpool);
 

Modified: subversion/trunk/subversion/libsvn_subr/subst.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_subr/subst.c?rev=903691&r1=903690&r2=903691&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_subr/subst.c (original)
+++ subversion/trunk/subversion/libsvn_subr/subst.c Wed Jan 27 15:40:23 2010
@@ -1136,6 +1136,43 @@
   return svn_error_return(err);
 }
 
+static svn_error_t *
+translated_stream_mark(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
+{
+  struct translated_stream_baton *b = baton;
+
+  return svn_error_return(svn_stream_mark(b->stream, mark, pool));
+}
+
+static svn_error_t *
+translated_stream_seek(void *baton, svn_stream_mark_t *mark)
+{
+  struct translated_stream_baton *b = baton;
+  svn_error_t *err;
+
+  /* Flush output buffer if necessary. */
+  if (b->written)
+    SVN_ERR(translate_chunk(b->stream, b->out_baton, NULL, 0, b->iterpool));
+
+  err = svn_stream_seek(b->stream, mark);
+  if (err == NULL)
+    {
+      /* Force into boring state. */
+      b->in_baton->newline_off = 0;
+      b->in_baton->keyword_off = 0;
+      b->out_baton->newline_off = 0;
+      b->out_baton->keyword_off = 0;
+
+      /* Output buffer has been flushed. */
+      b->written = FALSE;
+
+      /* Reset read buffer. */
+      svn_stringbuf_setempty(b->readbuf);
+      b->readbuf_off = 0;
+    }
+
+  return svn_error_return(err);
+}
 
 svn_error_t *
 svn_subst_read_specialfile(svn_stream_t **stream,
@@ -1235,6 +1272,8 @@
   svn_stream_set_write(s, translated_stream_write);
   svn_stream_set_close(s, translated_stream_close);
   svn_stream_set_reset(s, translated_stream_reset);
+  svn_stream_set_mark(s, translated_stream_mark);
+  svn_stream_set_seek(s, translated_stream_seek);
 
   return s;
 }

Modified: subversion/trunk/subversion/tests/cmdline/patch_tests.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/patch_tests.py?rev=903691&r1=903690&r2=903691&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/patch_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/patch_tests.py Wed Jan 27 15:40:23 2010
@@ -968,6 +968,66 @@
                                        1, # check-props
                                        1) # dry-run
 
+def patch_unidiff_keywords(sbox):
+  "apply a unidiff patch containing keywords"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+
+  # Set gamma contents
+  gamma_contents = "$Rev$\nHello there! I'm the file 'gamma'.\n"
+  gamma_path = os.path.join(wc_dir, 'A', 'D', 'gamma')
+  svntest.main.file_write(gamma_path, gamma_contents)
+  # Expand the keyword
+  svntest.main.run_svn(None, 'propset', 'svn:keywords', 'Rev',
+                       os.path.join(wc_dir, 'A', 'D', 'gamma'))
+  expected_output = svntest.wc.State(wc_dir, {
+    'A/D/gamma'       : Item(verb='Sending'),
+    })
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('A/D/gamma', wc_rev=2)
+  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
+                                        expected_status, None, wc_dir)
+
+  patch_file_path = tempfile.mkstemp(dir=os.path.abspath(svntest.main.temp_dir))[1]
+
+  # Apply patch
+
+  unidiff_patch = [
+   "Index: gamma\n",
+   "===================================================================\n",
+   "--- A/D/gamma	(revision 3)\n",
+   "+++ A/D/gamma	(working copy)\n",
+   "@@ -1,2 +1,3 @@\n",
+   " $Rev$\n",
+   " Hello there! I'm the file 'gamma'.\n",
+   "+booo\n",
+  ]
+
+  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))
+
+  expected_output = [
+    'U         %s\n' % os.path.join(wc_dir, 'A', 'D', 'gamma'),
+  ]
+
+  expected_disk = svntest.main.greek_state.copy()
+  gamma_contents = "$Rev: 2 $\nHello there! I'm the file 'gamma'.\nbooo\n"
+  expected_disk.tweak('A/D/gamma', contents=gamma_contents,
+                      props={'svn:keywords' : 'Rev'})
+
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('A/D/gamma', status='M ', wc_rev=2)
+
+  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,
+                                       None, # expected err
+                                       1, # check-props
+                                       1) # dry-run
 
 ########################################################################
 #Run the tests
@@ -981,6 +1041,7 @@
               patch_unidiff_strip1,
               patch_add_new_dir,
               patch_unidiff_reject,
+              patch_unidiff_keywords,
             ]
 
 if __name__ == '__main__':