Author: stsp
Date: Sat Jul 10 14:02:31 2010
New Revision: 962842

URL: http://svn.apache.org/viewvc?rev=962842&view=rev
Log:
Make svn patch tolerate hunks that are already applied.

This avoids spurious hunk applications or rejects when the same patch
is applied twice. But we only tolerate exact hunk applications.
If a hunk matched with fuzz or with an offset during a previous run of
svn patch, the current run might still apply the hunk elsewhere (possibly
with fuzz) or reject the hunk. It's too complicated to try to guess
whether a hunk has already been applied at a different offset and/or with
fuzz, because there is a possibility of ambiguity.

* subversion/include/svn_wc.h
  (svn_wc_notify_action_t): Add svn_wc_notify_patch_hunk_already_applied.

* subversion/libsvn_client/patch.c
  (hunk_info_t): Add 'already_applied'.
  (match_hunk, scan_for_match): Add 'match_modified' parameter, which causes
   the modified hunk text to be matched instead of the original hunk text.
  (match_existing_file): New helper function.
  (get_hunk_info): Before trying to match the hunk to the target, check
   if the hunk is already applied.
  (apply_hunk): Reset the modified text before reading it, because it
   may have been read during matching.
  (send_patch_notification): Send a special notification for hunks that
   were already applied before patching.
  (apply_one_patch): Do not apply hunks which have already been applied.

* subversion/tests/cmdline/patch_tests.py
  (patch_same_twice, test_list): New test.

* subversion/svn/notify.c
  (notify): Handle notification for already applied hunks.

Modified:
    subversion/trunk/subversion/include/svn_wc.h
    subversion/trunk/subversion/libsvn_client/patch.c
    subversion/trunk/subversion/svn/notify.c
    subversion/trunk/subversion/tests/cmdline/patch_tests.py

Modified: subversion/trunk/subversion/include/svn_wc.h
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/include/svn_wc.h?rev=962842&r1=962841&r2=962842&view=diff
==============================================================================
--- subversion/trunk/subversion/include/svn_wc.h (original)
+++ subversion/trunk/subversion/include/svn_wc.h Sat Jul 10 14:02:31 2010
@@ -1081,6 +1081,11 @@ typedef enum svn_wc_notify_action_t
   /** A hunk from a patch was rejected.
    * @since New in 1.7. */
   svn_wc_notify_patch_rejected_hunk,
+
+  /** A hunk from a patch was found to already be applied.
+   * @since New in 1.7. */
+  svn_wc_notify_patch_hunk_already_applied,
+
 } svn_wc_notify_action_t;
 
 

