]> sjero.net Git - wget/commitdiff
Automated merge.
authorMicah Cowan <micah@cowan.name>
Sat, 4 Jul 2009 22:32:57 +0000 (15:32 -0700)
committerMicah Cowan <micah@cowan.name>
Sat, 4 Jul 2009 22:32:57 +0000 (15:32 -0700)
18 files changed:
1  2 
src/connect.c
src/convert.c
src/ftp-basic.c
src/ftp-ls.c
src/ftp.c
src/host.c
src/http.c
src/init.c
src/log.c
src/main.c
src/openssl.c
src/options.h
src/retr.c
src/retr.h
src/url.c
src/utils.c
src/utils.h
src/wget.h

diff --cc src/connect.c
Simple merge
diff --cc src/convert.c
Simple merge
diff --cc src/ftp-basic.c
Simple merge
diff --cc src/ftp-ls.c
Simple merge
diff --cc src/ftp.c
index 8e05a796bb56b864796412075cd39ecc0fd8b71c,9bc92a8945428c1da5721a1c1e08e24375e8d3a9..827e597e75890e6fa31622aa6aa70e146200889b
+++ b/src/ftp.c
@@@ -547,11 -592,97 +596,97 @@@ Error in server response, closing contr
                DEBUGP (("  Unix: '%s'\n  VMS: '%s'\n", target, ntarget));
                target = ntarget;
              }
