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 2022/01/14 14:01:51 UTC

svn commit: r1897034 [22/37] - in /subversion/branches/multi-wc-format: ./ build/ build/ac-macros/ build/generator/ build/generator/swig/ build/generator/templates/ contrib/client-side/ contrib/client-side/svn_load_dirs/ contrib/hook-scripts/ contrib/s...

Modified: subversion/branches/multi-wc-format/subversion/libsvn_subr/io.c
URL: http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/subversion/libsvn_subr/io.c?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/subversion/libsvn_subr/io.c (original)
+++ subversion/branches/multi-wc-format/subversion/libsvn_subr/io.c Fri Jan 14 14:01:45 2022
@@ -144,6 +144,14 @@
 #ifdef WIN32
 
 #if _WIN32_WINNT < 0x600 /* Does the SDK assume Windows Vista+? */
+typedef struct _FILE_BASIC_INFO {
+  LARGE_INTEGER CreationTime;
+  LARGE_INTEGER LastAccessTime;
+  LARGE_INTEGER LastWriteTime;
+  LARGE_INTEGER ChangeTime;
+  DWORD FileAttributes;
+} FILE_BASIC_INFO, *PFILE_BASIC_INFO;
+
 typedef struct _FILE_RENAME_INFO {
   BOOL   ReplaceIfExists;
   HANDLE RootDirectory;
@@ -155,8 +163,15 @@ typedef struct _FILE_DISPOSITION_INFO {
   BOOL DeleteFile;
 } FILE_DISPOSITION_INFO, *PFILE_DISPOSITION_INFO;
 
+typedef struct _FILE_ATTRIBUTE_TAG_INFO {
+  DWORD FileAttributes;
+  DWORD ReparseTag;
+} FILE_ATTRIBUTE_TAG_INFO, *PFILE_ATTRIBUTE_TAG_INFO;
+
+#define FileBasicInfo 0
 #define FileRenameInfo 3
 #define FileDispositionInfo 4
+#define FileAttributeTagInfo 9
 #endif /* WIN32 < Vista */
 
 /* One-time initialization of the late bound Windows API functions. */
@@ -169,19 +184,30 @@ typedef DWORD (WINAPI *GETFINALPATHNAMEB
                DWORD cchFilePath,
                DWORD dwFlags);
 
+typedef BOOL (WINAPI *GetFileInformationByHandleEx_t)(HANDLE hFile,
+                                                      int FileInformationClass,
+                                                      LPVOID lpFileInformation,
+                                                      DWORD dwBufferSize);
+
 typedef BOOL (WINAPI *SetFileInformationByHandle_t)(HANDLE hFile,
                                                     int FileInformationClass,
                                                     LPVOID lpFileInformation,
                                                     DWORD dwBufferSize);
 
 static GETFINALPATHNAMEBYHANDLE get_final_path_name_by_handle_proc = NULL;
+static GetFileInformationByHandleEx_t get_file_information_by_handle_ex_proc = NULL;
 static SetFileInformationByHandle_t set_file_information_by_handle_proc = NULL;
 
-/* Forward declaration. */
+/* Forward declarations. */
 static svn_error_t * io_win_read_link(svn_string_t **dest,
                                       const char *path,
                                       apr_pool_t *pool);
 
+static svn_error_t * io_win_check_path(svn_node_kind_t *kind_p,
+                                       svn_boolean_t *is_symlink_p,
+                                       const char *path,
+                                       apr_pool_t *pool);
+
 #endif
 
 /* Forward declaration */
@@ -342,13 +368,7 @@ io_check_path(const char *path,
   /* Not using svn_io_stat() here because we want to check the
      apr_err return explicitly. */
   SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
-#ifdef WIN32
-  /* on Windows, svn does not handle reparse points or hard links.
-     So ignore the 'resolve_symlinks' flag. */
-  flags = APR_FINFO_MIN;
-#else
   flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK);
-#endif
   apr_err = apr_stat(&finfo, path_apr, flags, pool);
 
   if (APR_STATUS_IS_ENOENT(apr_err))
@@ -410,8 +430,12 @@ svn_io_check_resolved_path(const char *p
                            svn_node_kind_t *kind,
                            apr_pool_t *pool)
 {
+#if WIN32
+  return io_win_check_path(kind, NULL, path, pool);
+#else
   svn_boolean_t ignored;
   return io_check_path(path, TRUE, &ignored, kind, pool);
+#endif
 }
 
 svn_error_t *
@@ -419,8 +443,19 @@ svn_io_check_path(const char *path,
                   svn_node_kind_t *kind,
                   apr_pool_t *pool)
 {
+#if WIN32
+  svn_boolean_t is_symlink;
+
+  SVN_ERR(io_win_check_path(kind, &is_symlink, path, pool));
+
+  if (is_symlink)
+    *kind = svn_node_file;
+
+  return SVN_NO_ERROR;
+#else
   svn_boolean_t ignored;
   return io_check_path(path, FALSE, &ignored, kind, pool);
+#endif
 }
 
 svn_error_t *
@@ -429,7 +464,23 @@ svn_io_check_special_path(const char *pa
                           svn_boolean_t *is_special,
                           apr_pool_t *pool)
 {
+#ifdef WIN32
+  svn_boolean_t is_symlink;
+
+  SVN_ERR(io_win_check_path(kind, &is_symlink, path, pool));
+
+  if (is_symlink)
+    {
+      *is_special = TRUE;
+      *kind = svn_node_file;
+    }
+  else
+    *is_special = FALSE;
+
+  return SVN_NO_ERROR;
+#else
   return io_check_path(path, FALSE, is_special, kind, pool);
+#endif
 }
 
 struct temp_file_cleanup_s
@@ -1532,7 +1583,7 @@ reown_file(const char *path,
 }
 
 /* Determine what the PERMS for a new file should be by looking at the
-   permissions of a temporary file that we create in DIRECTORY.  
+   permissions of a temporary file that we create in DIRECTORY.
    DIRECTORY can be NULL in which case the system temporary dir is used.
    Unfortunately, umask() as defined in POSIX provides no thread-safe way
    to get at the current value of the umask, so what we're doing here is
@@ -1622,13 +1673,14 @@ merge_default_file_perms(apr_file_t *fd,
    that attempts to honor the users umask when dealing with
    permission changes.  It is a no-op when invoked on a symlink. */
 static svn_error_t *
-io_set_file_perms(const char *path,
-                  svn_boolean_t change_readwrite,
-                  svn_boolean_t enable_write,
-                  svn_boolean_t change_executable,
-                  svn_boolean_t executable,
-                  svn_boolean_t ignore_enoent,
-                  apr_pool_t *pool)
+io_set_perms(const char *path,
+             svn_boolean_t is_file,
+             svn_boolean_t change_readwrite,
+             svn_boolean_t enable_write,
+             svn_boolean_t change_executable,
+             svn_boolean_t executable,
+             svn_boolean_t ignore_enoent,
+             apr_pool_t *pool)
 {
   apr_status_t status;
   const char *path_apr;
@@ -1648,9 +1700,16 @@ io_set_file_perms(const char *path,
                             || SVN__APR_STATUS_IS_ENOTDIR(status)))
         return SVN_NO_ERROR;
       else if (status != APR_ENOTIMPL)
-        return svn_error_wrap_apr(status,
-                                  _("Can't change perms of file '%s'"),
-                                  svn_dirent_local_style(path, pool));
+        {
+          if (is_file)
+            return svn_error_wrap_apr(status,
+                                      _("Can't change perms of file '%s'"),
+                                      svn_dirent_local_style(path, pool));
+          else
+            return svn_error_wrap_apr(status,
+                                      _("Can't change perms of directory '%s'"),
+                                      svn_dirent_local_style(path, pool));
+        }
       return SVN_NO_ERROR;
     }
 
@@ -1750,10 +1809,50 @@ io_set_file_perms(const char *path,
       status = apr_file_attrs_set(path_apr, attrs, attrs_values, pool);
     }
 
-  return svn_error_wrap_apr(status,
-                            _("Can't change perms of file '%s'"),
-                            svn_dirent_local_style(path, pool));
+  if (is_file)
+    {
+      return svn_error_wrap_apr(status,
+                                _("Can't change perms of file '%s'"),
+                                svn_dirent_local_style(path, pool));
+    }
+  else
+    {
+      return svn_error_wrap_apr(status,
+                                _("Can't change perms of directory '%s'"),
+                                svn_dirent_local_style(path, pool));
+    }
+}
+
+static svn_error_t *
+io_set_file_perms(const char *path,
+                  svn_boolean_t change_readwrite,
+                  svn_boolean_t enable_write,
+                  svn_boolean_t change_executable,
+                  svn_boolean_t executable,
+                  svn_boolean_t ignore_enoent,
+                  apr_pool_t *pool)
+{
+  return svn_error_trace(io_set_perms(path, TRUE,
+                                      change_readwrite, enable_write,
+                                      change_executable, executable,
+                                      ignore_enoent, pool));
 }
+
+static svn_error_t *
+io_set_dir_perms(const char *path,
+                 svn_boolean_t change_readwrite,
+                 svn_boolean_t enable_write,
+                 svn_boolean_t change_executable,
+                 svn_boolean_t executable,
+                 svn_boolean_t ignore_enoent,
+                 apr_pool_t *pool)
+{
+  return svn_error_trace(io_set_perms(path, FALSE,
+                                      change_readwrite, enable_write,
+                                      change_executable, executable,
+                                      ignore_enoent, pool));
+}
+
 #endif /* !WIN32 && !__OS2__ */
 
 #ifdef WIN32
@@ -1902,6 +2001,9 @@ static svn_error_t *win_init_dynamic_imp
       get_final_path_name_by_handle_proc = (GETFINALPATHNAMEBYHANDLE)
         GetProcAddress(kernel32, "GetFinalPathNameByHandleW");
 
+      get_file_information_by_handle_ex_proc = (GetFileInformationByHandleEx_t)
+        GetProcAddress(kernel32, "GetFileInformationByHandleEx");
+
       set_file_information_by_handle_proc = (SetFileInformationByHandle_t)
         GetProcAddress(kernel32, "SetFileInformationByHandle");
     }
@@ -1978,6 +2080,33 @@ static svn_error_t * io_win_read_link(sv
       }
 }
 