Modified: subversion/trunk/subversion/libsvn_client/patch.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/patch.c?rev=962842&r1=962841&r2=962842&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/patch.c (original)
+++ subversion/trunk/subversion/libsvn_client/patch.c Sat Jul 10 14:02:31 2010
@@ -56,6 +56,10 @@ typedef struct hunk_info_t {
   /* Whether this hunk has been rejected. */
   svn_boolean_t rejected;
 
+  /* Whether this hunk has already been applied (either manually
+   * or by an earlier run of patch). */
+  svn_boolean_t already_applied;
+
   /* The fuzz factor used when matching this hunk, i.e. how many
    * lines of leading and trailing context to ignore during matching. */
   int fuzz;
@@ -599,12 +603,14 @@ seek_to_line(patch_target_t *target, svn
  * of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore
  * whitespace when doing the matching. 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. */
+ * changed. If MATCH_MODIFIED is TRUE, match the modified hunk text,
+ * rather than the original hunk text.
+ * Do temporary allocations in POOL. */
 static svn_error_t *
 match_hunk(svn_boolean_t *matched, patch_target_t *target,
            const svn_hunk_t *hunk, int fuzz, 
-           svn_boolean_t ignore_whitespace, apr_pool_t *pool)
+           svn_boolean_t ignore_whitespace,
+           svn_boolean_t match_modified, apr_pool_t *pool)
 {
   svn_stringbuf_t *hunk_line;
   const char *target_line;
@@ -628,7 +634,10 @@ match_hunk(svn_boolean_t *matched, patch
   original_length = svn_diff_hunk_get_original_length(hunk);
   leading_context = svn_diff_hunk_get_leading_context(hunk);
   trailing_context = svn_diff_hunk_get_trailing_context(hunk);
-  SVN_ERR(svn_diff_hunk_reset_original_text(hunk));
+  if (match_modified)
+    SVN_ERR(svn_diff_hunk_reset_modified_text(hunk));
+  else
+    SVN_ERR(svn_diff_hunk_reset_original_text(hunk));
   iterpool = svn_pool_create(pool);
   do
     {
@@ -636,9 +645,14 @@ match_hunk(svn_boolean_t *matched, patch
 
       svn_pool_clear(iterpool);
 
-      SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
-                                                   NULL, &hunk_eof,
-                                                   iterpool, iterpool));
+      if (match_modified)
+        SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
+                                                     NULL, &hunk_eof,
+                                                     iterpool, iterpool));
+      else
+        SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
+                                                     NULL, &hunk_eof,
+                                                     iterpool, iterpool));
 
       /* Contract keywords, if any, before matching. */
       SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
@@ -682,9 +696,14 @@ match_hunk(svn_boolean_t *matched, patch
     {
       /* If the target has no newline at end-of-file, we get an EOF
        * indication for the target earlier than we do get it for the hunk. */
-      SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
-                                                   NULL, &hunk_eof,
-                                                   iterpool, iterpool));
+      if (match_modified)
+        SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
+                                                     NULL, &hunk_eof,
+                                                     iterpool, iterpool));
+      else
+        SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
+                                                     NULL, &hunk_eof,
+                                                     iterpool, iterpool));
       if (hunk_line->len == 0 && hunk_eof)
         *matched = lines_matched;
       else