+ #endif /* 0 */
+           /* 2004-09-20 SMS.
+              A relative directory is relative to the initial directory. 
+              Thus, what _is_ useful on VMS (and probably elsewhere) is
+              to CWD to the initial directory (ideally, whatever the
+              server reports, _exactly_, NOT badly UNIX-ixed), and then
+              CWD to the (new) relative directory.  This should probably
+              be restructured as a function, called once or twice, but
+              I'm lazy enough to take the badly indented loop short-cut
+              for now.
+           */
+           /* Decide on one pass (absolute) or two (relative).
+              The VMS restriction may be relaxed when the squirrely code
+              above is reformed.
+           */
+         if ((con->rs == ST_VMS) && (target[0] != '/'))
+           {
+             cwd_start = 0;
+             DEBUGP (("Using two-step CWD for relative path.\n"));
+           }
+         else
+           {
+               /* Go straight to the target. */
+             cwd_start = 1;
+           }
+           /* At least one VMS FTP server (TCPware V5.6-2) can switch to
+              a UNIX emulation mode when given a UNIX-like directory
+              specification (like "a/b/c").  If allowed to continue this
+              way, LIST interpretation will be confused, because the
+              system type (SYST response) will not be re-checked, and
+              future UNIX-format directory listings (for multiple URLs or
+              "-r") will be horribly misinterpreted.
+              The cheap and nasty work-around is to do a "CWD []" after a
+              UNIX-like directory specification is used.  (A single-level
+              directory is harmless.)  This puts the TCPware server back
+              into VMS mode, and does no harm on other servers.
+              Unlike the rest of this block, this particular behavior
+              _is_ VMS-specific, so it gets its own VMS test.
+           */
+         if ((con->rs == ST_VMS) && (strchr( target, '/') != NULL))
+             {
+               cwd_end = 3;
+             DEBUGP (("Using extra \"CWD []\" step for VMS server.\n"));
+             }
+           else
+             {
+               cwd_end = 2;
+             }
+           /* 2004-09-20 SMS. */
+           /* Sorry about the deviant indenting.  Laziness. */
+         for (cwd_count = cwd_start; cwd_count < cwd_end; cwd_count++)
+       {
+           switch (cwd_count)
+             {
+               case 0:
+               /* Step one (optional): Go to the initial directory,
+                  exactly as reported by the server.
+               */
+               targ = con->id;
+                 break;
+               case 1:
+               /* Step two: Go to the target directory.  (Absolute or
+                  relative will work now.)
+               */
+               targ = target;
+                 break;
+               case 2:
+                 /* Step three (optional): "CWD []" to restore server
+                    VMS-ness.
+                 */
+                 targ = "[]";
+                 break;
+               default:
+                 /* Can't happen. */
+                 assert (1);
+           }
  
            if (!opt.server_response)
-             logprintf (LOG_VERBOSE, "==> CWD %s ... ", 
 -            logprintf (LOG_VERBOSE, "==> CWD (%d) %s ... ",
 -             cwd_count, escnonprint (target));
 -          err = ftp_cwd (csock, targ);
++            logprintf (LOG_VERBOSE, "==> CWD (%d) %s ... ", cwd_count,
 +                       quotearg_style (escape_quoting_style, target));
 +          err = ftp_cwd (csock, target);
            /* FTPRERR, WRITEFAILED, FTPNSFOD */
            switch (err)
              {
@@@ -1127,15 -1319,26 +1357,28 @@@ ftp_loop_internal (struct url *u, struc
    uerr_t err;
    struct_stat st;
  
-   if (!con->target)
-     con->target = url_file_name (u);
+   /* Get the target, and set the name for the message accordingly. */
+   if ((f == NULL) && (con->target))
+     {
+       /* Explicit file (like ".listing"). */
+       locf = con->target;
+     }
+   else
+     {
+       /* URL-derived file.  Consider "-O file" name. */
+       con->target = url_file_name (u);
+       if (!opt.output_document)
+         locf = con->target;
+       else
+         locf = opt.output_document;
+     }
  
 -  if (opt.noclobber && file_exists_p (con->target))
 +  /* If the output_document was given, then this check was already done and
 +     the file didn't exist. Hence the !opt.output_document */
 +  if (opt.noclobber && !opt.output_document && file_exists_p (con->target))
      {
        logprintf (LOG_VERBOSE,
 -                 _("File `%s' already there; not retrieving.\n"), con->target);
 +                 _("File %s already there; not retrieving.\n"), quote (con->target));
        /* If the file is there, we suppose it's retrieved OK.  */
        return RETROK;
      }
@@@ -1585,29 -1770,51 +1825,41 @@@ Already have correct symlink %s -> %s\n
            break;
          }       /* switch */
  
-       /* Set the time-stamp information to the local file.  Symlinks
-          are not to be stamped because it sets the stamp on the
-          original.  :( */
-       if (!(f->type == FT_SYMLINK && !opt.retr_symlinks)
-           && f->tstamp != -1
-           && dlthis
-           && file_exists_p (con->target))
+       /* 2004-12-15 SMS.
+        * Set permissions _before_ setting the times, as setting the
+        * permissions changes the modified-time, at least on VMS.
+        * Also, use the opt.output_document name here, too, as
+        * appropriate.  (Do the test once, and save the result.)
+        */
 -      /* #### This code repeats in http.c and ftp.c.  Move it to a
 -         function!  */
 -      actual_target = NULL;
 -      if (opt.output_document)
 -        {
 -          if (output_stream_regular)
 -            actual_target = opt.output_document;
 -        }
 -      else
 -        actual_target = con->target;
++      set_local_file (&actual_target, con->target);
+       /* If downloading a plain file, set valid (non-zero) permissions. */
+       if (dlthis && (actual_target != NULL) && (f->type == FT_PLAINFILE))
          {
-           const char *fl = NULL;
-           set_local_file (&fl, con->target);
-           if (fl)
-             touch (fl, f->tstamp);
+           if (f->perms)
+             chmod (actual_target, f->perms);
+           else
+             DEBUGP (("Unrecognized permissions for %s.\n", actual_target));
          }
-       else if (f->tstamp == -1)
-         logprintf (LOG_NOTQUIET, _("%s: corrupt time-stamp.\n"), con->target);
  
-       if (f->perms && f->type == FT_PLAINFILE && dlthis)
+       /* Set the time-stamp information to the local file.  Symlinks
+          are not to be stamped because it sets the stamp on the
+          original.  :( */
 -
+       if (actual_target != NULL)
          {
-           if (opt.preserve_perm)
-             chmod (con->target, f->perms);
+           if (!(f->type == FT_SYMLINK && !opt.retr_symlinks)
+               && f->tstamp != -1
+               && dlthis
+               && file_exists_p (con->target))
+             {
+               touch (actual_target, f->tstamp);
+             }
+           else if (f->tstamp == -1)
+             logprintf (LOG_NOTQUIET, _("%s: corrupt time-stamp.\n"),
 -            actual_target);
++                       actual_target);
          }
-       else
-         DEBUGP (("Unrecognized permissions for %s.\n", con->target));
  
        xfree (con->target);
        con->target = old_target;
@@@ -1822,10 -2028,10 +2074,10 @@@ ftp_retrieve_glob (struct url *u, ccon 
            /* No luck.  */
            /* #### This message SUCKS.  We should see what was the
               reason that nothing was retrieved.  */
 -          logprintf (LOG_VERBOSE, _("No matches on pattern `%s'.\n"),
 -                     escnonprint (u->file));
 +          logprintf (LOG_VERBOSE, _("No matches on pattern %s.\n"),
 +                     quote (u->file));
          }
-       else /* GLOB_GETONE or GLOB_GETALL */
+       else if (action == GLOB_GETONE) /* GLOB_GETONE or GLOB_GETALL */
          {
            /* Let's try retrieving it anyway.  */
            con->st |= ON_YOUR_OWN;
diff --cc src/host.c
Simple merge
diff --cc src/http.c
index ae89c46d642fb5e5c6807e88576b5f94422730e4,d9c85ed34b0a956495f9a24b46a45a2e8cda27fa..4e49ed4fd1ce0748e598890656a8eb94da2d9de9
@@@ -66,10 -66,13 +66,14 @@@ as that of the covered work.  *
  #include "test.h"
  #endif
  
- extern char *version_string;
+ #include "version.h"
+ #ifdef __VMS
+ # include "vms.h"
+ #endif /* def __VMS */
  
  /* Forward decls. */
 +struct http_stat;
  static char *create_authorization_line (const char *, const char *,
                                          const char *, const char *,
                                          const char *, bool *);
diff --cc src/init.c
index 23f8cb2cfd954271e8f49107ac761a5cb0a69ba3,43a0f95e363e1e428e4a6a308b5852a57ca195ce..879d3aac80b27a419cdd9154128fe8cfaf2fe74b
@@@ -404,68 -395,40 +407,75 @@@ wgetrc_env_file_name (void
          }
        return xstrdup (env);
      }
 +  return NULL;
 +}
  
 +/* Check for the existance of '$HOME/.wgetrc' and return it's path
 +   if it exists and is set.  */
 +char *
 +wgetrc_user_file_name (void) 
 +{
 +  char *home = home_dir ();
 +  char *file = NULL;
+   /* If that failed, try $HOME/.wgetrc (or equivalent).  */
+ #ifdef __VMS
+   file = "SYS$LOGIN:.wgetrc";
+ #else /* def __VMS */
+   home = home_dir ();
    if (home)
      file = aprintf ("%s/.wgetrc", home);
    xfree_null (home);
-   char *home = NULL;
+ #endif /* def __VMS [else] */
 +  if (!file)
 +    return NULL;
 +  if (!file_exists_p (file))
 +    {
 +      xfree (file);
 +      return NULL;
 +    }
 +  return file;
 +}
 +
 +/* Return the path to the user's .wgetrc.  This is either the value of
 +   `WGETRC' environment variable, or `$HOME/.wgetrc'.
 +
 +   Additionally, for windows, look in the directory where wget.exe 
 +   resides.  */
 +char *
 +wgetrc_file_name (void)
 +{
 +  char *file = wgetrc_env_file_name ();
 +  if (file && *file)
 +    return file;
 +  
 +  file = wgetrc_user_file_name ();
 +
  #ifdef WINDOWS
    /* Under Windows, if we still haven't found .wgetrc, look for the file
       `wget.ini' in the directory where `wget.exe' resides; we do this for
       backward compatibility with previous versions of Wget.
       SYSTEM_WGETRC should not be defined under WINDOWS.  */
-   home = home_dir ();
--  if (!file || !file_exists_p (file))
++  if (!file)
      {
++      char *home = home_dir ();
        xfree_null (file);
        file = NULL;
        home = ws_mypath ();
        if (home)
--        file = aprintf ("%s/wget.ini", home);
++        {
++          file = aprintf ("%s/wget.ini", home);
++          if (!file_exists_p (file))
++            {
++              xfree (file);
++              file = NULL;
++            }
++          xfree (home);
++        }
      }
-   xfree_null (home);
  #endif /* WINDOWS */
  
--  if (!file)
--    return NULL;
--  if (!file_exists_p (file))
--    {
--      xfree (file);
--      return NULL;
--    }
    return file;
  }
  