+/* Wrapper around Windows API function GetFileInformationByHandleEx() that
+ * returns APR status instead of boolean flag. */
+static apr_status_t
+win32_get_file_information_by_handle(HANDLE hFile,
+                                     int FileInformationClass,
+                                     LPVOID lpFileInformation,
+                                     DWORD dwBufferSize)
+{
+  svn_error_clear(svn_atomic__init_once(&win_dynamic_imports_state,
+                                        win_init_dynamic_imports,
+                                        NULL, NULL));
+
+  if (!get_file_information_by_handle_ex_proc)
+    {
+      return SVN_ERR_UNSUPPORTED_FEATURE;
+    }
+
+  if (!get_file_information_by_handle_ex_proc(hFile, FileInformationClass,
+                                              lpFileInformation,
+                                              dwBufferSize))
+    {
+      return apr_get_os_error();
+    }
+
+  return APR_SUCCESS;
+}
+
 /* Wrapper around Windows API function SetFileInformationByHandle() that
  * returns APR status instead of boolean flag. */
 static apr_status_t
@@ -2005,6 +2134,105 @@ win32_set_file_information_by_handle(HAN
   return APR_SUCCESS;
 }
 
+/* Fast Win32-specific helper for svn_io_check_path() and related functions
+ * that only requires a single GetFileAttributes() call in most cases.
+ */
+static svn_error_t * io_win_check_path(svn_node_kind_t *kind_p,
+                                       svn_boolean_t *is_symlink_p,
+                                       const char *path,
+                                       apr_pool_t *pool)
+{
+  DWORD attrs;
+  const WCHAR *wpath;
+  apr_status_t status;
+
+  if (path[0] == '\0')
+    path = ".";
+
+  SVN_ERR(svn_io__utf8_to_unicode_longpath(&wpath, path, pool));
+
+  attrs = GetFileAttributesW(wpath);
+  if (attrs == INVALID_FILE_ATTRIBUTES)
+    {
+      status = apr_get_os_error();
+      if (APR_STATUS_IS_ENOENT(status) || SVN__APR_STATUS_IS_ENOTDIR(status))
+        {
+          *kind_p = svn_node_none;
+          if (is_symlink_p)
+            *is_symlink_p = FALSE;
+          return SVN_NO_ERROR;
+        }
+      else
+        {
+          return svn_error_wrap_apr(status, _("Can't stat '%s'"),
+                                    svn_dirent_local_style(path, pool));
+        }
+    }
+
+  if (attrs & FILE_ATTRIBUTE_DIRECTORY)
+    *kind_p = svn_node_dir;
+  else
+    *kind_p = svn_node_file;
+
+  /* If this is a reparse point, and if we've been asked to check whether
+     we are dealing with a symlink, then open the file and check that.
+
+     Otherwise, it's either definitely not a symlink or the caller
+     doesn't care about this distinction.
+   */
+  if (is_symlink_p && (attrs & FILE_ATTRIBUTE_REPARSE_POINT))
+    {
+      const WCHAR *wfname;
+      HANDLE hFile;
+      FILE_ATTRIBUTE_TAG_INFO taginfo = { 0 };
+
+      SVN_ERR(svn_io__utf8_to_unicode_longpath(&wfname, path, pool));
+
+      hFile = CreateFileW(wfname, FILE_READ_ATTRIBUTES,
+                          FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                          NULL, OPEN_EXISTING,
+                          FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
+                          NULL);
+      if (hFile == INVALID_HANDLE_VALUE)
+        {
+          status = apr_get_os_error();
+          if (APR_STATUS_IS_ENOENT(status) || SVN__APR_STATUS_IS_ENOTDIR(status))
+            {
+              *kind_p = svn_node_none;
+              *is_symlink_p = FALSE;
+              return SVN_NO_ERROR;
+            }
+          else
+            {
+              return svn_error_wrap_apr(status, _("Can't stat '%s'"),
+                                        svn_dirent_local_style(path, pool));
+            }
+        }
+
+      status = win32_get_file_information_by_handle(hFile, FileAttributeTagInfo,
+                                                    &taginfo, sizeof(taginfo));
+      CloseHandle(hFile);
+
+      if (status)
+        return svn_error_wrap_apr(status, _("Can't stat '%s'"),
+                                  svn_dirent_local_style(path, pool));
+
+      /* The surrogate bit in the reparse tag specifies if "the file or directory
+         represents another named entity in the system" which is used to determine
+         if this reparse point behaves like a symlink.
+
+         https://docs.microsoft.com/en-us/windows/desktop/fileio/reparse-point-tags
+       */
+      *is_symlink_p = IsReparseTagNameSurrogate(taginfo.ReparseTag);
+    }
+  else if (is_symlink_p)
+    {
+      *is_symlink_p = FALSE;
+    }
+
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_io__win_delete_file_on_close(apr_file_t *file,
                                  const char *path,
@@ -2102,6 +2330,83 @@ svn_io__win_rename_open_file(apr_file_t
   return SVN_NO_ERROR;
 }
 
+/* Number of micro-seconds between the beginning of the Windows epoch
+ * (Jan. 1, 1601) and the Unix epoch (Jan. 1, 1970)
+ */
+#ifndef APR_DELTA_EPOCH_IN_USEC
+#define APR_DELTA_EPOCH_IN_USEC   APR_TIME_C(11644473600000000)
+#endif
+
+svn_error_t *
+svn_io__win_set_file_basic_info(apr_file_t *file,
+                                const char *path,
+                                apr_time_t set_mtime,
+                                svn_boolean_t set_read_only,
+                                apr_pool_t *pool)
+{
+  FILE_BASIC_INFO info;
+  HANDLE hFile;
+  apr_status_t status;
+
+  apr_os_file_get(&hFile, file);
+
+  if (set_read_only)
+    {
+      status = win32_get_file_information_by_handle(hFile, FileBasicInfo,
+                                                    &info, sizeof(info));
+      if (status)
+        {
+          return svn_error_wrap_apr(status, _("Can't get attributes of '%s'"),
+                                    svn_dirent_local_style(path, pool));
+        }
+    }
+
+  info.CreationTime.QuadPart = 0;
+  info.LastAccessTime.QuadPart = 0;
+  info.ChangeTime.QuadPart = 0;
+
+  if (set_mtime == SVN_IO__WIN_TIME_UNCHANGED)
+    {
+      /* If you specify a value of zero for any of the XxxTime members of the
+         FILE_BASIC_INFORMATION structure, the ZwSetInformationFile function
+         keeps a file's current setting for that time.
+         https://docs.microsoft.com/windows-hardware/drivers/ddi/wdm/ns-wdm-_file_basic_information#remarks
+       */
+      info.LastWriteTime.QuadPart = 0;
+    }
+  else if (set_mtime == SVN_IO__WIN_TIME_SUSPEND_UPDATE)
+    {
+      /* File system updates the values of the LastAccessTime, LastWriteTime,
+         and ChangeTime members as appropriate after an I/O operation is
+         performed on a file. A driver or application can request that the
+         file system not update one or more of these members for I/O operations
+         that are performed on the caller's file handle by setting the
+         appropriate members to -1.
+         https://docs.microsoft.com/windows-hardware/drivers/ddi/wdm/ns-wdm-_file_basic_information#remarks
+       */
+      info.LastWriteTime.QuadPart = -1;
+    }
+  else
+    {
+      info.LastWriteTime.QuadPart = (set_mtime + APR_DELTA_EPOCH_IN_USEC) * 10;
+    }
+
+  if (set_read_only)
+    info.FileAttributes |= FILE_ATTRIBUTE_READONLY;
+  else
+    info.FileAttributes = 0;
+
+  status = win32_set_file_information_by_handle(hFile, FileBasicInfo,
+                                                &info, sizeof(info));
+  if (status)
+    {
+      return svn_error_wrap_apr(status, _("Can't set attributes of '%s'"),
+                                svn_dirent_local_style(path, pool));
+    }
+
+  return SVN_NO_ERROR;
+}
+
 #endif /* WIN32 */
 
 svn_error_t *
@@ -2115,6 +2420,55 @@ svn_io_set_file_read_write_carefully(con
   return svn_io_set_file_read_only(path, ignore_enoent, pool);
 }
 
+#if defined(WIN32) || defined(__OS2__)
+/* Helper for svn_io_set_file_read_* */
+static svn_error_t *
+io_set_readonly_flag(const char *path_apr, /* file-system path */
+                     const char *path,     /* UTF-8 path */
+                     svn_boolean_t set_flag,
+                     svn_boolean_t is_file,
+                     svn_boolean_t ignore_enoent,
+                     apr_pool_t *pool)
+{
+  apr_status_t status;
+
+  status = apr_file_attrs_set(path_apr,
+                              (set_flag ? APR_FILE_ATTR_READONLY : 0),
+                              APR_FILE_ATTR_READONLY,
+                              pool);
+
+  if (status && status != APR_ENOTIMPL)
+    if (!(ignore_enoent && (APR_STATUS_IS_ENOENT(status)
+                            || SVN__APR_STATUS_IS_ENOTDIR(status))))
+      {
+        if (is_file)
+          {
+            if (set_flag)
+              return svn_error_wrap_apr(status,
+                                        _("Can't set file '%s' read-only"),
+                                        svn_dirent_local_style(path, pool));
+            else
+              return svn_error_wrap_apr(status,
+                                        _("Can't set file '%s' read-write"),
+                                        svn_dirent_local_style(path, pool));
+          }
+        else
+          {
+            if (set_flag)
+              return svn_error_wrap_apr(status,
+                                        _("Can't set directory '%s' read-only"),
+                                        svn_dirent_local_style(path, pool));
+            else
+              return svn_error_wrap_apr(status,
+                                        _("Can't set directory '%s' read-write"),
+                                        svn_dirent_local_style(path, pool));
+          }
+      }
+  return SVN_NO_ERROR;
+}
+#endif
+
+
 svn_error_t *
 svn_io_set_file_read_only(const char *path,
                           svn_boolean_t ignore_enoent,
@@ -2126,24 +2480,11 @@ svn_io_set_file_read_only(const char *pa
   return io_set_file_perms(path, TRUE, FALSE, FALSE, FALSE,
                            ignore_enoent, pool);
 #else
-  apr_status_t status;
   const char *path_apr;
 
   SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
-
-  status = apr_file_attrs_set(path_apr,
-                              APR_FILE_ATTR_READONLY,
-                              APR_FILE_ATTR_READONLY,
-                              pool);
-
-  if (status && status != APR_ENOTIMPL)
-    if (!(ignore_enoent && (APR_STATUS_IS_ENOENT(status)
-                            || SVN__APR_STATUS_IS_ENOTDIR(status))))
-      return svn_error_wrap_apr(status,
-                                _("Can't set file '%s' read-only"),
-                                svn_dirent_local_style(path, pool));
-
-  return SVN_NO_ERROR;
+  return io_set_readonly_flag(path_apr, path,
+                              TRUE, TRUE, ignore_enoent, pool);
 #endif
 }
 
@@ -2159,23 +2500,11 @@ svn_io_set_file_read_write(const char *p
   return io_set_file_perms(path, TRUE, TRUE, FALSE, FALSE,
                            ignore_enoent, pool);
 #else
-  apr_status_t status;
   const char *path_apr;
 
   SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
-
-  status = apr_file_attrs_set(path_apr,
-                              0,
-                              APR_FILE_ATTR_READONLY,
-                              pool);
-
-  if (status && status != APR_ENOTIMPL)
-    if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
-      return svn_error_wrap_apr(status,
-                                _("Can't set file '%s' read-write"),
-                                svn_dirent_local_style(path, pool));
-
-  return SVN_NO_ERROR;
+  return io_set_readonly_flag(path_apr, path,
+                              FALSE, TRUE, ignore_enoent, pool);
 #endif
 }
 
@@ -2460,7 +2789,6 @@ svn_io__file_lock_autocreate(const char
 svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file,
                                        apr_pool_t *pool)
 {
-  apr_os_file_t filehand;
   const char *fname;
   apr_status_t apr_err;
 
@@ -2470,49 +2798,21 @@ svn_error_t *svn_io_file_flush_to_disk(a
   if (apr_err)
     return svn_error_wrap_apr(apr_err, _("Can't get file name"));
 
-  /* ### In apr 1.4+ we could delegate most of this function to
-         apr_file_sync(). The only major difference is that this doesn't
-         contain the retry loop for EINTR on linux. */
-
-  /* First make sure that any user-space buffered data is flushed. */
-  SVN_ERR(svn_io_file_flush(file, pool));
-
-  apr_os_file_get(&filehand, file);
-
-  /* Call the operating system specific function to actually force the
-     data to disk. */
-  {
-#ifdef WIN32
-
-    if (! FlushFileBuffers(filehand))
-        return svn_error_wrap_apr(apr_get_os_error(),
-                                  _("Can't flush file '%s' to disk"),
-                                  try_utf8_from_internal_style(fname, pool));
-
-#else
-      int rv;
-
-      do {
-#ifdef F_FULLFSYNC
-        rv = fcntl(filehand, F_FULLFSYNC, 0);
-#else
-        rv = fsync(filehand);
-#endif
-      } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
+  do {
+    apr_err = apr_file_datasync(file);
+  } while(APR_STATUS_IS_EINTR(apr_err));
 
-      /* If the file is in a memory filesystem, fsync() may return
-         EINVAL.  Presumably the user knows the risks, and we can just
-         ignore the error. */
-      if (rv == -1 && APR_STATUS_IS_EINVAL(apr_get_os_error()))
-        return SVN_NO_ERROR;
+  /* If the file is in a memory filesystem, fsync() may return
+     EINVAL.  Presumably the user knows the risks, and we can just
+     ignore the error. */
+  if (APR_STATUS_IS_EINVAL(apr_err))
+    return SVN_NO_ERROR;
 
-      if (rv == -1)
-        return svn_error_wrap_apr(apr_get_os_error(),
-                                  _("Can't flush file '%s' to disk"),
-                                  try_utf8_from_internal_style(fname, pool));
+  if (apr_err)
+    return svn_error_wrap_apr(apr_err,
+                              _("Can't flush file '%s' to disk"),
+                              try_utf8_from_internal_style(fname, pool));
 
-#endif
-  }
   return SVN_NO_ERROR;
 }
 
@@ -2554,7 +2854,7 @@ stringbuf_from_aprfile(svn_stringbuf_t *
              correct, for instance, because the underlying handle could be
              pointing to a pipe.  We don't know that in advance, so attempt
              to read *one more* byte than necessary.  If we get an EOF, then
-             we're done and we have succesfully avoided reading the file chunk-
+             we're done and we have successfully avoided reading the file chunk-
              by-chunk.  If we don't, we fall through and do so to read the
              remaining part of the file. */
           svn_boolean_t eof;
@@ -2761,6 +3061,12 @@ svn_io_remove_dir2(const char *path, svn
       return svn_error_trace(err);
     }
 
+  /* On Unix, nothing can be removed from a non-writable directory. */
+#if !defined(WIN32) && !defined(__OS2__)
+  SVN_ERR(io_set_dir_perms(path, TRUE, TRUE, FALSE, FALSE,
+                           ignore_enoent, pool));
+#endif
+
   for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi))
     {
       const char *name = apr_hash_this_key(hi);
@@ -4242,7 +4548,45 @@ win32_file_rename(const WCHAR *from_path
     }
 
   if (!MoveFileExW(from_path_w, to_path_w, flags))
-      return apr_get_os_error();
+    {
+      apr_status_t err = apr_get_os_error();
+      /* If the target file is read only NTFS reports EACCESS and
+         FAT/FAT32 reports EEXIST */
+      if (APR_STATUS_IS_EACCES(err) || APR_STATUS_IS_EEXIST(err))
+        {
+          DWORD attrs = GetFileAttributesW(to_path_w);
+          if (attrs == INVALID_FILE_ATTRIBUTES)
+            {
+              apr_status_t stat_err = apr_get_os_error();
+              if (!(APR_STATUS_IS_ENOENT(stat_err) || SVN__APR_STATUS_IS_ENOTDIR(stat_err)))
+                /* We failed to stat the file, propagate the original error */
+                return err;
+            }
+          else if (attrs & FILE_ATTRIBUTE_READONLY)
+            {
+              /* Try to set the destination file writable because Windows will
+                 not allow us to rename when to_path is read-only, but will
+                 allow renaming when from_path is read only. */
+              attrs &= ~FILE_ATTRIBUTE_READONLY;
+              if (!SetFileAttributesW(to_path_w, attrs))
+                {
+                  err = apr_get_os_error();
+                  if (!(APR_STATUS_IS_ENOENT(err) || SVN__APR_STATUS_IS_ENOTDIR(err)))
+                    /* We failed to set file attributes, propagate this new error */
+                    return err;
+                }
+            }
+
+          /* NOTE: If the file is not read-only, we don't know if the file did
+             not have the read-only attribute in the first place or if this
+             attribute disappeared due to a race, so try to rename it anyway.
+           */
+          if (!MoveFileExW(from_path_w, to_path_w, flags))
+            return apr_get_os_error();
+        }
+      else
+        return err;
+    }
 
   return APR_SUCCESS;
 }
@@ -4266,18 +4610,6 @@ svn_io_file_rename2(const char *from_pat
   SVN_ERR(svn_io__utf8_to_unicode_longpath(&from_path_w, from_path_apr, pool));
   SVN_ERR(svn_io__utf8_to_unicode_longpath(&to_path_w, to_path_apr, pool));
   status = win32_file_rename(from_path_w, to_path_w, flush_to_disk);
-
-  /* If the target file is read only NTFS reports EACCESS and
-     FAT/FAT32 reports EEXIST */
-  if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status))
-    {
-      /* Set the destination file writable because Windows will not
-         allow us to rename when to_path is read-only, but will
-         allow renaming when from_path is read only. */
-      SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool));
-
-      status = win32_file_rename(from_path_w, to_path_w, flush_to_disk);
-    }
   WIN32_RETRY_LOOP(status, win32_file_rename(from_path_w, to_path_w,
                                              flush_to_disk));
 #elif defined(__OS2__)