@@ -707,6 +726,8 @@ match_hunk(svn_boolean_t *matched, patch
  * If the hunk matched multiple times, and MATCH_FIRST is FALSE,
  * return the line number at which the last match occured in *MATCHED_LINE.
  * If IGNORE_WHITESPACE is set, ignore whitespace during the matching.
+ * If MATCH_MODIFIED is TRUE, match the modified hunk text,
+ * rather than the original hunk text.
  * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
  * Do all allocations in POOL. */
 static svn_error_t *
@@ -714,6 +735,7 @@ scan_for_match(svn_linenum_t *matched_li
                const svn_hunk_t *hunk, svn_boolean_t match_first,
                svn_linenum_t upper_line, int fuzz, 
                svn_boolean_t ignore_whitespace,
+               svn_boolean_t match_modified,
                svn_cancel_func_t cancel_func, void *cancel_baton,
                apr_pool_t *pool)
 {
@@ -733,7 +755,7 @@ scan_for_match(svn_linenum_t *matched_li
         SVN_ERR((cancel_func)(cancel_baton));
 
       SVN_ERR(match_hunk(&matched, target, hunk, fuzz, ignore_whitespace,
-                         iterpool));
+                         match_modified, iterpool));
       if (matched)
         {
           svn_boolean_t taken = FALSE;
@@ -742,12 +764,18 @@ scan_for_match(svn_linenum_t *matched_li
           for (i = 0; i < target->hunks->nelts; i++)
             {
               const hunk_info_t *hi;
+              svn_linenum_t length;
 
               hi = APR_ARRAY_IDX(target->hunks, i, const hunk_info_t *);
+
+              if (match_modified)
+                length = svn_diff_hunk_get_modified_length(hi->hunk);
+              else
+                length = svn_diff_hunk_get_original_length(hi->hunk);
+
               taken = (! hi->rejected &&
                        target->current_line >= hi->matched_line &&
-                       target->current_line < (hi->matched_line +
-                         svn_diff_hunk_get_original_length(hi->hunk)));
+                       target->current_line < (hi->matched_line + length));
               if (taken)
                 break;
             }
@@ -768,6 +796,69 @@ scan_for_match(svn_linenum_t *matched_li
   return SVN_NO_ERROR;
 }
 
+/* Indicate in *MATCH whether the file at TARGET->abspath matches the
+ * modified text of HUNK. Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+match_existing_file(svn_boolean_t *match,
+                    patch_target_t *target,
+                    const svn_hunk_t *hunk,
+                    apr_pool_t *scratch_pool)
+{
+  apr_file_t *file;
+  svn_stream_t *stream;
+  svn_boolean_t lines_matched;
+  apr_pool_t *iterpool;
+  svn_boolean_t eof;
+  svn_boolean_t hunk_eof;
+
+  SVN_ERR(svn_io_file_open(&file, target->local_abspath,
+                           APR_READ | APR_BINARY, APR_OS_DEFAULT,
+                           scratch_pool));
+  stream = svn_stream_from_aprfile2(file, FALSE, scratch_pool);
+
+
+  SVN_ERR(svn_diff_hunk_reset_modified_text(hunk));
+
+  iterpool = svn_pool_create(scratch_pool);
+  do
+    {
+      svn_stringbuf_t *line;
+      svn_stringbuf_t *hunk_line;
+      const char *line_translated;
+      const char *hunk_line_translated;
+
+      svn_pool_clear(iterpool);
+
+      SVN_ERR(svn_stream_readline_detect_eol(stream, &line, NULL,
+                                             &eof, iterpool));
+      SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
+                                                   NULL, &hunk_eof,
+                                                   iterpool, iterpool));
+      /* Contract keywords. */
+      SVN_ERR(svn_subst_translate_cstring2(line->data, &line_translated,
+                                           NULL, FALSE, target->keywords,
+                                           FALSE, iterpool));
+      SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
+                                           &hunk_line_translated,
+                                           NULL, FALSE, target->keywords,
+                                           FALSE, iterpool));
+      lines_matched = ! strcmp(line_translated, hunk_line_translated);
+      if (eof != hunk_eof)
+        {
+          svn_pool_destroy(iterpool);
+          *match = FALSE;
+          svn_stream_close(stream);
+          return SVN_NO_ERROR;
+        }
+      }
+    while (lines_matched && ! eof && ! hunk_eof);
+    svn_pool_destroy(iterpool);
+
+    *match = (lines_matched && eof == hunk_eof);
+    svn_stream_close(stream);
+    return SVN_NO_ERROR;
+}
+
 /* Determine the line at which a HUNK applies to the TARGET file,
  * and return an appropriate hunk_info object in *HI, allocated from
  * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
@@ -786,62 +877,112 @@ get_hunk_info(hunk_info_t **hi, patch_ta
 {
   svn_linenum_t matched_line;
   svn_linenum_t original_start;
+  svn_boolean_t already_applied;
 
   original_start = svn_diff_hunk_get_original_start(hunk);
+  already_applied = FALSE;
 
   /* An original offset of zero means that this hunk wants to create
    * a new file. Don't bother matching hunks in that case, since
    * the hunk applies at line 1. If the file already exists, the hunk
-   * is rejected. */
+   * is rejected, unless the file is versioned and its content matches
+   * the file the patch wants to create. */
   if (original_start == 0)
     {
       if (target->kind_on_disk == svn_node_file)
-        matched_line = 0;
+        {
+          if (target->db_kind == svn_node_file)
+            {
+              svn_boolean_t file_matches;
+
+              SVN_ERR(match_existing_file(&file_matches, target, hunk,
+                                          scratch_pool));
+              if (file_matches)
+                {
+                  matched_line = 1;
+                  already_applied = TRUE;
+                }
+              else
+                matched_line = 0; /* reject */
+            }
+          else
+            matched_line = 0; /* reject */
+        }
       else
         matched_line = 1;
     }
   else if (original_start > 0 && target->kind_on_disk == svn_node_file)
     {
       svn_linenum_t saved_line = target->current_line;
+      svn_linenum_t modified_start;
 
-      /* Scan for a match at the line where the hunk thinks it
-       * should be going. */
-      SVN_ERR(seek_to_line(target, original_start, scratch_pool));
-      if (target->current_line != original_start)
+      /* Check if the hunk is already applied.
+       * We only check for an exact match here, and don't bother checking
+       * for already applied patches with offset/fuzz, because such a
+       * check would be ambiguous. */
+      if (fuzz == 0)
         {
-          /* Seek failed. */
-          matched_line = 0;
+          modified_start = svn_diff_hunk_get_modified_start(hunk);
+          if (modified_start == 0)
+            {
+              /* Patch wants to delete the file. */
+              already_applied = target->locally_deleted;
+            }
+          else
+            {
+              SVN_ERR(seek_to_line(target, modified_start, scratch_pool));
+              SVN_ERR(scan_for_match(&matched_line, target, hunk, TRUE,
+                                     modified_start + 1,
+                                     fuzz, ignore_whitespace, TRUE,
+                                     cancel_func, cancel_baton,
+                                     scratch_pool));
+              already_applied = (matched_line == modified_start);
+            }
         }
       else
-        SVN_ERR(scan_for_match(&matched_line, target, hunk, TRUE,
-                               original_start + 1, fuzz,
-                               ignore_whitespace,
-                               cancel_func, cancel_baton,
-                               scratch_pool));
-
-      if (matched_line != original_start)
-        {
-          /* Scan the whole file again from the start. */
-          SVN_ERR(seek_to_line(target, 1, scratch_pool));
-
-          /* Scan forward towards the hunk's line and look for a line
-           * where the hunk matches. */
-          SVN_ERR(scan_for_match(&matched_line, target, hunk, FALSE,
-                                 original_start, fuzz,
-                                 ignore_whitespace,
-                                 cancel_func, cancel_baton,
-                                 scratch_pool));
-
-          /* In tie-break situations, we arbitrarily prefer early matches
-           * to save us from scanning the rest of the file. */
-          if (matched_line == 0)
+        already_applied = FALSE;
+
+      if (! already_applied)
+        {
+          /* Scan for a match at the line where the hunk thinks it
+           * should be going. */
+          SVN_ERR(seek_to_line(target, original_start, scratch_pool));
+          if (target->current_line != original_start)
             {
-              /* Scan forward towards the end of the file and look
-               * for a line where the hunk matches. */
-              SVN_ERR(scan_for_match(&matched_line, target, hunk, TRUE, 0,
-                                     fuzz, ignore_whitespace,
+              /* Seek failed. */
+              matched_line = 0;
+            }
+          else
+            SVN_ERR(scan_for_match(&matched_line, target, hunk, TRUE,
+                                   original_start + 1, fuzz,
+                                   ignore_whitespace, FALSE,
+                                   cancel_func, cancel_baton,
+                                   scratch_pool));
+
+          if (matched_line != original_start)
+            {
+              /* Scan the whole file again from the start. */
+              SVN_ERR(seek_to_line(target, 1, scratch_pool));
+
+              /* Scan forward towards the hunk's line and look for a line
+               * where the hunk matches. */
+              SVN_ERR(scan_for_match(&matched_line, target, hunk, FALSE,
+                                     original_start, fuzz,
+                                     ignore_whitespace, FALSE,
                                      cancel_func, cancel_baton,
                                      scratch_pool));
+
+              /* In tie-break situations, we arbitrarily prefer early matches
+               * to save us from scanning the rest of the file. */
+              if (matched_line == 0)
+                {
+                  /* Scan forward towards the end of the file and look
+                   * for a line where the hunk matches. */
+                  SVN_ERR(scan_for_match(&matched_line, target, hunk, TRUE, 0,
+                                         fuzz, ignore_whitespace, FALSE,
+                                         cancel_func, cancel_baton,
+                                         scratch_pool));
+                }
             }
         }
 
@@ -857,6 +998,7 @@ get_hunk_info(hunk_info_t **hi, patch_ta
   (*hi)->hunk = hunk;
   (*hi)->matched_line = matched_line;
   (*hi)->rejected = (matched_line == 0);
+  (*hi)->already_applied = already_applied;
   (*hi)->fuzz = fuzz;
 
   return SVN_NO_ERROR;
@@ -996,6 +1138,7 @@ apply_hunk(patch_target_t *target, hunk_
   /* Write the hunk's version to the patched result.
    * Don't write the lines which matched with fuzz. */
   lines_read = 0;
+  SVN_ERR(svn_diff_hunk_reset_modified_text(hi->hunk));
   iterpool = svn_pool_create(pool);
   do
     {
@@ -1096,7 +1239,9 @@ send_patch_notification(const patch_targ
 
           hi = APR_ARRAY_IDX(target->hunks, i, hunk_info_t *);
 
-          if (hi->rejected)
+          if (hi->already_applied)
+            action = svn_wc_notify_patch_hunk_already_applied;
+          else if (hi->rejected)
             action = svn_wc_notify_patch_rejected_hunk;
           else
             action = svn_wc_notify_patch_applied_hunk;
@@ -1198,7 +1343,7 @@ apply_one_patch(patch_target_t **patch_t
                                 result_pool, iterpool));
           fuzz++;
         }
-      while (hi->rejected && fuzz <= MAX_FUZZ);
+      while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
 
       APR_ARRAY_PUSH(target->hunks, hunk_info_t *) = hi;
     }
@@ -1211,7 +1356,9 @@ apply_one_patch(patch_target_t **patch_t
       svn_pool_clear(iterpool);
 
       hi = APR_ARRAY_IDX(target->hunks, i, hunk_info_t *);
-      if (hi->rejected)
+      if (hi->already_applied)
+        continue;
+      else if (hi->rejected)
         SVN_ERR(reject_hunk(target, hi, iterpool));
       else
         SVN_ERR(apply_hunk(target, hi, iterpool));

Modified: subversion/trunk/subversion/svn/notify.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/svn/notify.c?rev=962842&r1=962841&r2=962842&view=diff
==============================================================================
--- subversion/trunk/subversion/svn/notify.c (original)
+++ subversion/trunk/subversion/svn/notify.c Sat Jul 10 14:02:31 2010
@@ -362,6 +362,19 @@ notify(void *baton, const svn_wc_notify_
         goto print_error;
       break;
 
+    case svn_wc_notify_patch_hunk_already_applied:
+      nb->received_some_change = TRUE;
+      if ((err = svn_cmdline_printf(pool,
+                                    _(">         hunk "
+                                      "@@ -%lu,%lu +%lu,%lu @@ "
+                                      "already applied\n"),
+                                    n->hunk_original_start,
+                                    n->hunk_original_length,
+                                    n->hunk_modified_start,
+                                    n->hunk_modified_length)))
+        goto print_error;
+      break;
+
     case svn_wc_notify_update_update:
     case svn_wc_notify_merge_record_info:
       {

Modified: subversion/trunk/subversion/tests/cmdline/patch_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/patch_tests.py?rev=962842&r1=962841&r2=962842&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/patch_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/patch_tests.py Sat Jul 10 
14:02:31 2010
@@ -2223,6 +2223,202 @@ def patch_with_properties(sbox):
                                        1, # check-props
                                        1) # dry-run
 
+def patch_same_twice(sbox):
+  "apply the same patch twice"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+
+  patch_file_path = make_patch_path(sbox)
+  mu_path = os.path.join(wc_dir, 'A', 'mu')
+  beta_path = os.path.join(wc_dir, 'A', 'B', 'E', 'beta')
+
+  mu_contents = [
+    "Dear internet user,\n",
+    "\n",
+    "We wish to congratulate you over your email success in our computer\n",
+    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
+    "in which email addresses were used. All participants were selected\n",
+    "through a computer ballot system drawn from over 100,000 company\n",
+    "and 50,000,000 individual email addresses from all over the world.\n",
+    "\n",
+    "Your email address drew and have won the sum of  750,000 Euros\n",
+    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
+    "file with\n",
+    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
+    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
+    "    BATCH NUMBERS :\n",
+    "    EULO/1007/444/606/08;\n",
+    "    SERIAL NUMBER: 45327\n",
+    "and PROMOTION DATE: 13th June. 2009\n",
+    "\n",
+    "To claim your winning prize, you are to contact the appointed\n",
+    "agent below as soon as possible for the immediate release of your\n",
+    "winnings with the below details.\n",
+    "\n",
+    "Again, we wish to congratulate you over your email success in our\n"
+    "computer Balloting.\n"
+  ]
+
+  # Set mu contents
+  svntest.main.file_write(mu_path, ''.join(mu_contents))
+  expected_output = svntest.wc.State(wc_dir, {
+    'A/mu'       : Item(verb='Sending'),
+    })
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('A/mu', wc_rev=2)
+  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
+                                        expected_status, None, wc_dir)
+
+  # Apply patch
+
+  unidiff_patch = [
+    "Index: A/D/gamma\n",
+    "===================================================================\n",
+    "--- A/D/gamma\t(revision 1)\n",
+    "+++ A/D/gamma\t(working copy)\n",
+    "@@ -1 +1 @@\n",
+    "-This is the file 'gamma'.\n",
+    "+It is the file 'gamma'.\n",
+    "Index: iota\n",
+    "===================================================================\n",
+    "--- iota\t(revision 1)\n",
+    "+++ iota\t(working copy)\n",
+    "@@ -1 +1,2 @@\n",
+    " This is the file 'iota'.\n",
+    "+Some more bytes\n",
+    "\n",
+    "Index: new\n",
+    "===================================================================\n",
+    "--- new   (revision 0)\n",
+    "+++ new   (revision 0)\n",
+    "@@ -0,0 +1 @@\n",
+    "+new\n",
+    "\n",
+    "--- A/mu.orig     2009-06-24 15:23:55.000000000 +0100\n",
+    "+++ A/mu  2009-06-24 15:21:23.000000000 +0100\n",
+    "@@ -6,6 +6,9 @@\n",
+    " through a computer ballot system drawn from over 100,000 company\n",
+    " and 50,000,000 individual email addresses from all over the world.\n",
+    " \n",
+    "+It is a promotional program aimed at encouraging internet users;\n",
+    "+therefore you do not need to buy ticket to enter for it.\n",
+    "+\n",
+    " Your email address drew and have won the sum of  750,000 Euros\n",
+    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
+    " file with\n",
+    "@@ -14,11 +17,8 @@\n",
+    "     BATCH NUMBERS :\n",
+    "     EULO/1007/444/606/08;\n",
+    "     SERIAL NUMBER: 45327\n",
+    "-and PROMOTION DATE: 13th June. 2009\n",
+    "+and PROMOTION DATE: 14th June. 2009\n",
+    " \n",
+    " To claim your winning prize, you are to contact the appointed\n",
+    " agent below as soon as possible for the immediate release of your\n",
+    " winnings with the below details.\n",
+    "-\n",
+    "-Again, we wish to congratulate you over your email success in our\n",
+    "-computer Balloting.\n",
+    "Index: A/B/E/beta\n",
+    "===================================================================\n",
+    "--- A/B/E/beta    (revision 1)\n",
+    "+++ A/B/E/beta    (working copy)\n",
+    "@@ -1 +0,0 @@\n",
+    "-This is the file 'beta'.\n",
+  ]
+
+  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))
+
+  gamma_contents = "It is the file 'gamma'.\n"
+  iota_contents = "This is the file 'iota'.\nSome more bytes\n"
+  new_contents = "new\n"
+  mu_contents = [
+    "Dear internet user,\n",
+    "\n",
+    "We wish to congratulate you over your email success in our computer\n",
+    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
+    "in which email addresses were used. All participants were selected\n",
+    "through a computer ballot system drawn from over 100,000 company\n",
+    "and 50,000,000 individual email addresses from all over the world.\n",
+    "\n",
+    "It is a promotional program aimed at encouraging internet users;\n",
+    "therefore you do not need to buy ticket to enter for it.\n",
+    "\n",
+    "Your email address drew and have won the sum of  750,000 Euros\n",
+    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
+    "file with\n",
+    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
+    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
+    "    BATCH NUMBERS :\n",
+    "    EULO/1007/444/606/08;\n",
+    "    SERIAL NUMBER: 45327\n",
+    "and PROMOTION DATE: 14th June. 2009\n",
+    "\n",
+    "To claim your winning prize, you are to contact the appointed\n",
+    "agent below as soon as possible for the immediate release of your\n",
+    "winnings with the below details.\n",
+  ]
+
+  expected_output = [
+    'U         %s\n' % os.path.join(wc_dir, 'A', 'D', 'gamma'),
+    'U         %s\n' % os.path.join(wc_dir, 'iota'),
+    'A         %s\n' % os.path.join(wc_dir, 'new'),
+    'U         %s\n' % os.path.join(wc_dir, 'A', 'mu'),
+    'D         %s\n' % beta_path,
+  ]
+
+  expected_disk = svntest.main.greek_state.copy()
+  expected_disk.tweak('A/D/gamma', contents=gamma_contents)
+  expected_disk.tweak('iota', contents=iota_contents)
+  expected_disk.add({'new' : Item(contents=new_contents)})
+  expected_disk.tweak('A/mu', contents=''.join(mu_contents))
+  expected_disk.remove('A/B/E/beta')
+
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('A/D/gamma', status='M ')
+  expected_status.tweak('iota', status='M ')
+  expected_status.add({'new' : Item(status='A ', wc_rev=0)})
+  expected_status.tweak('A/mu', status='M ', wc_rev=2)
+  expected_status.tweak('A/B/E/beta', status='D ')
+
+  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
+  # apply the patch again
+  expected_output = [
+    'G         %s\n' % os.path.join(wc_dir, 'A', 'D', 'gamma'),
+    '>         hunk @@ -1,1 +1,1 @@ already applied\n',
+    'G         %s\n' % os.path.join(wc_dir, 'iota'),
+    '>         hunk @@ -1,1 +1,2 @@ already applied\n',
+    'G         %s\n' % os.path.join(wc_dir, 'new'),
+    '>         hunk @@ -0,0 +1,1 @@ already applied\n',
+    'G         %s\n' % os.path.join(wc_dir, 'A', 'mu'),
+    '>         hunk @@ -6,6 +6,9 @@ already applied\n',
+    '>         hunk @@ -14,11 +17,8 @@ already applied\n',
+    'Skipped \'%s\'\n' % beta_path,
+    'Summary of conflicts:\n',
+    '  Skipped paths: 1\n',
+  ]
+
+  expected_skip = wc.State('', {beta_path : Item()})
+
+  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
 
@@ -2247,6 +2443,7 @@ test_list = [ None,
               patch_replace_locally_deleted_file,
               patch_no_eol_at_eof,
               XFail(patch_with_properties),
+              patch_same_twice,
             ]
 
 if __name__ == '__main__':


Reply via email to