diff --cc src/log.c
index b62bf9dd3781cc1e9d2b7a7e59d0471a6ac6b161,cbdf59fbc61f06e132f4f27f235a662257b11926..9ee0621160ebca923233e94889ce72153a532049
+++ b/src/log.c
@@@ -43,7 -43,20 +43,20 @@@ as that of the covered work.  *
  #include "utils.h"
  #include "log.h"
  
- /* This file implement support for "logging".  Logging means printing
+ /* 2005-10-25 SMS.
+    VMS log files are often VFC record format, not stream, so fputs() can
+    produce multiple records, even when there's no newline terminator in
+    the buffer.  The result is unsightly output with spurious newlines.
+    Using fprintf() instead of fputs(), along with inhibiting some
+    fflush() activity below, seems to solve the problem.
+ */
+ #ifdef __VMS
+ # define FPUTS( s, f) fprintf( (f), "%s", (s))
+ #else /* def __VMS */
+ # define FPUTS( s, f) fputs( (s), (f))
+ #endif /* def __VMS [else] */
 -/* This file impplement support for "logging".  Logging means printing
++/* This file implements support for "logging".  Logging means printing
     output, plus several additional features:
  
     - Cataloguing output by importance.  You can specify that a log
diff --cc src/main.c
index 85d7ff49693d9ce87fbd8684aac355c41c11e2a3,3e32c40967e342c4874ece4901095f07e0f8486b..5898a1989058995a619b3d244037f853c4f4684d
@@@ -56,9 -56,13 +56,15 @@@ as that of the covered work.  *
  #include "http.h"               /* for save_cookies */
  
  #include <getopt.h>
 +#include <getpass.h>
 +#include <quote.h>
  