@@ -4499,8 +4831,17 @@ svn_io_dir_remove_nonrecursive(const cha
   {
     svn_boolean_t retry = TRUE;
 
+    if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status))
+      {
+        /* Make the destination directory writable because Windows
+           forbids deleting read-only items. */
+        SVN_ERR(io_set_readonly_flag(dirname_apr, dirname,
+                                     FALSE, FALSE, TRUE, pool));
+        status = apr_dir_remove(dirname_apr, pool);
+      }
+
     if (status == APR_FROM_OS_ERROR(ERROR_DIR_NOT_EMPTY))
-    {
+      {
         apr_status_t empty_status = dir_is_empty(dirname_apr, pool);
 
         if (APR_STATUS_IS_ENOTEMPTY(empty_status))

Modified: subversion/branches/multi-wc-format/subversion/libsvn_subr/iter.c
URL: http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/subversion/libsvn_subr/iter.c?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/subversion/libsvn_subr/iter.c (original)
+++ subversion/branches/multi-wc-format/subversion/libsvn_subr/iter.c Fri Jan 14 14:01:45 2022
@@ -37,7 +37,6 @@ static svn_error_t internal_break_error
     __LINE__ /* line number */
   };
 
-#if APR_VERSION_AT_LEAST(1, 4, 0)
 struct hash_do_baton
 {
   void *baton;
@@ -59,7 +58,6 @@ int hash_do_callback(void *baton,
 
   return hdb->err == SVN_NO_ERROR;
 }
-#endif
 
 svn_error_t *
 svn_iter_apr_hash(svn_boolean_t *completed,
@@ -68,7 +66,6 @@ svn_iter_apr_hash(svn_boolean_t *complet
                   void *baton,
                   apr_pool_t *pool)
 {
-#if APR_VERSION_AT_LEAST(1, 4, 0)
   struct hash_do_baton hdb;
   svn_boolean_t error_received;
 
@@ -97,43 +94,6 @@ svn_iter_apr_hash(svn_boolean_t *complet
     }
 
   return hdb.err;
-#else
-  svn_error_t *err = SVN_NO_ERROR;
-  apr_pool_t *iterpool = svn_pool_create(pool);
-  apr_hash_index_t *hi;
-
-  for (hi = apr_hash_first(pool, hash);
-       ! err && hi; hi = apr_hash_next(hi))
-    {
-      const void *key;
-      void *val;
-      apr_ssize_t len;
-
-      svn_pool_clear(iterpool);
-
-      apr_hash_this(hi, &key, &len, &val);
-      err = (*func)(baton, key, len, val, iterpool);
-    }
-
-  if (completed)
-    *completed = ! err;
-
-  if (err && err->apr_err == SVN_ERR_ITER_BREAK)
-    {
-      if (err != &internal_break_error)
-        /* Errors - except those created by svn_iter_break() -
-           need to be cleared when not further propagated. */
-        svn_error_clear(err);
-
-      err = SVN_NO_ERROR;
-    }
-
-  /* Clear iterpool, because callers may clear the error but have no way
-     to clear the iterpool with potentially lots of allocated memory */
-  svn_pool_destroy(iterpool);
-
-  return err;
-#endif
 }
 
 svn_error_t *

Propchange: subversion/branches/multi-wc-format/subversion/libsvn_subr/lz4/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Fri Jan 14 14:01:45 2022
@@ -0,0 +1 @@
+.libs

Modified: subversion/branches/multi-wc-format/subversion/libsvn_subr/mergeinfo.c
URL: http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/subversion/libsvn_subr/mergeinfo.c?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/subversion/libsvn_subr/mergeinfo.c (original)
+++ subversion/branches/multi-wc-format/subversion/libsvn_subr/mergeinfo.c Fri Jan 14 14:01:45 2022
@@ -44,8 +44,9 @@
 /* Return TRUE iff the forward revision range FIRST wholly contains the
  * forward revision range SECOND and (if CONSIDER_INHERITANCE is TRUE) has
  * the same inheritability. */
-static svn_boolean_t
-range_contains(const svn_merge_range_t *first, const svn_merge_range_t *second,
+static svn_error_t *
+range_contains(svn_boolean_t *result,
+               const svn_merge_range_t *first, const svn_merge_range_t *second,
                svn_boolean_t consider_inheritance);
 
 
@@ -457,21 +458,48 @@ combine_with_lastrange(const svn_merge_r
 }
 
 /* Convert a single svn_merge_range_t *RANGE back into a string.  */
-static char *
-range_to_string(const svn_merge_range_t *range,
+static svn_error_t *
+range_to_string(char **s,
+                const svn_merge_range_t *range,
                 apr_pool_t *pool)
 {
   const char *mark
     = range->inheritable ? "" : SVN_MERGEINFO_NONINHERITABLE_STR;
 
   if (range->start == range->end - 1)
-    return apr_psprintf(pool, "%ld%s", range->end, mark);
+    *s = apr_psprintf(pool, "%ld%s", range->end, mark);
   else if (range->start - 1 == range->end)
-    return apr_psprintf(pool, "-%ld%s", range->start, mark);
+    *s = apr_psprintf(pool, "-%ld%s", range->start, mark);
   else if (range->start < range->end)
-    return apr_psprintf(pool, "%ld-%ld%s", range->start + 1, range->end, mark);
+    *s = apr_psprintf(pool, "%ld-%ld%s", range->start + 1, range->end, mark);
+  else if (range->start > range->end)
+    *s = apr_psprintf(pool, "%ld-%ld%s", range->start, range->end + 1, mark);
   else
-    return apr_psprintf(pool, "%ld-%ld%s", range->start, range->end + 1, mark);
+    {
+      return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL,
+                               _("bad range {start=%ld,end=%ld,inheritable=%d}"),
+                               range->start, range->end, range->inheritable);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Convert a single svn_merge_range_t *RANGE back into a string.  */
+static char *
+range_to_string_debug(const svn_merge_range_t *range,
+                      apr_pool_t *pool)
+{
+  svn_error_t *err;
+  char *s;
+
+  err = range_to_string(&s, range, pool);
+  if (err)
+    {
+      svn_error_clear(err);
+      s = apr_psprintf(pool, _("bad range {start=%ld,end=%ld,inheritable=%d}"),
+                       range->start, range->end, range->inheritable);
+    }
+  return s;
 }
 
 /* Helper for svn_mergeinfo_parse()
@@ -667,10 +695,10 @@ svn_rangelist__canonicalize(svn_rangelis
                                          "revision ranges '%s' and '%s' "
                                          "with different inheritance "
                                          "types"),
-                                       range_to_string(lastrange,
-                                                       scratch_pool),
-                                       range_to_string(range,
-                                                       scratch_pool));
+                                       range_to_string_debug(lastrange,
+                                                             scratch_pool),
+                                       range_to_string_debug(range,
+                                                             scratch_pool));
             }
 
           /* Combine overlapping or adjacent ranges with the
@@ -678,7 +706,7 @@ svn_rangelist__canonicalize(svn_rangelis
           if (lastrange->inheritable == range->inheritable)
             {
               lastrange->end = MAX(range->end, lastrange->end);
-              svn_sort__array_delete(rangelist, i, 1);
+              SVN_ERR(svn_sort__array_delete2(rangelist, i, 1));
               i--;
             }
         }
@@ -788,490 +816,349 @@ svn_mergeinfo_parse(svn_mergeinfo_t *mer
   return err;
 }
 
-/* Cleanup after svn_rangelist_merge2 when it modifies the ending range of
-   a single rangelist element in-place.
-
-   If *RANGE_INDEX is not a valid element in RANGELIST do nothing.  Otherwise
-   ensure that RANGELIST[*RANGE_INDEX]->END does not adjoin or overlap any
-   subsequent ranges in RANGELIST.
-
-   If overlap is found, then remove, modify, and/or add elements to RANGELIST
-   as per the invariants for rangelists documented in svn_mergeinfo.h.  If
-   RANGELIST[*RANGE_INDEX]->END adjoins a subsequent element then combine the
-   elements if their inheritability permits -- The inheritance of intersecting
-   and adjoining ranges is handled as per svn_mergeinfo_merge2.  Upon return
-   set *RANGE_INDEX to the index of the youngest element modified, added, or
-   adjoined to RANGELIST[*RANGE_INDEX].
+static const char *
+rangelist_to_string_debug(const svn_rangelist_t *rl,
+                          apr_pool_t *pool)
+{
+  svn_string_t *rls;
+  svn_error_t *err;
 
-   Note: Adjoining rangelist elements are those where the end rev of the older
-   element is equal to the start rev of the younger element.
+  err = svn_rangelist_to_string(&rls, rl, pool);
+  if (err)
+    {
+      char *s = apr_psprintf(pool, _("<bad rangelist [%d ranges]: %s>"),
+                             rl->nelts, err->message);
+      svn_error_clear(err);
+      return s;
+    }
+  return rls->data;
+}
 
-   Any new elements inserted into RANGELIST are allocated in  RESULT_POOL.*/
-static void
-adjust_remaining_ranges(svn_rangelist_t *rangelist,
-                        int *range_index,
-                        apr_pool_t *result_pool)
+static svn_boolean_t
+rangelist_is_sorted(const svn_rangelist_t *rangelist)
 {
   int i;
-  int starting_index;
-  int elements_to_delete = 0;
-  svn_merge_range_t *modified_range;
-
-  if (*range_index >= rangelist->nelts)
-    return;
 
-  starting_index = *range_index + 1;
-  modified_range = APR_ARRAY_IDX(rangelist, *range_index, svn_merge_range_t *);
-
-  for (i = *range_index + 1; i < rangelist->nelts; i++)
+  for (i = 1; i < rangelist->nelts; i++)
     {
-      svn_merge_range_t *next_range = APR_ARRAY_IDX(rangelist, i,
-                                                    svn_merge_range_t *);
+      const svn_merge_range_t *lastrange
+        = APR_ARRAY_IDX(rangelist, i-1, svn_merge_range_t *);
+      const svn_merge_range_t *thisrange
+        = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
 
-      /* If MODIFIED_RANGE doesn't adjoin or overlap the next range in
-         RANGELIST then we are finished. */
-      if (modified_range->end < next_range->start)
-        break;
+      if (svn_sort_compare_ranges(&lastrange, &thisrange) > 0)
+        return FALSE;
+    }
+  return TRUE;
+}
 
-      /* Does MODIFIED_RANGE adjoin NEXT_RANGE? */
-      if (modified_range->end == next_range->start)
-        {
-          if (modified_range->inheritable == next_range->inheritable)
-            {
-              /* Combine adjoining ranges with the same inheritability. */
-              modified_range->end = next_range->end;
-              elements_to_delete++;
-            }
-          else
-            {
-              /* Cannot join because inheritance differs. */
-              (*range_index)++;
-            }
-          break;
-        }
+/* Mergeinfo inheritance or absence in a rangelist interval */
+enum rangelist_interval_kind_t { MI_NONE, MI_NON_INHERITABLE, MI_INHERITABLE };
 
-      /* Alright, we know MODIFIED_RANGE overlaps NEXT_RANGE, but how? */
-      if (modified_range->end > next_range->end)
-        {
-          /* NEXT_RANGE is a proper subset of MODIFIED_RANGE and the two
-             don't share the same end range. */
-          if (modified_range->inheritable
-              || (modified_range->inheritable == next_range->inheritable))
-            {
-              /* MODIFIED_RANGE absorbs NEXT_RANGE. */
-              elements_to_delete++;
-            }
-          else
-            {
-              /* NEXT_RANGE is a proper subset MODIFIED_RANGE but
-                 MODIFIED_RANGE is non-inheritable and NEXT_RANGE is
-                 inheritable.  This means MODIFIED_RANGE is truncated,
-                 NEXT_RANGE remains, and the portion of MODIFIED_RANGE
-                 younger than NEXT_RANGE is added as a separate range:
-                  ______________________________________________
-                 |                                              |
-                 M                 MODIFIED_RANGE               N
-                 |                 (!inheritable)               |
-                 |______________________________________________|
-                                  |              |
-                                  O  NEXT_RANGE  P
-                                  | (inheritable)|
-                                  |______________|
-                                         |
-                                         V
-                  _______________________________________________
-                 |                |              |               |
-                 M MODIFIED_RANGE O  NEXT_RANGE  P   NEW_RANGE   N
-                 | (!inheritable) | (inheritable)| (!inheritable)|
-                 |________________|______________|_______________|
-              */
-              svn_merge_range_t *new_modified_range =
-                apr_palloc(result_pool, sizeof(*new_modified_range));
-              new_modified_range->start = next_range->end;
-              new_modified_range->end = modified_range->end;
-              new_modified_range->inheritable = FALSE;
-              modified_range->end = next_range->start;
-              (*range_index) += 2 + elements_to_delete;
-              svn_sort__array_insert(rangelist, &new_modified_range,
-                                     *range_index);
-              /* Recurse with the new range. */
-              adjust_remaining_ranges(rangelist, range_index, result_pool);
-              break;
-            }
-        }
-      else if (modified_range->end == next_range->end)
-        {
-          /* NEXT_RANGE is a proper subset MODIFIED_RANGE and share
-             the same end range. */
-          if (modified_range->inheritable
-              || (modified_range->inheritable == next_range->inheritable))
-            {
-              /* MODIFIED_RANGE absorbs NEXT_RANGE. */
-              elements_to_delete++;
-            }
-          else
-            {
-              /* The intersection between MODIFIED_RANGE and NEXT_RANGE is
-                 absorbed by the latter. */
-              modified_range->end = next_range->start;
-              (*range_index)++;
-            }
-          break;
-        }
-      else
-        {
-          /* NEXT_RANGE and MODIFIED_RANGE intersect but NEXT_RANGE is not
-             a proper subset of MODIFIED_RANGE, nor do the two share the
-             same end revision, i.e. they overlap. */
-          if (modified_range->inheritable == next_range->inheritable)
-            {
-              /* Combine overlapping ranges with the same inheritability. */
-              modified_range->end = next_range->end;
-              elements_to_delete++;
-            }
-          else if (modified_range->inheritable)
-            {
-              /* MODIFIED_RANGE absorbs the portion of NEXT_RANGE it overlaps
-                 and NEXT_RANGE is truncated. */
-              next_range->start = modified_range->end;
-              (*range_index)++;
-            }
-          else
-            {
-              /* NEXT_RANGE absorbs the portion of MODIFIED_RANGE it overlaps
-                 and MODIFIED_RANGE is truncated. */
-              modified_range->end = next_range->start;
-              (*range_index)++;
-            }
-          break;
-        }
+/* A rangelist interval: like svn_merge_range_t but an interval can represent
+ * a gap in the rangelist (kind = MI_NONE). */
+typedef struct rangelist_interval_t
+{
+  svn_revnum_t start, end;
+  enum rangelist_interval_kind_t kind;
+} rangelist_interval_t;
+
+/* Iterator for intervals in a rangelist. */
+typedef struct rangelist_interval_iterator_t {
+  /* iteration state: */
+  const svn_rangelist_t *rl;  /* input */
+  int i;  /* current interval is this range in RL or the gap before it */
+  svn_boolean_t in_range;  /* current interval is range RL[I], not a gap? */
+
+  /* current interval: */
+  rangelist_interval_t interval;
+} rangelist_interval_iterator_t;
+
+/* Update IT->interval to match the current iteration state of IT.
+ * Return the iterator, or NULL if the iteration has reached its end.
+ */
+static rangelist_interval_iterator_t *
+rlii_update(rangelist_interval_iterator_t *it)
+{
+  const svn_merge_range_t *range
+    = (it->i < it->rl->nelts
+       ? APR_ARRAY_IDX(it->rl, it->i, void *) : NULL);
+
+  if (!range)
+    return NULL;
+
+  if (!it->in_range)
+    {
+      it->interval.start
+        = (it->i > 0
+           ? APR_ARRAY_IDX(it->rl, it->i - 1, svn_merge_range_t *)->end
+           : 0);
+      it->interval.end = range->start;
+      it->interval.kind = MI_NONE;
+    }
+  else
+    {
+      it->interval.start = range->start;
+      it->interval.end = range->end;
+      it->interval.kind
+        = (range->inheritable ? MI_INHERITABLE : MI_NON_INHERITABLE);
     }
+  return it;
+}
 
-  if (elements_to_delete)
-    svn_sort__array_delete(rangelist, starting_index, elements_to_delete);
+/* Move to the next interval, which might be a zero-length interval.
+ * Return IT, or return NULL at the end of iteration. */
+static rangelist_interval_iterator_t *
+rlii_next_any_interval(rangelist_interval_iterator_t *it)
+{
+  /* Should be called before iteration is finished. */
+  if (it->i >= it->rl->nelts)
+    return NULL;
+
+  /* If we are in a range, move to the next pre-range gap;
+   * else, move from this pre-range gap into this range. */
+  if (it->in_range)
+    it->i++;
+  it->in_range = !it->in_range;
+  return it;
 }
 
-#if 0 /* Temporary debug helper code */
-static svn_error_t *
-dual_dump(const char *prefix,
-  const svn_rangelist_t *rangelist,
-  const svn_rangelist_t *changes,
-  apr_pool_t *scratch_pool)
+/* Return an iterator pointing at the first non-zero-length interval in RL,
+ * or NULL if there are none. */
+static rangelist_interval_iterator_t *
+rlii_first(const svn_rangelist_t *rl,
+           apr_pool_t *pool)
 {
-  svn_string_t *rls, *chg;
+  rangelist_interval_iterator_t *it = apr_palloc(pool, sizeof(*it));
 
-  SVN_ERR(svn_rangelist_to_string(&rls, rangelist, scratch_pool));
-  SVN_ERR(svn_rangelist_to_string(&chg, changes, scratch_pool));
+  it->rl = rl;
+  it->i = 0;
+  it->in_range = FALSE;
 
-  SVN_DBG(("%s: %s / %s", prefix, rls->data, chg->data));
-  return SVN_NO_ERROR;
+  /* Update, and skip empty intervals */
+  while ((it = rlii_update(it)) && it->interval.start == it->interval.end)
+    {
+      it = rlii_next_any_interval(it);
+    }
+  return it;
 }
-#endif
 
-svn_error_t *
-svn_rangelist_merge2(svn_rangelist_t *rangelist,
-                     const svn_rangelist_t *chg,
-                     apr_pool_t *result_pool,
-                     apr_pool_t *scratch_pool)
+/* Move to the next non-empty interval.
+ * Intervals will be generated in this sequence:
+ *  (0,               MI_NONE,  RL[0]->start),  // i=0, !in_range
+ *  (RL[0]->start,    MI_*      RL[0]->end),    // i=0, in_range
+ *  (RL[0]->end,      MI_NONE,  RL[1]->start),
+ *  (RL[1]->start,    MI_*      RL[1]->end),
+ *  ...
+ *  (RL[n-2]->end,    MI_NONE,  RL[n-1]->start),
+ *  (RL[n-1]->start,  MI_*      RL[n-1]->end),
+ * but excluding empty intervals.
+ * Return IT, or return NULL at the end of iteration. */
+static rangelist_interval_iterator_t *
+rlii_next(rangelist_interval_iterator_t *it)
 {
-  svn_rangelist_t *changes;
-  int i = 0;
-  int j;
+  it = rlii_next_any_interval(it);
 
-  SVN_ERR(svn_rangelist__canonicalize(rangelist, scratch_pool));
+  /* Update, and skip empty intervals */
+  while ((it = rlii_update(it)) && it->interval.start == it->interval.end)
+    {
+      it = rlii_next_any_interval(it);
+    }
+  return it;
+}
 
-  /* We may modify CHANGES, so make a copy in SCRATCH_POOL. */
-  changes = svn_rangelist_dup(chg, scratch_pool);
-  SVN_ERR(svn_rangelist__canonicalize(changes, scratch_pool));
+/* Rangelist builder. Accumulates consecutive intervals, combining them
+ * when possible. */
+typedef struct rangelist_builder_t {
+  svn_rangelist_t *rl;  /* rangelist to build */
+  rangelist_interval_t accu_interval;  /* current interval accumulator */
+  apr_pool_t *pool;  /* from which to allocate ranges */
+} rangelist_builder_t;
 
-  for (j = 0; j < changes->nelts; j++)
-    {
-      svn_merge_range_t *range;
-      svn_merge_range_t *change =
-        APR_ARRAY_IDX(changes, j, svn_merge_range_t *);
-      int res;
+/* Return an initialized rangelist builder. */
+static rangelist_builder_t *
+rl_builder_new(svn_rangelist_t *rl,
+               apr_pool_t *pool)
+{
+  rangelist_builder_t *b = apr_pcalloc(pool, sizeof(*b));
 
-      range = (i < rangelist->nelts)
-              ? APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *)
-              : NULL;
-
-      if (!range || change->end < range->start)
-        {
-          /* No overlap, nor adjoin, copy change to result range */
-          svn_merge_range_t *chg_copy = svn_merge_range_dup(change,
-                                                            result_pool);
-          svn_sort__array_insert(rangelist, &chg_copy, i++);
-          continue;
-        }
-      else if ((change->start > range->end)
-               || (change->start == range->end
-                   && change->inheritable != range->inheritable))
-        {
-          /* No overlap, nor adjoin. Check next range item against change */
-          i++;
-          j--;
-          continue;
-        }
+  b->rl = rl;
+  /* b->accu_interval = {0, 0, RL_NONE} */
+  b->pool = pool;
+  return b;
+}
 
-      if (change->start < range->start
-          && range->inheritable != change->inheritable
-          && ! (change->inheritable && range_contains(change, range, FALSE))
-          && ! (range->inheritable && range_contains(range, change, FALSE)))
-        {
-          /* Can't fold change into existing range.
-             Insert new range before range */
+/* Flush the last accumulated interval in the rangelist builder B. */
+static void
+rl_builder_flush(rangelist_builder_t *b)
+{
+  if (b->accu_interval.kind > MI_NONE)
+    {
+      svn_merge_range_t *mrange = apr_pcalloc(b->pool, sizeof(*mrange));
+      mrange->start = b->accu_interval.start;
+      mrange->end = b->accu_interval.end;
+      mrange->inheritable = (b->accu_interval.kind == MI_INHERITABLE);
+      APR_ARRAY_PUSH(b->rl, svn_merge_range_t *) = mrange;
+    }
+}
 
-          svn_merge_range_t *chg_copy = svn_merge_range_dup(change,
-                                                            result_pool);
+/* Add a new INTERVAL to the rangelist builder B. */
+static void
+rl_builder_add_interval(rangelist_builder_t *b,
+                        const rangelist_interval_t *interval)
+{
+  SVN_ERR_ASSERT_NO_RETURN(interval->start < interval->end);
+  SVN_ERR_ASSERT_NO_RETURN(interval->start == b->accu_interval.end);
 
-          chg_copy->start = MIN(change->start, range->start);
-          if (! change->inheritable)
-            chg_copy->end = range->start;
-          else
-            range->start = change->end;
+  /* Extend the accumulating interval, or end it and start another? */
+  if (interval->kind == b->accu_interval.kind)
+    {
+      b->accu_interval.end = interval->end;
+    }
+  else
+    {
+      /* Push the accumulated interval onto the building rangelist. */
+      rl_builder_flush(b);
+      /* Start accumulating a new interval */
+      b->accu_interval = *interval;
+    }
+}
 
-          svn_sort__array_insert(rangelist, &chg_copy, i++);
+/* Set RL_OUT to the union (merge) of RL1 and RL2.
+ * On entry, RL_OUT must be an empty rangelist.
+ *
+ * Each range added to RL_OUT will be either shallow-copied from RL1 or
+ * allocated from RESULT_POOL.
+ */
+static svn_error_t *
+rangelist_merge(svn_rangelist_t *rl_out,
+                const svn_rangelist_t *rl1,
+                const svn_rangelist_t *rl2,
+                apr_pool_t *result_pool,
+                apr_pool_t *scratch_pool)
+{
+  rangelist_interval_iterator_t *it[2];
+  rangelist_builder_t *rl_builder = rl_builder_new(rl_out, result_pool);
+  svn_revnum_t r_last = 0;
+
+  /*SVN_ERR_ASSERT(svn_rangelist__is_canonical(rl1));*/
+  /*SVN_ERR_ASSERT(svn_rangelist__is_canonical(rl2));*/
+  SVN_ERR_ASSERT(rangelist_is_sorted(rl1));
+  SVN_ERR_ASSERT(rangelist_is_sorted(rl2));
+  SVN_ERR_ASSERT(rl_out->nelts == 0);
+
+  /* Initialize the input iterators and the output generator */
+  it[0] = rlii_first(rl1, scratch_pool);
+  it[1] = rlii_first(rl2, scratch_pool);
+
+  /* Keep choosing the next input revision (whether a start or end of a range)
+   * at which to consider making an output transition. */
+  while (it[0] || it[1])
+    {
+      svn_revnum_t r_next = !it[1] ? it[0]->interval.end
+                            : !it[0] ? it[1]->interval.end
+                            : MIN(it[0]->interval.end, it[1]->interval.end);
+      rangelist_interval_t interval;
+
+      interval.start = r_last;
+      interval.end = r_next;
+      interval.kind = !it[1] ? it[0]->interval.kind
+                       : !it[0] ? it[1]->interval.kind
+                       : MAX(it[0]->interval.kind, it[1]->interval.kind);
+
+      /* Accumulate */
+      SVN_ERR_ASSERT(interval.start < interval.end);
+      rl_builder_add_interval(rl_builder, &interval);
+
+      /* if we have used up either or both input intervals, increment them */
+      if (it[0] && it[0]->interval.end <= r_next)
+        it[0] = rlii_next(it[0]);
+      if (it[1] && it[1]->interval.end <= r_next)
+        it[1] = rlii_next(it[1]);
 
-          change->start = chg_copy->end;
-          if (change->start >= change->end)
-            continue; /* No overlap with range left */
-        }
-      else
-        {
-          range->start = MIN(range->start, change->start);
-        }
+      r_last = interval.end;
+    }
+  rl_builder_flush(rl_builder);
+  return SVN_NO_ERROR;
+}
 
-      SVN_ERR_ASSERT(change->start >= range->start);
+svn_error_t *
+svn_rangelist_merge2(svn_rangelist_t *rangelist,
+                     const svn_rangelist_t *chg,
+                     apr_pool_t *result_pool,
+                     apr_pool_t *scratch_pool)
+{
+  svn_error_t *err;
+  svn_rangelist_t *rangelist_orig;
 
-      res = svn_sort_compare_ranges(&range, &change);
+#ifdef SVN_DEBUG
+  SVN_ERR_ASSERT(rangelist_is_sorted(rangelist));
+  SVN_ERR_ASSERT(rangelist_is_sorted(chg));
+#endif
 
-      if (res == 0)
-        {
-          /* Only when merging two non-inheritable ranges is the result also
-             non-inheritable.  In all other cases ensure an inheritable
-             result. */
-          if (range->inheritable || change->inheritable)
-            range->inheritable = TRUE;
-          i++;
-          continue;
-        }
-      else if (res < 0) /* CHANGE is younger than RANGE */
-        {
-          if (range->end == change->start)
-            {
-              /* RANGE and CHANGE adjoin */
-              if (range->inheritable == change->inheritable)
-                {
-                  /* RANGE and CHANGE have the same inheritability so
-                     RANGE expands to absord CHANGE. */
-                  range->end = change->end;
-                  adjust_remaining_ranges(rangelist, &i, result_pool);
-                  continue;
-                }
-              else
-                {
-                  /* RANGE and CHANGE adjoin, but have different
-                     inheritability.  Since RANGE is older, just
-                     move on to the next RANGE. */
-                  SVN_ERR_MALFUNCTION();
-                }
-            }
-          else
-            {
-              /* RANGE and CHANGE overlap, but how? */
-              if ((range->inheritable == change->inheritable)
-                  || range->inheritable)
-                {
-                  /* If CHANGE is a proper subset of RANGE, it absorbs RANGE
-                      with no adjustment otherwise only the intersection is
-                      absorbed and CHANGE is truncated. */
-                  if (range->end >= change->end)
-                    continue;
-                  else
-                    {
-                      change->start = range->end;
-                      j--;
-                      continue;
-                    }
-                }
-              else
-                {
-                  /* RANGE is non-inheritable and CHANGE is inheritable. */
-                  if (range->start < change->start)
-                    {
-                      /* CHANGE absorbs intersection with RANGE and RANGE
-                         is truncated. */
-                      svn_merge_range_t *range_copy =
-                        svn_merge_range_dup(range, result_pool);
-                      range_copy->end = change->start;
-                      range->start = change->start;
-                      svn_sort__array_insert(rangelist, &range_copy, i++);
-                      j--;
-                      continue;
-                    }
-                  else
-                    {
-                      /* CHANGE and RANGE share the same start rev, but
-                         RANGE is considered older because its end rev
-                         is older. */
-                      range->inheritable = TRUE;
-                      change->start = range->end;
-                      j--;
-                      continue;
-                    }
-                }
-            }
-        }
-      else /* res > 0, CHANGE is older than RANGE */
-        {
-          if (change->end == range->start)
-            {
-              /* RANGE and CHANGE adjoin */
-              if (range->inheritable == change->inheritable)
-                {
-                  /* RANGE and CHANGE have the same inheritability so we
-                     can simply combine the two in place. */
-                  range->start = change->start;
-                  continue;
-                }
-              else
-                {
-                  /* RANGE and CHANGE have different inheritability so insert
-                     a copy of CHANGE into RANGELIST. */
-                  SVN_ERR_MALFUNCTION(); /* Already handled */
-                }
-            }
-          else
-            {
-              /* RANGE and CHANGE overlap. */
-              if (range->inheritable == change->inheritable)
-                {
-                  /* RANGE and CHANGE have the same inheritability so we
-                     can simply combine the two in place... */
-                  range->start = change->start;
-                  if (range->end < change->end)
-                    {
-                      /* ...but if RANGE is expanded ensure that we don't
-                         violate any rangelist invariants. */
-                      range->end = change->end;
-                      adjust_remaining_ranges(rangelist, &i, result_pool);
-                    }
-                  continue;
-                }
-              else if (range->inheritable)
-                {
-                  if (change->start < range->start)
-                    {
-                      /* RANGE is inheritable so absorbs any part of CHANGE
-                         it overlaps.  CHANGE is truncated and the remainder
-                         inserted into RANGELIST. */
-                      SVN_ERR_MALFUNCTION(); /* Already handled */
-                    }
-                  else
-                    {
-                      /* CHANGE and RANGE share the same start rev, but
-                         CHANGE is considered older because CHANGE->END is
-                         older than RANGE->END. */
-                      continue;
-                    }
-                }
-              else
-                {
-                  /* RANGE is non-inheritable and CHANGE is inheritable. */
-                  if (change->start < range->start)
-                    {
-                      if (change->end == range->end)
-                        {
-                          /* RANGE is a proper subset of CHANGE and share the
-                             same end revision, so set RANGE equal to CHANGE. */
-                          range->start = change->start;
-                          range->inheritable = TRUE;
-                          continue;
-                        }
-                      else if (change->end > range->end)
-                        {
-                          /* RANGE is a proper subset of CHANGE and CHANGE has
-                             a younger end revision, so set RANGE equal to its
-                             intersection with CHANGE and truncate CHANGE. */
-                          range->start = change->start;
-                          range->inheritable = TRUE;
-                          change->start = range->end;
-                          j--;
-                          continue;
-                        }
-                      else
-                        {
-                          /* CHANGE and RANGE overlap. Set RANGE equal to its
-                             intersection with CHANGE and take the remainder
-                             of RANGE and insert it into RANGELIST. */
-                          svn_merge_range_t *range_copy =
-                            svn_merge_range_dup(range, result_pool);
-                          range_copy->start = change->end;
-                          range->start = change->start;
-                          range->end = change->end;
-                          range->inheritable = TRUE;
-                          svn_sort__array_insert(rangelist, &range_copy, ++i);
-                          continue;
-                        }
-                    }
-                  else
-                    {
-                      /* CHANGE and RANGE share the same start rev, but
-                         CHANGE is considered older because its end rev
-                         is older.
-
-                         Insert the intersection of RANGE and CHANGE into
-                         RANGELIST and then set RANGE to the non-intersecting
-                         portion of RANGE. */
-                      svn_merge_range_t *range_copy =
-                        svn_merge_range_dup(range, result_pool);
-                      range_copy->end = change->end;
-                      range_copy->inheritable = TRUE;
-                      range->start = change->end;
-                      svn_sort__array_insert(rangelist, &range_copy, i++);
-                      continue;
-                    }
-                }
-            }
-        }
-      SVN_ERR_MALFUNCTION(); /* Unreachable */
-    }
+  /* Move the original rangelist aside. A shallow copy suffices,
+   * as rangelist_merge() won't modify its inputs. */
+  rangelist_orig = apr_array_copy(scratch_pool, rangelist);
+  apr_array_clear(rangelist);
+  err = svn_error_trace(rangelist_merge(rangelist, rangelist_orig, chg,
+                                        result_pool, scratch_pool));
 
 #ifdef SVN_DEBUG
-  SVN_ERR_ASSERT(svn_rangelist__is_canonical(rangelist));
+  if (err)
+    {
+      err = svn_error_createf(SVN_ERR_ASSERTION_FAIL, err,
+              "svn_rangelist_merge2( %s / %s ): internal error",
+              rangelist_to_string_debug(rangelist_orig, scratch_pool),
+              rangelist_to_string_debug(chg, scratch_pool));
+    }
+  else if (! svn_rangelist__is_canonical(rangelist)
+           && svn_rangelist__is_canonical(rangelist_orig)
+           && svn_rangelist__is_canonical(chg))
+    {
+      err = svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL,
+              "svn_rangelist_merge2( %s / %s ): canonical inputs, "
+              "non-canonical result ( %s )",
+              rangelist_to_string_debug(rangelist_orig, scratch_pool),
+              rangelist_to_string_debug(chg, scratch_pool),
+              rangelist_to_string_debug(rangelist, scratch_pool));
+    }
 #endif
 
-  return SVN_NO_ERROR;
+  return err;
 }
 
-/* Return TRUE iff the forward revision ranges FIRST and SECOND overlap and
- * (if CONSIDER_INHERITANCE is TRUE) have the same inheritability. */
-static svn_boolean_t
-range_intersect(const svn_merge_range_t *first, const svn_merge_range_t *second,
+/* Set *RESULT to TRUE iff the forward revision ranges FIRST and SECOND overlap
+ * and (if CONSIDER_INHERITANCE is TRUE) have the same inheritability. */
+static svn_error_t *
+range_intersect(svn_boolean_t *result,
+                const svn_merge_range_t *first, const svn_merge_range_t *second,
                 svn_boolean_t consider_inheritance)
 {
-  SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(first));
-  SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(second));
+  SVN_ERR_ASSERT(IS_VALID_FORWARD_RANGE(first));
+  SVN_ERR_ASSERT(IS_VALID_FORWARD_RANGE(second));
 
-  return (first->start + 1 <= second->end)
-    && (second->start + 1 <= first->end)
-    && (!consider_inheritance
-        || (!(first->inheritable) == !(second->inheritable)));
+  *result = (first->start + 1 <= second->end)
+            && (second->start + 1 <= first->end)
+            && (!consider_inheritance
+                || (!(first->inheritable) == !(second->inheritable)));
+  return SVN_NO_ERROR;
 }
 
-/* Return TRUE iff the forward revision range FIRST wholly contains the
+/* Set *RESULT to TRUE iff the forward revision range FIRST wholly contains the
  * forward revision range SECOND and (if CONSIDER_INHERITANCE is TRUE) has
  * the same inheritability. */
-static svn_boolean_t
-range_contains(const svn_merge_range_t *first, const svn_merge_range_t *second,
+static svn_error_t *
+range_contains(svn_boolean_t *result,
+               const svn_merge_range_t *first, const svn_merge_range_t *second,
                svn_boolean_t consider_inheritance)
 {
-  SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(first));
-  SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(second));
+  SVN_ERR_ASSERT(IS_VALID_FORWARD_RANGE(first));
+  SVN_ERR_ASSERT(IS_VALID_FORWARD_RANGE(second));
 
-  return (first->start <= second->start) && (second->end <= first->end)
-    && (!consider_inheritance
-        || (!(first->inheritable) == !(second->inheritable)));
+  *result = (first->start <= second->start) && (second->end <= first->end)
+            && (!consider_inheritance
+                || (!(first->inheritable) == !(second->inheritable)));
+  return SVN_NO_ERROR;
 }
 
 /* Swap start and end fields of RANGE. */
@@ -1390,6 +1277,7 @@ rangelist_intersect_or_remove(svn_rangel
   while (i1 < rangelist1->nelts && i2 < rangelist2->nelts)
     {
       svn_merge_range_t *elt1, *elt2;
+      svn_boolean_t elt1_contains_elt2, elt1_intersects_elt2;
 
       elt1 = APR_ARRAY_IDX(rangelist1, i1, svn_merge_range_t *);
 
@@ -1405,6 +1293,10 @@ rangelist_intersect_or_remove(svn_rangel
 
       elt2 = &working_elt2;
 
+      SVN_ERR(range_contains(&elt1_contains_elt2,
+                             elt1, elt2, consider_inheritance));
+      SVN_ERR(range_intersect(&elt1_intersects_elt2,
+                              elt1, elt2, consider_inheritance));
       /* If the rangelist2 range is contained completely in the
          rangelist1, we increment the rangelist2.
          If the ranges intersect, and match exactly, we increment both
@@ -1413,7 +1305,7 @@ rangelist_intersect_or_remove(svn_rangel
          the removal of rangelist1 from rangelist2, and possibly change
          the rangelist2 to the remaining portion of the right part of
          the removal, to test against. */
-      if (range_contains(elt1, elt2, consider_inheritance))
+      if (elt1_contains_elt2)
         {
           if (!do_remove)
             {
@@ -1434,7 +1326,7 @@ rangelist_intersect_or_remove(svn_rangel
           if (elt2->start == elt1->start && elt2->end == elt1->end)
             i1++;
         }
-      else if (range_intersect(elt1, elt2, consider_inheritance))
+      else if (elt1_intersects_elt2)
         {
           if (elt2->start < elt1->start)
             {
@@ -1977,18 +1869,21 @@ svn_rangelist_to_string(svn_string_t **o
     {
       int i;
       svn_merge_range_t *range;
+      char *s;
 
       /* Handle the elements that need commas at the end.  */
       for (i = 0; i < rangelist->nelts - 1; i++)
         {
           range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
-          svn_stringbuf_appendcstr(buf, range_to_string(range, pool));
+          SVN_ERR(range_to_string(&s, range, pool));
+          svn_stringbuf_appendcstr(buf, s);
           svn_stringbuf_appendcstr(buf, ",");
         }
 
       /* Now handle the last element, which needs no comma.  */
       range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
-      svn_stringbuf_appendcstr(buf, range_to_string(range, pool));
+      SVN_ERR(range_to_string(&s, range, pool));
+      svn_stringbuf_appendcstr(buf, s);
     }
 
   *output = svn_stringbuf__morph_into_string(buf);

Modified: subversion/branches/multi-wc-format/subversion/libsvn_subr/object_pool.c
URL: http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/subversion/libsvn_subr/object_pool.c?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/subversion/libsvn_subr/object_pool.c (original)
+++ subversion/branches/multi-wc-format/subversion/libsvn_subr/object_pool.c Fri Jan 14 14:01:45 2022
@@ -174,7 +174,7 @@ add_object_ref(object_ref_t *object_ref,
 
   /* Make sure the reference gets released automatically.
      Since POOL might be a parent pool of OBJECT_REF->OBJECT_POOL,
-     to the reference counting update before destroing any of the
+     to the reference counting update before destroying any of the
      pool hierarchy. */
   apr_pool_pre_cleanup_register(pool, object_ref, object_ref_cleanup);
 }
@@ -321,7 +321,7 @@ svn_object_pool__insert(void **object,
 {
   *object = NULL;
   SVN_MUTEX__WITH_LOCK(object_pool->mutex,
-                       insert(object, object_pool, key, item, 
+                       insert(object, object_pool, key, item,
                               item_pool, result_pool));
   return SVN_NO_ERROR;
 }

Modified: subversion/branches/multi-wc-format/subversion/libsvn_subr/opt.c
URL: http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/subversion/libsvn_subr/opt.c?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/subversion/libsvn_subr/opt.c (original)
+++ subversion/branches/multi-wc-format/subversion/libsvn_subr/opt.c Fri Jan 14 14:01:45 2022
@@ -211,12 +211,16 @@ svn_opt_subcommand_takes_option4(const s
 
 /* Print the canonical command name for CMD, and all its aliases, to
    STREAM.  If HELP is set, print CMD's help string too, in which case
-   obtain option usage from OPTIONS_TABLE. */
+   obtain option usage from OPTIONS_TABLE.
+
+   Include global and experimental options iff VERBOSE is true.
+ */
 static svn_error_t *
 print_command_info3(const svn_opt_subcommand_desc3_t *cmd,
                     const apr_getopt_option_t *options_table,
                     const int *global_options,
                     svn_boolean_t help,
+                    svn_boolean_t verbose,
                     apr_pool_t *pool,
                     FILE *stream)
 {
@@ -251,6 +255,7 @@ print_command_info3(const svn_opt_subcom
       const apr_getopt_option_t *option;
       const char *long_alias;
       svn_boolean_t have_options = FALSE;
+      svn_boolean_t have_experimental = FALSE;
 
       SVN_ERR(svn_cmdline_fprintf(stream, pool, ": "));
 
@@ -279,6 +284,17 @@ print_command_info3(const svn_opt_subcom
               if (option && option->description)
                 {
                   const char *optstr;
+
+                  if (option->name && strncmp(option->name, "x-", 2) == 0)
+                    {
+                      if (verbose && !have_experimental)
+                        SVN_ERR(svn_cmdline_fputs(_("\nExperimental options:\n"),
+                                                  stream, pool));
+                      have_experimental = TRUE;
+                      if (!verbose)
+                        continue;
+                    }
+
                   format_option(&optstr, option, long_alias, TRUE, pool);
                   SVN_ERR(svn_cmdline_fprintf(stream, pool, "  %s\n",
                                               optstr));
@@ -286,7 +302,7 @@ print_command_info3(const svn_opt_subcom
             }
         }
       /* And global options too */
-      if (global_options && *global_options)
+      if (verbose && global_options && *global_options)
         {
           SVN_ERR(svn_cmdline_fputs(_("\nGlobal options:\n"),
                                     stream, pool));
@@ -310,6 +326,9 @@ print_command_info3(const svn_opt_subcom
             }
         }
 
+      if (!verbose && global_options && *global_options)
+        SVN_ERR(svn_cmdline_fputs(_("\n(Use '-v' to show global and experimental options.)\n"),
+                                  stream, pool));
       if (have_options)
         SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n"));
     }
@@ -324,23 +343,37 @@ print_generic_help_body3(const char *hea
                          const svn_opt_subcommand_desc3_t *cmd_table,
                          const apr_getopt_option_t *opt_table,
                          const char *footer,
+                         svn_boolean_t with_experimental,
                          apr_pool_t *pool, FILE *stream)
 {
-  int i = 0;
+  svn_boolean_t have_experimental = FALSE;
+  int i;
 
   if (header)
     SVN_ERR(svn_cmdline_fputs(header, stream, pool));
 
-  while (cmd_table[i].name)
+  for (i = 0; cmd_table[i].name; i++)
     {
+      if (strncmp(cmd_table[i].name, "x-", 2) == 0)
+        {
+          if (with_experimental && !have_experimental)
+            SVN_ERR(svn_cmdline_fputs(_("\nExperimental subcommands:\n"),
+                                      stream, pool));
+          have_experimental = TRUE;
+          if (!with_experimental)
+            continue;
+        }
       SVN_ERR(svn_cmdline_fputs("   ", stream, pool));
       SVN_ERR(print_command_info3(cmd_table + i, opt_table,
-                                  NULL, FALSE,
+                                  NULL, FALSE, FALSE,
                                   pool, stream));
       SVN_ERR(svn_cmdline_fputs("\n", stream, pool));
-      i++;
     }
 
+  if (have_experimental && !with_experimental)
+    SVN_ERR(svn_cmdline_fputs(_("\n(Use '-v' to show experimental subcommands.)\n"),
+                              stream, pool));
+
   SVN_ERR(svn_cmdline_fputs("\n", stream, pool));
 
   if (footer)
@@ -349,17 +382,19 @@ print_generic_help_body3(const char *hea
   return SVN_NO_ERROR;
 }
 
-void
-svn_opt_print_generic_help3(const char *header,
-                            const svn_opt_subcommand_desc3_t *cmd_table,
-                            const apr_getopt_option_t *opt_table,
-                            const char *footer,
-                            apr_pool_t *pool, FILE *stream)
+static void
+print_generic_help(const char *header,
+                   const svn_opt_subcommand_desc3_t *cmd_table,
+                   const apr_getopt_option_t *opt_table,
+                   const char *footer,
+                   svn_boolean_t with_experimental,
+                   apr_pool_t *pool, FILE *stream)
 {
   svn_error_t *err;
 
-  err = print_generic_help_body3(header, cmd_table, opt_table, footer, pool,
-                                 stream);
+  err = print_generic_help_body3(header, cmd_table, opt_table, footer,
+                                 with_experimental,
+                                 pool, stream);
 
   /* Issue #3014:
    * Don't print anything on broken pipes. The pipe was likely
@@ -373,13 +408,29 @@ svn_opt_print_generic_help3(const char *
   svn_error_clear(err);
 }
 
-
 void
-svn_opt_subcommand_help4(const char *subcommand,
-                         const svn_opt_subcommand_desc3_t *table,
-                         const apr_getopt_option_t *options_table,
-                         const int *global_options,
-                         apr_pool_t *pool)
+svn_opt_print_generic_help3(const char *header,
+                            const svn_opt_subcommand_desc3_t *cmd_table,
+                            const apr_getopt_option_t *opt_table,
+                            const char *footer,
+                            apr_pool_t *pool, FILE *stream)
+{
+  print_generic_help(header, cmd_table, opt_table, footer,
+                     TRUE, pool, stream);
+}
+
+
+/* The body of svn_opt_subcommand_help4(), which see.
+ *
+ * VERBOSE means show also the subcommand's global and experimental options.
+ */
+static void
+subcommand_help(const char *subcommand,
+                const svn_opt_subcommand_desc3_t *table,
+                const apr_getopt_option_t *options_table,
+                const int *global_options,
+                svn_boolean_t verbose,
+                apr_pool_t *pool)
 {
   const svn_opt_subcommand_desc3_t *cmd =
     svn_opt_get_canonical_subcommand3(table, subcommand);
@@ -387,7 +438,7 @@ svn_opt_subcommand_help4(const char *sub
 
   if (cmd)
     err = print_command_info3(cmd, options_table, global_options,
-                              TRUE, pool, stdout);
+                              TRUE, verbose, pool, stdout);
   else
     err = svn_cmdline_fprintf(stderr, pool,
                               _("\"%s\": unknown command.\n\n"), subcommand);
@@ -400,6 +451,17 @@ svn_opt_subcommand_help4(const char *sub
   }
 }
 
+void
+svn_opt_subcommand_help4(const char *subcommand,
+                         const svn_opt_subcommand_desc3_t *table,
+                         const apr_getopt_option_t *options_table,
+                         const int *global_options,
+                         apr_pool_t *pool)
+{
+  subcommand_help(subcommand, table, options_table, global_options,
+                  TRUE, pool);
+}
+
 
 
 /*** Parsing revision and date options. ***/
@@ -1185,9 +1247,9 @@ svn_opt_print_help5(apr_getopt_t *os,
 
       for (i = 0; i < targets->nelts; i++)
         {
-          svn_opt_subcommand_help4(APR_ARRAY_IDX(targets, i, const char *),
-                                   cmd_table, option_table,
-                                   global_options, pool);
+          subcommand_help(APR_ARRAY_IDX(targets, i, const char *),
+                          cmd_table, option_table, global_options,
+                          verbose, pool);
         }
     }
   else if (print_version)   /* just --version */
@@ -1197,12 +1259,9 @@ svn_opt_print_help5(apr_getopt_t *os,
                                           quiet, verbose, pool));
     }
   else if (os && !targets->nelts)            /* `-h', `--help', or `help' */
-    svn_opt_print_generic_help3(header,
-                                cmd_table,
-                                option_table,
-                                footer,
-                                pool,
-                                stdout);
+    print_generic_help(header, cmd_table, option_table, footer,
+                       verbose,
+                       pool, stdout);
   else                                       /* unknown option or cmd */
     SVN_ERR(svn_cmdline_fprintf(stderr, pool,
                                 _("Type '%s help' for usage.\n"), pgm_name));

Modified: subversion/branches/multi-wc-format/subversion/libsvn_subr/path.c
URL: http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/subversion/libsvn_subr/path.c?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/subversion/libsvn_subr/path.c (original)
+++ subversion/branches/multi-wc-format/subversion/libsvn_subr/path.c Fri Jan 14 14:01:45 2022
@@ -590,7 +590,7 @@ svn_path_is_ancestor(const char *path1,
 {
   apr_size_t path1_len = strlen(path1);
 
-  /* If path1 is empty and path2 is not absoulte, then path1 is an ancestor. */
+  /* If path1 is empty and path2 is not absolute, then path1 is an ancestor. */
   if (SVN_PATH_IS_EMPTY(path1))
     return *path2 != '/';
 

Modified: subversion/branches/multi-wc-format/subversion/libsvn_subr/pool.c
URL: http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/subversion/libsvn_subr/pool.c?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/subversion/libsvn_subr/pool.c (original)
+++ subversion/branches/multi-wc-format/subversion/libsvn_subr/pool.c Fri Jan 14 14:01:45 2022
@@ -133,7 +133,7 @@ svn_pool_create_allocator(svn_boolean_t
 #endif
 
   /* By default, allocators are *not* thread-safe. We must provide a mutex
-   * if we want thread-safety for that mutex. */
+   * if we want thread-safety for that pool. */
 
 #if APR_HAS_THREADS
   if (thread_safe)
@@ -151,16 +151,6 @@ svn_pool_create_allocator(svn_boolean_t
 }
 
 
-/*
- * apr_pool_create_core_ex was introduced in APR 1.3.0, then
- * deprecated and renamed to apr_pool_create_unmanaged_ex in 1.3.3.
- * Since our minimum requirement is APR 1.3.0, one or the other of
- * these functions will always be available.
- */
-#if !APR_VERSION_AT_LEAST(1,3,3)
-#define apr_pool_create_unmanaged_ex apr_pool_create_core_ex
-#endif
-
 /* Private function that creates an unmanaged pool. */
 apr_pool_t *
 svn_pool__create_unmanaged(svn_boolean_t thread_safe)

Modified: subversion/branches/multi-wc-format/subversion/libsvn_subr/prompt.c
URL: http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/subversion/libsvn_subr/prompt.c?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/subversion/libsvn_subr/prompt.c (original)
+++ subversion/branches/multi-wc-format/subversion/libsvn_subr/prompt.c Fri Jan 14 14:01:45 2022
@@ -262,7 +262,7 @@ terminal_puts(const char *string, termin
 
 /* These codes can be returned from terminal_getc instead of a character. */
 #define TERMINAL_NONE  0x80000               /* no character read, retry */
-#define TERMINAL_DEL   (TERMINAL_NONE + 1)   /* the input was a deleteion */
+#define TERMINAL_DEL   (TERMINAL_NONE + 1)   /* the input was a deletion */
 #define TERMINAL_EOL   (TERMINAL_NONE + 2)   /* end of input/end of line */
 #define TERMINAL_EOF   (TERMINAL_NONE + 3)   /* end of file during input */
 

Modified: subversion/branches/multi-wc-format/subversion/libsvn_subr/sorts.c
URL: http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/subversion/libsvn_subr/sorts.c?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/subversion/libsvn_subr/sorts.c (original)
+++ subversion/branches/multi-wc-format/subversion/libsvn_subr/sorts.c Fri Jan 14 14:01:45 2022
@@ -34,6 +34,8 @@
 #include "svn_error.h"
 #include "private/svn_sorts_private.h"
 
+#include "svn_private_config.h"
+
 
 
 /*** svn_sort__hash() ***/
@@ -299,15 +301,20 @@ svn_sort__array_lookup(const apr_array_h
   return compare_func(result, key) ? NULL : result;
 }
 
-void
-svn_sort__array_insert(apr_array_header_t *array,
-                       const void *new_element,
-                       int insert_index)
+svn_error_t *
+svn_sort__array_insert2(apr_array_header_t *array,
+                        const void *new_element,
+                        int insert_index)
 {
   int elements_to_move;
   char *new_position;
 
-  assert(0 <= insert_index && insert_index <= array->nelts);
+  if (insert_index < 0 || insert_index > array->nelts)
+    return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+                             _("svn_sort__array_insert2: Attempted insert "
+                               "at index %d in array length %d"),
+                             insert_index, array->nelts);
+
   elements_to_move = array->nelts - insert_index;  /* before bumping nelts */
 
   /* Grow the array, allocating a new space at the end. Note: this can
@@ -322,31 +329,35 @@ svn_sort__array_insert(apr_array_header_
 
   /* Copy in the new element */
   memcpy(new_position, new_element, array->elt_size);
+  return SVN_NO_ERROR;
 }
 
-void
-svn_sort__array_delete(apr_array_header_t *arr,
-                       int delete_index,
-                       int elements_to_delete)
-{
-  /* Do we have a valid index and are there enough elements? */
-  if (delete_index >= 0
-      && delete_index < arr->nelts
-      && elements_to_delete > 0
-      && (arr->nelts - delete_index) >= elements_to_delete)
-    {
-      /* If we are not deleting a block of elements that extends to the end
-         of the array, then we need to move the remaining elements to keep
-         the array contiguous. */
-      if ((elements_to_delete + delete_index) < arr->nelts)
-        memmove(
-          arr->elts + arr->elt_size * delete_index,
-          arr->elts + (arr->elt_size * (delete_index + elements_to_delete)),
-          arr->elt_size * (arr->nelts - elements_to_delete - delete_index));
-
-      /* Delete the last ELEMENTS_TO_DELETE elements. */
-      arr->nelts -= elements_to_delete;
-    }
+svn_error_t *
+svn_sort__array_delete2(apr_array_header_t *arr,
+                        int delete_index,
+                        int elements_to_delete)
+{
+  if (!(delete_index >= 0
+        && delete_index < arr->nelts
+        && elements_to_delete > 0
+        && (arr->nelts - delete_index) >= elements_to_delete))
+    return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+                             _("svn_sort__array_delete2: Attempted delete "
+                               "at index %d, %d elements, in array length %d"),
+                             delete_index, elements_to_delete, arr->nelts);
+
+  /* If we are deleting a block of elements that does not extend to the end
+     of the array, then we need to move the remaining elements to keep
+     the array contiguous. */
+  if ((elements_to_delete + delete_index) < arr->nelts)
+    memmove(
+      arr->elts + arr->elt_size * delete_index,
+      arr->elts + (arr->elt_size * (delete_index + elements_to_delete)),
+      arr->elt_size * (arr->nelts - elements_to_delete - delete_index));
+
+  /* Delete the last ELEMENTS_TO_DELETE elements. */
+  arr->nelts -= elements_to_delete;
+  return SVN_NO_ERROR;
 }
 
 void

Modified: subversion/branches/multi-wc-format/subversion/libsvn_subr/sqlite3wrapper.c
URL: http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/subversion/libsvn_subr/sqlite3wrapper.c?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/subversion/libsvn_subr/sqlite3wrapper.c (original)
+++ subversion/branches/multi-wc-format/subversion/libsvn_subr/sqlite3wrapper.c Fri Jan 14 14:01:45 2022
@@ -25,6 +25,8 @@
 /* Include sqlite3 inline, making all symbols private. */
 #ifdef SVN_SQLITE_INLINE
 #  define SQLITE_OMIT_DEPRECATED 1
+#  define SQLITE_DEFAULT_MEMSTATUS 0
+#  define SQLITE_OMIT_WAL 1
 #  define SQLITE_API static
 #  if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2)
 #    pragma GCC diagnostic ignored "-Wunreachable-code"