+ #ifdef __VMS
+ #include "vms.h"
+ #endif /* __VMS */
+ #include "version.h"
  #ifndef PATH_SEPARATOR
  # define PATH_SEPARATOR '/'
  #endif
@@@ -617,10 -606,14 +630,15 @@@ Recursive download:\n")
      N_("\
         --delete-after       delete files locally after downloading them.\n"),
      N_("\
 -  -k,  --convert-links      make links in downloaded HTML point to local files.\n"),
 +  -k,  --convert-links      make links in downloaded HTML or CSS point to\n\
 +                            local files.\n"),
+ #ifdef __VMS
+     N_("\
+   -K,  --backup-converted   before converting file X, back up as X_orig.\n"),
+ #else /* def __VMS */
      N_("\
    -K,  --backup-converted   before converting file X, back up as X.orig.\n"),
+ #endif /* def __VMS [else] */
      N_("\
    -m,  --mirror             shortcut for -N -r -l inf --no-remove-listing.\n"),
      N_("\
@@@ -660,10 -653,10 +678,10 @@@ Recursive accept/reject:\n")
      N_("Mail bug reports and suggestions to <bug-wget@gnu.org>.\n")
    };
  
 -  int i;
 +  size_t i;
  
    printf (_("GNU Wget %s, a non-interactive network retriever.\n"),
-           version_string);
+           VERSION_STRING);
    print_usage ();
  
    for (i = 0; i < countof (help); i++)
@@@ -754,62 -694,12 +772,68 @@@ format_and_print_line (const char *pref
  static void
  print_version (void)
  {
 -  printf ("GNU Wget %s built on %s.\n\n", VERSION_STRING, OS_TYPE);
 +  const char *wgetrc_title  = _("Wgetrc: ");
 +  const char *locale_title  = _("Locale: ");
 +  const char *compile_title = _("Compile: ");
 +  const char *link_title    = _("Link: ");
 +  char *line;
 +  char *env_wgetrc, *user_wgetrc;
 +  int i;
 +
 +  printf (_("GNU Wget %s\n\n"), version_string);
+ #ifdef __VMS
+   printf ("GNU Wget %s built on VMS %s %s.\n\n",
+    VERSION_STRING, vms_arch(), vms_vers());
+ #else /* def __VMS */
++  printf ("GNU Wget %s built on %s.\n\n", version_string, OS_TYPE);
+ #endif /* def __VMS */
 +  /* compiled_features is a char*[]. We limit the characters per
 +     line to MAX_CHARS_PER_LINE and prefix each line with a constant
 +     number of spaces for proper alignment. */
 +  for (i = 0; compiled_features[i] != NULL; ) 
 +    {
 +      int line_length = MAX_CHARS_PER_LINE;
 +      while ((line_length > 0) && (compiled_features[i] != NULL)) 
 +        {
 +          printf ("%s ", compiled_features[i]);
 +          line_length -= strlen (compiled_features[i]) + 2;
 +          i++;
 +        }
 +      printf ("\n");
 +    }
 +  printf ("\n");
 +  /* Handle the case when $WGETRC is unset and $HOME/.wgetrc is 
 +     absent. */
 +  printf ("%s\n", wgetrc_title);
 +  env_wgetrc = wgetrc_env_file_name ();
 +  if (env_wgetrc && *env_wgetrc) 
 +    {
 +      printf ("    %s (env)\n", env_wgetrc);
 +      xfree (env_wgetrc);
 +    }
 +  user_wgetrc = wgetrc_user_file_name ();
 +  if (user_wgetrc) 
 +    {
 +      printf ("    %s (user)\n", user_wgetrc);
 +      xfree (user_wgetrc);
 +    }
 +#ifdef SYSTEM_WGETRC
 +  printf ("    %s (system)\n", SYSTEM_WGETRC);
 +#endif
 +
 +  format_and_print_line (locale_title,
 +                       LOCALEDIR, 
 +                       MAX_CHARS_PER_LINE);
 +  
 +  format_and_print_line (compile_title,
 +                       compilation_string,
 +                       MAX_CHARS_PER_LINE);
 +
 +  format_and_print_line (link_title,
 +                       link_string,
 +                       MAX_CHARS_PER_LINE);
 +
 +  printf ("\n");
    /* TRANSLATORS: When available, an actual copyright character
       (cirle-c) should be used in preference to "(C)". */
    fputs (_("\
diff --cc src/openssl.c
Simple merge
diff --cc src/options.h
index 8dc7fee2ad3d34bd0e79afae75769b6bbb95ad33,e0d7521da4f054e61e2930c287b5cda4d6be94cb..382fe312c516aae637ba25bf57fe9b9de55f047a
@@@ -234,11 -235,12 +234,16 @@@ struct option
    
    bool content_disposition;   /* Honor HTTP Content-Disposition header. */
    bool auth_without_challenge;  /* Issue Basic authentication creds without
 -                                  waiting for a challenge. */
 +                                   waiting for a challenge. */
 +
 +  bool enable_iri;
 +  char *encoding_remote;
 +  char *locale;
+ #ifdef __VMS
+   int ftp_stmlf;                /* Force Stream_LF format for binary FTP. */
+ #endif /* def __VMS */
  };
  
  extern struct options opt;
diff --cc src/retr.c
Simple merge
diff --cc src/retr.h
Simple merge
diff --cc src/url.c
Simple merge
diff --cc src/utils.c
index 15e3f89ba392b726050e6ba7dccc4af502bca4a7,8c5c8dc3aaaa138dcf05cd032acaf80d03517dc5..7a93376a46d36a0bd246f4ae0ef057b3151c7300
@@@ -88,32 -92,87 +92,113 @@@ as that of the covered work.  *
  #include "test.h"
  #endif 
  
 +static void
 +memfatal (const char *context, long attempted_size)
 +{
 +  /* Make sure we don't try to store part of the log line, and thus
 +     call malloc.  */
 +  log_set_save_context (false);
 +
 +  /* We have different log outputs in different situations:
 +     1) output without bytes information
 +     2) output with bytes information  */
 +  if (attempted_size == UNKNOWN_ATTEMPTED_SIZE)
 +    {
 +      logprintf (LOG_ALWAYS,
 +                 _("%s: %s: Failed to allocate enough memory; memory exhausted.\n"),
 +                 exec_name, context);
 +    }
 +  else
 +    {
 +      logprintf (LOG_ALWAYS,
 +                 _("%s: %s: Failed to allocate %ld bytes; memory exhausted.\n"),
 +                 exec_name, context, attempted_size);
 +    }
 +
 +  exit (1);
 +}
 +
+ /* Character property table for (re-)escaping VMS ODS5 extended file
+    names.  Note that this table ignores Unicode.
+    ODS2 valid characters: 0-9 A-Z a-z $ - _ ~
+    ODS5 Invalid characters:
+       C0 control codes (0x00 to 0x1F inclusive)
+       Asterisk (*)
+       Question mark (?)
+    ODS5 Invalid characters only in VMS V7.2 (which no one runs, right?):
+       Double quotation marks (")
+       Backslash (\)
+       Colon (:)
+       Left angle bracket (<)
+       Right angle bracket (>)
+       Slash (/)
+       Vertical bar (|)
+    Characters escaped by "^":
+       SP  !  #  %  &  '  (  )  +  ,  .  ;  =  @  [  ]  ^  `  {  }  ~
+    Either "^_" or "^ " is accepted as a space.  Period (.) is a special
+    case.  Note that un-escaped < and > can also confuse a directory
+    spec.
+    Characters put out as ^xx:
+       7F (DEL)
+       80-9F (C1 control characters)
+       A0 (nonbreaking space)
+       FF (Latin small letter y diaeresis)
+    Other cases:
+       Unicode: "^Uxxxx", where "xxxx" is four hex digits.
+     Property table values:
+       Normal escape:    1
+       Space:            2
+       Dot:              4
+       Hex-hex escape:   8
+       ODS2 normal:     16
+       ODS2 lower case: 32
+       Hex digit:       64
+ */
+ unsigned char char_prop[ 256] = {
+ /* NUL SOH STX ETX EOT ENQ ACK BEL   BS  HT  LF  VT  FF  CR  SO  SI */
+     0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  0,
+ /* DLE DC1 DC2 DC3 DC4 NAK SYN ETB  CAN  EM SUB ESC  FS  GS  RS  US */
+     0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  0,
+ /*  SP  !   "   #   $   %   &   '    (   )   *   +   ,   -   .   /  */
+     2,  1,  0,  1, 16,  1,  1,  1,   1,  1,  0,  1,  1, 16,  4,  0,
+ /*  0   1   2   3   4   5   6   7    8   9   :   ;   <   =   >   ?  */
+    80, 80, 80, 80, 80, 80, 80, 80,  80, 80,  0,  1,  1,  1,  1,  1,
+ /*  @   A   B   C   D   E   F   G    H   I   J   K   L   M   N   O  */
+     1, 80, 80, 80, 80, 80, 80, 16,  16, 16, 16, 16, 16, 16, 16, 16,
+ /*  P   Q   R   S   T   U   V   W    X   Y   Z   [   \   ]   ^   _  */
+    16, 16, 16, 16, 16, 16, 16, 16,  16, 16, 16,  1,  0,  1,  1, 16,
+ /*  `   a   b   c   d   e   f   g    h   i   j   k   l   m   n   o  */
+     1, 96, 96, 96, 96, 96, 96, 32,  32, 32, 32, 32, 32, 32, 32, 32,
+ /*  p   q   r   s   t   u   v   w    x   y   z   {   |   }   ~  DEL */
+    32, 32, 32, 32, 32, 32, 32, 32,  32, 32, 32,  1,  0,  1, 17,  8,
+     8,  8,  8,  8,  8,  8,  8,  8,   8,  8,  8,  8,  8,  8,  8,  8,
+     8,  8,  8,  8,  8,  8,  8,  8,   8,  8,  8,  8,  8,  8,  8,  8,
+     8,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  0,
+     0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  0,
+     0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  0,
+     0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  0,
+     0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  0,
+     0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  8
+ };
  /* Utility function: like xstrdup(), but also lowercases S.  */
  
  char *
diff --cc src/utils.h
Simple merge
diff --cc src/wget.h
Simple merge