[Unison-hackers] [unison-svn] r504 - trunk/src

vouillon at seas.upenn.edu vouillon at seas.upenn.edu
Thu Aug 9 10:06:21 EDT 2012


Author: vouillon
Date: 2012-08-09 10:06:21 -0400 (Thu, 09 Aug 2012)
New Revision: 504

Added:
   trunk/src/fswatchold.ml
   trunk/src/fswatchold.mli
Modified:
   trunk/src/.depend
   trunk/src/Makefile.OCaml
   trunk/src/RECENTNEWS
   trunk/src/mkProjectInfo.ml
   trunk/src/os.ml
   trunk/src/os.mli
   trunk/src/test.ml
   trunk/src/uigtk2.ml
   trunk/src/uimacbridge.ml
   trunk/src/uimacbridgenew.ml
   trunk/src/uitext.ml
   trunk/src/update.ml
   trunk/src/update.mli
Log:
* Bumped version number: incompatible protocol changes
* Improvements to the file watching functionality:
  - retries paths with failures using an exponential backoff algorithm
  - the information returned by the file watchers are used
    independently for each replica; thus, when only one replica has
    changes, Unison will only rescan this replica
  - when available, used by the graphical UIs to speed up rescanning
    (can be disabled by setting the new 'watch' preference to false)


Modified: trunk/src/.depend
===================================================================
--- trunk/src/.depend	2012-08-07 20:06:46 UTC (rev 503)
+++ trunk/src/.depend	2012-08-09 14:06:21 UTC (rev 504)
@@ -1,359 +1,371 @@
-abort.cmi: uutil.cmi 
-bytearray.cmi: 
-case.cmi: ubase/prefs.cmi 
-checksum.cmi: 
-clroot.cmi: 
+abort.cmi: uutil.cmi
+bytearray.cmi:
+case.cmi: ubase/prefs.cmi
+checksum.cmi:
+clroot.cmi:
 common.cmi: uutil.cmi props.cmi path.cmi osx.cmi os.cmi name.cmi fspath.cmi \
-    fileinfo.cmi 
+    fileinfo.cmi
 copy.cmi: uutil.cmi props.cmi path.cmi osx.cmi os.cmi lwt/lwt.cmi fspath.cmi \
-    fileinfo.cmi common.cmi 
-external.cmi: lwt/lwt.cmi 
+    fileinfo.cmi common.cmi
+external.cmi: lwt/lwt.cmi
 fileinfo.cmi: system.cmi props.cmi ubase/prefs.cmi path.cmi osx.cmi \
-    fspath.cmi 
+    fspath.cmi
 files.cmi: uutil.cmi system.cmi props.cmi path.cmi lwt/lwt_util.cmi \
-    lwt/lwt.cmi common.cmi 
-fileutil.cmi: 
-fingerprint.cmi: uutil.cmi path.cmi fspath.cmi 
+    lwt/lwt.cmi common.cmi
+fileutil.cmi:
+fingerprint.cmi: uutil.cmi path.cmi fspath.cmi
 fpcache.cmi: system.cmi props.cmi path.cmi osx.cmi os.cmi fspath.cmi \
-    fileinfo.cmi 
-fs.cmi: system/system_intf.cmo fspath.cmi 
-fspath.cmi: system.cmi path.cmi name.cmi 
-globals.cmi: ubase/prefs.cmi pred.cmi path.cmi lwt/lwt.cmi common.cmi 
-lock.cmi: system.cmi 
-name.cmi: 
-os.cmi: system.cmi props.cmi path.cmi name.cmi fspath.cmi fileinfo.cmi 
-osx.cmi: uutil.cmi ubase/prefs.cmi path.cmi fspath.cmi fingerprint.cmi 
-path.cmi: pred.cmi name.cmi 
-pred.cmi: 
-props.cmi: uutil.cmi ubase/prefs.cmi path.cmi osx.cmi fspath.cmi 
-recon.cmi: props.cmi path.cmi common.cmi 
+    fileinfo.cmi
+fs.cmi: system/system_intf.cmo fspath.cmi
+fspath.cmi: system.cmi path.cmi name.cmi
+fswatchold.cmi: path.cmi lwt/lwt.cmi fspath.cmi
+globals.cmi: ubase/prefs.cmi pred.cmi path.cmi lwt/lwt.cmi common.cmi
+lock.cmi: system.cmi
+name.cmi:
+os.cmi: uutil.cmi system.cmi props.cmi path.cmi name.cmi fspath.cmi \
+    fileinfo.cmi
+osx.cmi: uutil.cmi ubase/prefs.cmi path.cmi fspath.cmi fingerprint.cmi
+path.cmi: pred.cmi name.cmi
+pred.cmi:
+props.cmi: uutil.cmi ubase/prefs.cmi path.cmi osx.cmi fspath.cmi
+recon.cmi: props.cmi path.cmi common.cmi
 remote.cmi: ubase/prefs.cmi lwt/lwt.cmi fspath.cmi common.cmi clroot.cmi \
-    bytearray.cmi 
-sortri.cmi: common.cmi 
-stasher.cmi: update.cmi ubase/prefs.cmi path.cmi os.cmi fspath.cmi 
-strings.cmi: 
-system.cmi: system/system_intf.cmo 
-terminal.cmi: lwt/lwt_unix.cmi 
-test.cmi: 
-transfer.cmi: uutil.cmi lwt/lwt.cmi bytearray.cmi 
-transport.cmi: uutil.cmi lwt/lwt.cmi common.cmi 
-tree.cmi: 
-ui.cmi: 
-uicommon.cmi: uutil.cmi ubase/prefs.cmi path.cmi lwt/lwt.cmi common.cmi 
-uigtk.cmi: uicommon.cmi 
-uigtk2.cmi: uicommon.cmi 
-uitext.cmi: uicommon.cmi 
-unicode.cmi: 
+    bytearray.cmi
+sortri.cmi: common.cmi
+stasher.cmi: update.cmi ubase/prefs.cmi path.cmi os.cmi fspath.cmi
+strings.cmi:
+system.cmi: system/system_intf.cmo
+terminal.cmi: lwt/lwt_unix.cmi
+test.cmi:
+transfer.cmi: uutil.cmi lwt/lwt.cmi bytearray.cmi
+transport.cmi: uutil.cmi lwt/lwt.cmi common.cmi
+tree.cmi:
+ui.cmi:
+uicommon.cmi: uutil.cmi ubase/prefs.cmi path.cmi lwt/lwt.cmi common.cmi
+uigtk.cmi: uicommon.cmi
+uigtk2.cmi: uicommon.cmi
+uitext.cmi: uicommon.cmi
+unicode.cmi:
 update.cmi: uutil.cmi tree.cmi props.cmi path.cmi osx.cmi os.cmi name.cmi \
-    lwt/lwt.cmi fspath.cmi fileinfo.cmi common.cmi 
-uutil.cmi: 
-xferhint.cmi: ubase/prefs.cmi path.cmi os.cmi fspath.cmi 
-abort.cmo: uutil.cmi ubase/util.cmi ubase/trace.cmi ubase/prefs.cmi abort.cmi 
-abort.cmx: uutil.cmx ubase/util.cmx ubase/trace.cmx ubase/prefs.cmx abort.cmi 
-bytearray.cmo: bytearray.cmi 
-bytearray.cmx: bytearray.cmi 
-case.cmo: ubase/util.cmi unicode.cmi ubase/prefs.cmi case.cmi 
-case.cmx: ubase/util.cmx unicode.cmx ubase/prefs.cmx case.cmi 
-checksum.cmo: checksum.cmi 
-checksum.cmx: checksum.cmi 
-clroot.cmo: ubase/util.cmi ubase/rx.cmi ubase/prefs.cmi clroot.cmi 
-clroot.cmx: ubase/util.cmx ubase/rx.cmx ubase/prefs.cmx clroot.cmi 
+    ubase/myMap.cmi lwt/lwt.cmi fspath.cmi fileinfo.cmi common.cmi
+uutil.cmi:
+xferhint.cmi: ubase/prefs.cmi path.cmi os.cmi fspath.cmi
+abort.cmo: uutil.cmi ubase/util.cmi ubase/trace.cmi ubase/prefs.cmi abort.cmi
+abort.cmx: uutil.cmx ubase/util.cmx ubase/trace.cmx ubase/prefs.cmx abort.cmi
+bytearray.cmo: bytearray.cmi
+bytearray.cmx: bytearray.cmi
+case.cmo: ubase/util.cmi unicode.cmi ubase/prefs.cmi case.cmi
+case.cmx: ubase/util.cmx unicode.cmx ubase/prefs.cmx case.cmi
+checksum.cmo: checksum.cmi
+checksum.cmx: checksum.cmi
+clroot.cmo: ubase/util.cmi ubase/rx.cmi ubase/prefs.cmi clroot.cmi
+clroot.cmx: ubase/util.cmx ubase/rx.cmx ubase/prefs.cmx clroot.cmi
 common.cmo: uutil.cmi ubase/util.cmi ubase/safelist.cmi props.cmi path.cmi \
-    osx.cmi os.cmi name.cmi fspath.cmi fileinfo.cmi common.cmi 
+    osx.cmi os.cmi name.cmi fspath.cmi fileinfo.cmi common.cmi
 common.cmx: uutil.cmx ubase/util.cmx ubase/safelist.cmx props.cmx path.cmx \
-    osx.cmx os.cmx name.cmx fspath.cmx fileinfo.cmx common.cmi 
+    osx.cmx os.cmx name.cmx fspath.cmx fileinfo.cmx common.cmi
 copy.cmo: xferhint.cmi uutil.cmi ubase/util.cmi update.cmi transfer.cmi \
     ubase/trace.cmi ubase/safelist.cmi remote.cmi props.cmi ubase/prefs.cmi \
     path.cmi osx.cmi os.cmi lwt/lwt_util.cmi lwt/lwt.cmi globals.cmi \
     fspath.cmi fs.cmi fpcache.cmi fingerprint.cmi fileinfo.cmi external.cmi \
-    common.cmi clroot.cmi bytearray.cmi abort.cmi copy.cmi 
+    common.cmi clroot.cmi bytearray.cmi abort.cmi copy.cmi
 copy.cmx: xferhint.cmx uutil.cmx ubase/util.cmx update.cmx transfer.cmx \
     ubase/trace.cmx ubase/safelist.cmx remote.cmx props.cmx ubase/prefs.cmx \
     path.cmx osx.cmx os.cmx lwt/lwt_util.cmx lwt/lwt.cmx globals.cmx \
     fspath.cmx fs.cmx fpcache.cmx fingerprint.cmx fileinfo.cmx external.cmx \
-    common.cmx clroot.cmx bytearray.cmx abort.cmx copy.cmi 
+    common.cmx clroot.cmx bytearray.cmx abort.cmx copy.cmi
 external.cmo: ubase/util.cmi system.cmi ubase/safelist.cmi lwt/lwt_util.cmi \
-    lwt/lwt_unix.cmi lwt/lwt.cmi external.cmi 
+    lwt/lwt_unix.cmi lwt/lwt.cmi external.cmi
 external.cmx: ubase/util.cmx system.cmx ubase/safelist.cmx lwt/lwt_util.cmx \
-    lwt/lwt_unix.cmx lwt/lwt.cmx external.cmi 
+    lwt/lwt_unix.cmx lwt/lwt.cmx external.cmi
 fileinfo.cmo: ubase/util.cmi system.cmi props.cmi ubase/prefs.cmi path.cmi \
-    osx.cmi fspath.cmi fs.cmi fileinfo.cmi 
+    osx.cmi fspath.cmi fs.cmi fileinfo.cmi
 fileinfo.cmx: ubase/util.cmx system.cmx props.cmx ubase/prefs.cmx path.cmx \
-    osx.cmx fspath.cmx fs.cmx fileinfo.cmi 
+    osx.cmx fspath.cmx fs.cmx fileinfo.cmi
 files.cmo: xferhint.cmi uutil.cmi ubase/util.cmi update.cmi ubase/trace.cmi \
     system.cmi stasher.cmi ubase/safelist.cmi ubase/rx.cmi remote.cmi \
     props.cmi ubase/prefs.cmi path.cmi osx.cmi os.cmi name.cmi \
     lwt/lwt_util.cmi lwt/lwt_unix.cmi lwt/lwt.cmi globals.cmi fspath.cmi \
     fs.cmi fingerprint.cmi fileinfo.cmi external.cmi copy.cmi common.cmi \
-    abort.cmi files.cmi 
+    abort.cmi files.cmi
 files.cmx: xferhint.cmx uutil.cmx ubase/util.cmx update.cmx ubase/trace.cmx \
     system.cmx stasher.cmx ubase/safelist.cmx ubase/rx.cmx remote.cmx \
     props.cmx ubase/prefs.cmx path.cmx osx.cmx os.cmx name.cmx \
     lwt/lwt_util.cmx lwt/lwt_unix.cmx lwt/lwt.cmx globals.cmx fspath.cmx \
     fs.cmx fingerprint.cmx fileinfo.cmx external.cmx copy.cmx common.cmx \
-    abort.cmx files.cmi 
-fileutil.cmo: fileutil.cmi 
-fileutil.cmx: fileutil.cmi 
-fingerprint.cmo: uutil.cmi ubase/util.cmi fspath.cmi fs.cmi fingerprint.cmi 
-fingerprint.cmx: uutil.cmx ubase/util.cmx fspath.cmx fs.cmx fingerprint.cmi 
+    abort.cmx files.cmi
+fileutil.cmo: fileutil.cmi
+fileutil.cmx: fileutil.cmi
+fingerprint.cmo: uutil.cmi ubase/util.cmi path.cmi fspath.cmi fs.cmi \
+    fingerprint.cmi
+fingerprint.cmx: uutil.cmx ubase/util.cmx path.cmx fspath.cmx fs.cmx \
+    fingerprint.cmi
 fpcache.cmo: uutil.cmi ubase/util.cmi ubase/trace.cmi system.cmi \
-    ubase/safelist.cmi props.cmi path.cmi osx.cmi os.cmi fileinfo.cmi \
-    fpcache.cmi 
+    ubase/safelist.cmi props.cmi ubase/prefs.cmi path.cmi osx.cmi os.cmi \
+    fspath.cmi fileinfo.cmi fpcache.cmi
 fpcache.cmx: uutil.cmx ubase/util.cmx ubase/trace.cmx system.cmx \
-    ubase/safelist.cmx props.cmx path.cmx osx.cmx os.cmx fileinfo.cmx \
-    fpcache.cmi 
-fs.cmo: fspath.cmi fs.cmi 
-fs.cmx: fspath.cmx fs.cmi 
+    ubase/safelist.cmx props.cmx ubase/prefs.cmx path.cmx osx.cmx os.cmx \
+    fspath.cmx fileinfo.cmx fpcache.cmi
+fs.cmo: fspath.cmi fs.cmi
+fs.cmx: fspath.cmx fs.cmi
 fspath.cmo: uutil.cmi ubase/util.cmi system.cmi ubase/rx.cmi path.cmi \
-    name.cmi fileutil.cmi fspath.cmi 
+    name.cmi fileutil.cmi fspath.cmi
 fspath.cmx: uutil.cmx ubase/util.cmx system.cmx ubase/rx.cmx path.cmx \
-    name.cmx fileutil.cmx fspath.cmi 
+    name.cmx fileutil.cmx fspath.cmi
+fswatchold.cmo: uutil.cmi ubase/util.cmi system.cmi ubase/safelist.cmi \
+    ubase/prefs.cmi pred.cmi path.cmi os.cmi lwt/lwt_unix.cmi lwt/lwt.cmi \
+    globals.cmi fspath.cmi fswatchold.cmi
+fswatchold.cmx: uutil.cmx ubase/util.cmx system.cmx ubase/safelist.cmx \
+    ubase/prefs.cmx pred.cmx path.cmx os.cmx lwt/lwt_unix.cmx lwt/lwt.cmx \
+    globals.cmx fspath.cmx fswatchold.cmi
 globals.cmo: ubase/util.cmi ubase/trace.cmi ubase/safelist.cmi remote.cmi \
     ubase/prefs.cmi pred.cmi path.cmi os.cmi name.cmi lwt/lwt_util.cmi \
-    lwt/lwt_unix.cmi lwt/lwt.cmi common.cmi clroot.cmi globals.cmi 
+    lwt/lwt_unix.cmi lwt/lwt.cmi common.cmi clroot.cmi globals.cmi
 globals.cmx: ubase/util.cmx ubase/trace.cmx ubase/safelist.cmx remote.cmx \
     ubase/prefs.cmx pred.cmx path.cmx os.cmx name.cmx lwt/lwt_util.cmx \
-    lwt/lwt_unix.cmx lwt/lwt.cmx common.cmx clroot.cmx globals.cmi 
-library_info.cmo: 
-library_info.cmx: 
-linkgtk.cmo: uigtk.cmi main.cmo 
-linkgtk.cmx: uigtk.cmx main.cmx 
-linkgtk2.cmo: uigtk2.cmi main.cmo 
-linkgtk2.cmx: uigtk2.cmx main.cmx 
-linktext.cmo: uitext.cmi main.cmo 
-linktext.cmx: uitext.cmx main.cmx 
-lock.cmo: ubase/util.cmi system.cmi lock.cmi 
-lock.cmx: ubase/util.cmx system.cmx lock.cmi 
+    lwt/lwt_unix.cmx lwt/lwt.cmx common.cmx clroot.cmx globals.cmi
+linkgtk.cmo: uigtk.cmi main.cmo
+linkgtk.cmx: uigtk.cmx main.cmx
+linkgtk2.cmo: uigtk2.cmi main.cmo
+linkgtk2.cmx: uigtk2.cmx main.cmx
+linktext.cmo: uitext.cmi main.cmo
+linktext.cmx: uitext.cmx main.cmx
+lock.cmo: ubase/util.cmi system.cmi lock.cmi
+lock.cmx: ubase/util.cmx system.cmx lock.cmi
 main.cmo: uutil.cmi ubase/util.cmi uitext.cmi uicommon.cmi strings.cmi \
-    ubase/safelist.cmi remote.cmi ubase/prefs.cmi os.cmi 
+    ubase/safelist.cmi remote.cmi ubase/prefs.cmi os.cmi
 main.cmx: uutil.cmx ubase/util.cmx uitext.cmx uicommon.cmx strings.cmx \
-    ubase/safelist.cmx remote.cmx ubase/prefs.cmx os.cmx 
-mkProjectInfo.cmo: 
-mkProjectInfo.cmx: 
-name.cmo: ubase/util.cmi ubase/rx.cmi case.cmi name.cmi 
-name.cmx: ubase/util.cmx ubase/rx.cmx case.cmx name.cmi 
-os.cmo: uutil.cmi ubase/util.cmi system.cmi ubase/safelist.cmi props.cmi \
-    ubase/prefs.cmi path.cmi osx.cmi name.cmi fspath.cmi fs.cmi \
-    fingerprint.cmi fileinfo.cmi os.cmi 
-os.cmx: uutil.cmx ubase/util.cmx system.cmx ubase/safelist.cmx props.cmx \
-    ubase/prefs.cmx path.cmx osx.cmx name.cmx fspath.cmx fs.cmx \
-    fingerprint.cmx fileinfo.cmx os.cmi 
+    ubase/safelist.cmx remote.cmx ubase/prefs.cmx os.cmx
+mkProjectInfo.cmo:
+mkProjectInfo.cmx:
+name.cmo: ubase/util.cmi ubase/rx.cmi case.cmi name.cmi
+name.cmx: ubase/util.cmx ubase/rx.cmx case.cmx name.cmi
+os.cmo: uutil.cmi ubase/util.cmi ubase/trace.cmi system.cmi \
+    ubase/safelist.cmi props.cmi ubase/prefs.cmi path.cmi osx.cmi name.cmi \
+    fspath.cmi fs.cmi fingerprint.cmi fileinfo.cmi os.cmi
+os.cmx: uutil.cmx ubase/util.cmx ubase/trace.cmx system.cmx \
+    ubase/safelist.cmx props.cmx ubase/prefs.cmx path.cmx osx.cmx name.cmx \
+    fspath.cmx fs.cmx fingerprint.cmx fileinfo.cmx os.cmi
 osx.cmo: uutil.cmi ubase/util.cmi ubase/trace.cmi system.cmi \
     ubase/safelist.cmi ubase/prefs.cmi path.cmi name.cmi fspath.cmi fs.cmi \
-    fingerprint.cmi osx.cmi 
+    fingerprint.cmi osx.cmi
 osx.cmx: uutil.cmx ubase/util.cmx ubase/trace.cmx system.cmx \
     ubase/safelist.cmx ubase/prefs.cmx path.cmx name.cmx fspath.cmx fs.cmx \
-    fingerprint.cmx osx.cmi 
+    fingerprint.cmx osx.cmi
 path.cmo: ubase/util.cmi ubase/safelist.cmi ubase/rx.cmi pred.cmi name.cmi \
-    fileutil.cmi case.cmi path.cmi 
+    fileutil.cmi case.cmi path.cmi
 path.cmx: ubase/util.cmx ubase/safelist.cmx ubase/rx.cmx pred.cmx name.cmx \
-    fileutil.cmx case.cmx path.cmi 
-pixmaps.cmo: 
-pixmaps.cmx: 
+    fileutil.cmx case.cmx path.cmi
+pixmaps.cmo:
+pixmaps.cmx:
 pred.cmo: ubase/util.cmi ubase/safelist.cmi ubase/rx.cmi ubase/prefs.cmi \
-    case.cmi pred.cmi 
+    case.cmi pred.cmi
 pred.cmx: ubase/util.cmx ubase/safelist.cmx ubase/rx.cmx ubase/prefs.cmx \
-    case.cmx pred.cmi 
+    case.cmx pred.cmi
 props.cmo: uutil.cmi ubase/util.cmi ubase/prefs.cmi path.cmi osx.cmi \
-    lwt/lwt_unix.cmi fspath.cmi fs.cmi external.cmi props.cmi 
+    lwt/lwt_unix.cmi fspath.cmi fs.cmi external.cmi props.cmi
 props.cmx: uutil.cmx ubase/util.cmx ubase/prefs.cmx path.cmx osx.cmx \
-    lwt/lwt_unix.cmx fspath.cmx fs.cmx external.cmx props.cmi 
+    lwt/lwt_unix.cmx fspath.cmx fs.cmx external.cmx props.cmi
 recon.cmo: ubase/util.cmi update.cmi tree.cmi ubase/trace.cmi sortri.cmi \
     ubase/safelist.cmi props.cmi ubase/prefs.cmi pred.cmi path.cmi name.cmi \
-    globals.cmi fileinfo.cmi common.cmi recon.cmi 
+    globals.cmi fileinfo.cmi common.cmi recon.cmi
 recon.cmx: ubase/util.cmx update.cmx tree.cmx ubase/trace.cmx sortri.cmx \
     ubase/safelist.cmx props.cmx ubase/prefs.cmx pred.cmx path.cmx name.cmx \
-    globals.cmx fileinfo.cmx common.cmx recon.cmi 
+    globals.cmx fileinfo.cmx common.cmx recon.cmi
 remote.cmo: uutil.cmi ubase/util.cmi ubase/trace.cmi terminal.cmi system.cmi \
     ubase/safelist.cmi ubase/prefs.cmi os.cmi lwt/lwt_util.cmi \
     lwt/lwt_unix.cmi lwt/lwt.cmi fspath.cmi fs.cmi common.cmi clroot.cmi \
-    case.cmi bytearray.cmi remote.cmi 
+    case.cmi bytearray.cmi remote.cmi
 remote.cmx: uutil.cmx ubase/util.cmx ubase/trace.cmx terminal.cmx system.cmx \
     ubase/safelist.cmx ubase/prefs.cmx os.cmx lwt/lwt_util.cmx \
     lwt/lwt_unix.cmx lwt/lwt.cmx fspath.cmx fs.cmx common.cmx clroot.cmx \
-    case.cmx bytearray.cmx remote.cmi 
+    case.cmx bytearray.cmx remote.cmi
 sortri.cmo: ubase/util.cmi ubase/safelist.cmi ubase/prefs.cmi pred.cmi \
-    path.cmi common.cmi sortri.cmi 
+    path.cmi common.cmi sortri.cmi
 sortri.cmx: ubase/util.cmx ubase/safelist.cmx ubase/prefs.cmx pred.cmx \
-    path.cmx common.cmx sortri.cmi 
+    path.cmx common.cmx sortri.cmi
 stasher.cmo: xferhint.cmi ubase/util.cmi update.cmi system.cmi \
     ubase/safelist.cmi remote.cmi props.cmi ubase/prefs.cmi pred.cmi path.cmi \
     osx.cmi os.cmi lwt/lwt_unix.cmi lwt/lwt.cmi globals.cmi fspath.cmi \
-    fingerprint.cmi fileutil.cmi fileinfo.cmi copy.cmi common.cmi stasher.cmi 
+    fingerprint.cmi fileutil.cmi fileinfo.cmi copy.cmi common.cmi stasher.cmi
 stasher.cmx: xferhint.cmx ubase/util.cmx update.cmx system.cmx \
     ubase/safelist.cmx remote.cmx props.cmx ubase/prefs.cmx pred.cmx path.cmx \
     osx.cmx os.cmx lwt/lwt_unix.cmx lwt/lwt.cmx globals.cmx fspath.cmx \
-    fingerprint.cmx fileutil.cmx fileinfo.cmx copy.cmx common.cmx stasher.cmi 
-strings.cmo: strings.cmi 
-strings.cmx: strings.cmi 
-system.cmo: system.cmi 
-system.cmx: system.cmi 
+    fingerprint.cmx fileutil.cmx fileinfo.cmx copy.cmx common.cmx stasher.cmi
+strings.cmo: strings.cmi
+strings.cmx: strings.cmi
+system.cmo: system.cmi
+system.cmx: system.cmi
 terminal.cmo: system.cmi ubase/rx.cmi lwt/lwt_unix.cmi lwt/lwt.cmi \
-    terminal.cmi 
+    terminal.cmi
 terminal.cmx: system.cmx ubase/rx.cmx lwt/lwt_unix.cmx lwt/lwt.cmx \
-    terminal.cmi 
+    terminal.cmi
 test.cmo: uutil.cmi ubase/util.cmi update.cmi uicommon.cmi transport.cmi \
     ubase/trace.cmi stasher.cmi ubase/safelist.cmi remote.cmi recon.cmi \
     ubase/prefs.cmi path.cmi os.cmi lwt/lwt_util.cmi lwt/lwt_unix.cmi \
     lwt/lwt.cmi globals.cmi fspath.cmi fs.cmi fingerprint.cmi common.cmi \
-    test.cmi 
+    test.cmi
 test.cmx: uutil.cmx ubase/util.cmx update.cmx uicommon.cmx transport.cmx \
     ubase/trace.cmx stasher.cmx ubase/safelist.cmx remote.cmx recon.cmx \
     ubase/prefs.cmx path.cmx os.cmx lwt/lwt_util.cmx lwt/lwt_unix.cmx \
     lwt/lwt.cmx globals.cmx fspath.cmx fs.cmx fingerprint.cmx common.cmx \
-    test.cmi 
+    test.cmi
 transfer.cmo: uutil.cmi ubase/util.cmi ubase/trace.cmi ubase/safelist.cmi \
-    lwt/lwt.cmi checksum.cmi bytearray.cmi transfer.cmi 
+    lwt/lwt.cmi checksum.cmi bytearray.cmi transfer.cmi
 transfer.cmx: uutil.cmx ubase/util.cmx ubase/trace.cmx ubase/safelist.cmx \
-    lwt/lwt.cmx checksum.cmx bytearray.cmx transfer.cmi 
+    lwt/lwt.cmx checksum.cmx bytearray.cmx transfer.cmi
 transport.cmo: uutil.cmi ubase/util.cmi ubase/trace.cmi remote.cmi props.cmi \
     ubase/prefs.cmi path.cmi osx.cmi lwt/lwt_util.cmi lwt/lwt.cmi globals.cmi \
-    files.cmi common.cmi abort.cmi transport.cmi 
+    files.cmi common.cmi abort.cmi transport.cmi
 transport.cmx: uutil.cmx ubase/util.cmx ubase/trace.cmx remote.cmx props.cmx \
     ubase/prefs.cmx path.cmx osx.cmx lwt/lwt_util.cmx lwt/lwt.cmx globals.cmx \
-    files.cmx common.cmx abort.cmx transport.cmi 
-tree.cmo: ubase/safelist.cmi tree.cmi 
-tree.cmx: ubase/safelist.cmx tree.cmi 
+    files.cmx common.cmx abort.cmx transport.cmi
+tree.cmo: ubase/safelist.cmi tree.cmi
+tree.cmx: ubase/safelist.cmx tree.cmi
 uicommon.cmo: xferhint.cmi uutil.cmi ubase/util.cmi update.cmi \
     ubase/trace.cmi system.cmi stasher.cmi ubase/safelist.cmi remote.cmi \
     recon.cmi props.cmi ubase/prefs.cmi path.cmi osx.cmi os.cmi name.cmi \
     lwt/lwt_unix.cmi lwt/lwt.cmi globals.cmi fspath.cmi files.cmi \
-    fileinfo.cmi common.cmi clroot.cmi case.cmi uicommon.cmi 
+    fileinfo.cmi common.cmi clroot.cmi case.cmi uicommon.cmi
 uicommon.cmx: xferhint.cmx uutil.cmx ubase/util.cmx update.cmx \
     ubase/trace.cmx system.cmx stasher.cmx ubase/safelist.cmx remote.cmx \
     recon.cmx props.cmx ubase/prefs.cmx path.cmx osx.cmx os.cmx name.cmx \
     lwt/lwt_unix.cmx lwt/lwt.cmx globals.cmx fspath.cmx files.cmx \
-    fileinfo.cmx common.cmx clroot.cmx case.cmx uicommon.cmi 
+    fileinfo.cmx common.cmx clroot.cmx case.cmx uicommon.cmi
 uigtk.cmo: uutil.cmi ubase/util.cmi update.cmi uitext.cmi uicommon.cmi \
     transport.cmi ubase/trace.cmi system.cmi strings.cmi sortri.cmi \
     ubase/safelist.cmi remote.cmi recon.cmi ubase/prefs.cmi pixmaps.cmo \
     path.cmi os.cmi lwt/lwt_util.cmi lwt/lwt_unix.cmi lwt/lwt.cmi globals.cmi \
-    files.cmi common.cmi clroot.cmi uigtk.cmi 
+    files.cmi common.cmi clroot.cmi uigtk.cmi
 uigtk.cmx: uutil.cmx ubase/util.cmx update.cmx uitext.cmx uicommon.cmx \
     transport.cmx ubase/trace.cmx system.cmx strings.cmx sortri.cmx \
     ubase/safelist.cmx remote.cmx recon.cmx ubase/prefs.cmx pixmaps.cmx \
     path.cmx os.cmx lwt/lwt_util.cmx lwt/lwt_unix.cmx lwt/lwt.cmx globals.cmx \
-    files.cmx common.cmx clroot.cmx uigtk.cmi 
+    files.cmx common.cmx clroot.cmx uigtk.cmi
 uigtk2.cmo: uutil.cmi ubase/util.cmi update.cmi unicode.cmi uitext.cmi \
     uicommon.cmi transport.cmi ubase/trace.cmi system.cmi strings.cmi \
     sortri.cmi ubase/safelist.cmi remote.cmi recon.cmi ubase/prefs.cmi \
     pixmaps.cmo path.cmi os.cmi lwt/lwt_util.cmi lwt/lwt_unix.cmi lwt/lwt.cmi \
-    globals.cmi files.cmi common.cmi clroot.cmi case.cmi uigtk2.cmi 
+    globals.cmi files.cmi common.cmi clroot.cmi case.cmi uigtk2.cmi
 uigtk2.cmx: uutil.cmx ubase/util.cmx update.cmx unicode.cmx uitext.cmx \
     uicommon.cmx transport.cmx ubase/trace.cmx system.cmx strings.cmx \
     sortri.cmx ubase/safelist.cmx remote.cmx recon.cmx ubase/prefs.cmx \
     pixmaps.cmx path.cmx os.cmx lwt/lwt_util.cmx lwt/lwt_unix.cmx lwt/lwt.cmx \
-    globals.cmx files.cmx common.cmx clroot.cmx case.cmx uigtk2.cmi 
+    globals.cmx files.cmx common.cmx clroot.cmx case.cmx uigtk2.cmi
 uimacbridge.cmo: xferhint.cmi uutil.cmi ubase/util.cmi update.cmi \
     uicommon.cmi transport.cmi ubase/trace.cmi terminal.cmi system.cmi \
     stasher.cmi ubase/safelist.cmi remote.cmi recon.cmi ubase/prefs.cmi \
     path.cmi os.cmi main.cmo lwt/lwt_util.cmi lwt/lwt_unix.cmi lwt/lwt.cmi \
-    globals.cmi fspath.cmi files.cmi common.cmi clroot.cmi 
+    globals.cmi fspath.cmi files.cmi common.cmi clroot.cmi
 uimacbridge.cmx: xferhint.cmx uutil.cmx ubase/util.cmx update.cmx \
     uicommon.cmx transport.cmx ubase/trace.cmx terminal.cmx system.cmx \
     stasher.cmx ubase/safelist.cmx remote.cmx recon.cmx ubase/prefs.cmx \
     path.cmx os.cmx main.cmx lwt/lwt_util.cmx lwt/lwt_unix.cmx lwt/lwt.cmx \
-    globals.cmx fspath.cmx files.cmx common.cmx clroot.cmx 
+    globals.cmx fspath.cmx files.cmx common.cmx clroot.cmx
 uimacbridgenew.cmo: xferhint.cmi uutil.cmi ubase/util.cmi update.cmi \
     unicode.cmi uicommon.cmi transport.cmi ubase/trace.cmi terminal.cmi \
     system.cmi stasher.cmi ubase/safelist.cmi remote.cmi recon.cmi \
     ubase/prefs.cmi path.cmi os.cmi main.cmo lwt/lwt_util.cmi \
     lwt/lwt_unix.cmi lwt/lwt.cmi globals.cmi fspath.cmi files.cmi common.cmi \
-    clroot.cmi 
+    clroot.cmi
 uimacbridgenew.cmx: xferhint.cmx uutil.cmx ubase/util.cmx update.cmx \
     unicode.cmx uicommon.cmx transport.cmx ubase/trace.cmx terminal.cmx \
     system.cmx stasher.cmx ubase/safelist.cmx remote.cmx recon.cmx \
     ubase/prefs.cmx path.cmx os.cmx main.cmx lwt/lwt_util.cmx \
     lwt/lwt_unix.cmx lwt/lwt.cmx globals.cmx fspath.cmx files.cmx common.cmx \
-    clroot.cmx 
+    clroot.cmx
 uitext.cmo: uutil.cmi ubase/util.cmi update.cmi uicommon.cmi transport.cmi \
     ubase/trace.cmi system.cmi ubase/safelist.cmi remote.cmi recon.cmi \
     ubase/prefs.cmi path.cmi lwt/lwt_util.cmi lwt/lwt_unix.cmi lwt/lwt.cmi \
-    globals.cmi common.cmi uitext.cmi 
+    globals.cmi fswatchold.cmi common.cmi uitext.cmi
 uitext.cmx: uutil.cmx ubase/util.cmx update.cmx uicommon.cmx transport.cmx \
     ubase/trace.cmx system.cmx ubase/safelist.cmx remote.cmx recon.cmx \
     ubase/prefs.cmx path.cmx lwt/lwt_util.cmx lwt/lwt_unix.cmx lwt/lwt.cmx \
-    globals.cmx common.cmx uitext.cmi 
-unicode.cmo: unicode_tables.cmo unicode.cmi 
-unicode.cmx: unicode_tables.cmx unicode.cmi 
-unicode_tables.cmo: 
-unicode_tables.cmx: 
+    globals.cmx fswatchold.cmx common.cmx uitext.cmi
+unicode.cmo: unicode_tables.cmo unicode.cmi
+unicode.cmx: unicode_tables.cmx unicode.cmi
+unicode_tables.cmo:
+unicode_tables.cmx:
 update.cmo: xferhint.cmi uutil.cmi ubase/util.cmi tree.cmi ubase/trace.cmi \
     system.cmi ubase/safelist.cmi remote.cmi props.cmi ubase/proplist.cmi \
     ubase/prefs.cmi pred.cmi path.cmi osx.cmi os.cmi name.cmi ubase/myMap.cmi \
-    lwt/lwt_unix.cmi lwt/lwt.cmi lock.cmi globals.cmi fspath.cmi fpcache.cmi \
-    fingerprint.cmi fileinfo.cmi common.cmi case.cmi update.cmi 
+    lwt/lwt_unix.cmi lwt/lwt.cmi lock.cmi globals.cmi fswatchold.cmi \
+    fspath.cmi fpcache.cmi fingerprint.cmi fileinfo.cmi common.cmi case.cmi \
+    update.cmi
 update.cmx: xferhint.cmx uutil.cmx ubase/util.cmx tree.cmx ubase/trace.cmx \
     system.cmx ubase/safelist.cmx remote.cmx props.cmx ubase/proplist.cmx \
     ubase/prefs.cmx pred.cmx path.cmx osx.cmx os.cmx name.cmx ubase/myMap.cmx \
-    lwt/lwt_unix.cmx lwt/lwt.cmx lock.cmx globals.cmx fspath.cmx fpcache.cmx \
-    fingerprint.cmx fileinfo.cmx common.cmx case.cmx update.cmi 
-uutil.cmo: ubase/util.cmi ubase/trace.cmi uutil.cmi 
-uutil.cmx: ubase/util.cmx ubase/trace.cmx uutil.cmi 
+    lwt/lwt_unix.cmx lwt/lwt.cmx lock.cmx globals.cmx fswatchold.cmx \
+    fspath.cmx fpcache.cmx fingerprint.cmx fileinfo.cmx common.cmx case.cmx \
+    update.cmi
+uutil.cmo: ubase/util.cmi ubase/trace.cmi ubase/projectInfo.cmo uutil.cmi
+uutil.cmx: ubase/util.cmx ubase/trace.cmx ubase/projectInfo.cmx uutil.cmi
 xferhint.cmo: ubase/util.cmi ubase/trace.cmi ubase/prefs.cmi path.cmi os.cmi \
-    fspath.cmi xferhint.cmi 
+    fspath.cmi xferhint.cmi
 xferhint.cmx: ubase/util.cmx ubase/trace.cmx ubase/prefs.cmx path.cmx os.cmx \
-    fspath.cmx xferhint.cmi 
-lwt/lwt.cmo: lwt/lwt.cmi 
-lwt/lwt.cmx: lwt/lwt.cmi 
-lwt/lwt_unix.cmo: lwt/lwt_unix.cmi 
-lwt/lwt_unix.cmx: lwt/lwt_unix.cmi 
-lwt/lwt_util.cmo: lwt/lwt.cmi lwt/lwt_util.cmi 
-lwt/lwt_util.cmx: lwt/lwt.cmx lwt/lwt_util.cmi 
-lwt/pqueue.cmo: lwt/pqueue.cmi 
-lwt/pqueue.cmx: lwt/pqueue.cmi 
-system/system_generic.cmo: 
-system/system_generic.cmx: 
-system/system_intf.cmo: 
-system/system_intf.cmx: 
-system/system_win.cmo: unicode.cmi system/system_generic.cmo ubase/rx.cmi 
-system/system_win.cmx: unicode.cmx system/system_generic.cmx ubase/rx.cmx 
-ubase/myMap.cmo: ubase/myMap.cmi 
-ubase/myMap.cmx: ubase/myMap.cmi 
+    fspath.cmx xferhint.cmi
+lwt/lwt.cmo: lwt/lwt.cmi
+lwt/lwt.cmx: lwt/lwt.cmi
+lwt/lwt_unix.cmo: lwt/lwt_unix.cmi
+lwt/lwt_unix.cmx: lwt/lwt_unix.cmi
+lwt/lwt_util.cmo: lwt/lwt.cmi lwt/lwt_util.cmi
+lwt/lwt_util.cmx: lwt/lwt.cmx lwt/lwt_util.cmi
+lwt/pqueue.cmo: lwt/pqueue.cmi
+lwt/pqueue.cmx: lwt/pqueue.cmi
+system/system_generic.cmo:
+system/system_generic.cmx:
+system/system_intf.cmo:
+system/system_intf.cmx:
+system/system_win.cmo: unicode.cmi system/system_generic.cmo ubase/rx.cmi
+system/system_win.cmx: unicode.cmx system/system_generic.cmx ubase/rx.cmx
+ubase/myMap.cmo: ubase/myMap.cmi
+ubase/myMap.cmx: ubase/myMap.cmi
 ubase/prefs.cmo: ubase/util.cmi ubase/uarg.cmi system.cmi ubase/safelist.cmi \
-    ubase/prefs.cmi 
+    ubase/prefs.cmi
 ubase/prefs.cmx: ubase/util.cmx ubase/uarg.cmx system.cmx ubase/safelist.cmx \
-    ubase/prefs.cmi 
-ubase/proplist.cmo: ubase/util.cmi ubase/proplist.cmi 
-ubase/proplist.cmx: ubase/util.cmx ubase/proplist.cmi 
-ubase/rx.cmo: ubase/rx.cmi 
-ubase/rx.cmx: ubase/rx.cmi 
-ubase/safelist.cmo: ubase/safelist.cmi 
-ubase/safelist.cmx: ubase/safelist.cmi 
+    ubase/prefs.cmi
+ubase/projectInfo.cmo:
+ubase/projectInfo.cmx:
+ubase/proplist.cmo: ubase/util.cmi ubase/proplist.cmi
+ubase/proplist.cmx: ubase/util.cmx ubase/proplist.cmi
+ubase/rx.cmo: ubase/rx.cmi
+ubase/rx.cmx: ubase/rx.cmi
+ubase/safelist.cmo: ubase/safelist.cmi
+ubase/safelist.cmx: ubase/safelist.cmi
 ubase/trace.cmo: ubase/util.cmi system.cmi ubase/safelist.cmi ubase/prefs.cmi \
-    ubase/trace.cmi 
+    ubase/trace.cmi
 ubase/trace.cmx: ubase/util.cmx system.cmx ubase/safelist.cmx ubase/prefs.cmx \
-    ubase/trace.cmi 
-ubase/uarg.cmo: ubase/util.cmi system.cmi ubase/safelist.cmi ubase/uarg.cmi 
-ubase/uarg.cmx: ubase/util.cmx system.cmx ubase/safelist.cmx ubase/uarg.cmi 
-ubase/uprintf.cmo: ubase/uprintf.cmi 
-ubase/uprintf.cmx: ubase/uprintf.cmi 
+    ubase/trace.cmi
+ubase/uarg.cmo: ubase/util.cmi system.cmi ubase/safelist.cmi ubase/uarg.cmi
+ubase/uarg.cmx: ubase/util.cmx system.cmx ubase/safelist.cmx ubase/uarg.cmi
+ubase/uprintf.cmo: ubase/uprintf.cmi
+ubase/uprintf.cmx: ubase/uprintf.cmi
 ubase/util.cmo: ubase/uprintf.cmi system.cmi ubase/safelist.cmi \
-    ubase/util.cmi 
+    ubase/util.cmi
 ubase/util.cmx: ubase/uprintf.cmx system.cmx ubase/safelist.cmx \
-    ubase/util.cmi 
-lwt/lwt.cmi: 
-lwt/lwt_unix.cmi: lwt/lwt.cmi 
-lwt/lwt_util.cmi: lwt/lwt.cmi 
-lwt/pqueue.cmi: 
-ubase/myMap.cmi: 
-ubase/prefs.cmi: ubase/util.cmi system.cmi 
-ubase/proplist.cmi: 
-ubase/rx.cmi: 
-ubase/safelist.cmi: 
-ubase/trace.cmi: ubase/prefs.cmi 
-ubase/uarg.cmi: 
-ubase/uprintf.cmi: 
-ubase/util.cmi: system.cmi 
-lwt/example/editor.cmo: lwt/lwt_unix.cmi 
-lwt/example/editor.cmx: lwt/lwt_unix.cmx 
-lwt/example/relay.cmo: lwt/lwt_unix.cmi lwt/lwt.cmi 
-lwt/example/relay.cmx: lwt/lwt_unix.cmx lwt/lwt.cmx 
-lwt/generic/lwt_unix_impl.cmo: lwt/pqueue.cmi lwt/lwt.cmi 
-lwt/generic/lwt_unix_impl.cmx: lwt/pqueue.cmx lwt/lwt.cmx 
-lwt/win/lwt_unix_impl.cmo: lwt/pqueue.cmi lwt/lwt.cmi 
-lwt/win/lwt_unix_impl.cmx: lwt/pqueue.cmx lwt/lwt.cmx 
-system/generic/system_impl.cmo: system/system_generic.cmo 
-system/generic/system_impl.cmx: system/system_generic.cmx 
-system/win/system_impl.cmo: system/system_win.cmo system/system_generic.cmo 
-system/win/system_impl.cmx: system/system_win.cmx system/system_generic.cmx 
+    ubase/util.cmi
+lwt/lwt.cmi:
+lwt/lwt_unix.cmi: lwt/lwt.cmi
+lwt/lwt_util.cmi: lwt/lwt.cmi
+lwt/pqueue.cmi:
+ubase/myMap.cmi:
+ubase/prefs.cmi: ubase/util.cmi system.cmi
+ubase/proplist.cmi:
+ubase/rx.cmi:
+ubase/safelist.cmi:
+ubase/trace.cmi: ubase/prefs.cmi
+ubase/uarg.cmi:
+ubase/uprintf.cmi:
+ubase/util.cmi: system.cmi
+lwt/example/editor.cmo: lwt/lwt_unix.cmi
+lwt/example/editor.cmx: lwt/lwt_unix.cmx
+lwt/example/relay.cmo: lwt/lwt_unix.cmi lwt/lwt.cmi
+lwt/example/relay.cmx: lwt/lwt_unix.cmx lwt/lwt.cmx
+lwt/generic/lwt_unix_impl.cmo: lwt/pqueue.cmi lwt/lwt.cmi
+lwt/generic/lwt_unix_impl.cmx: lwt/pqueue.cmx lwt/lwt.cmx
+lwt/win/lwt_unix_impl.cmo: lwt/pqueue.cmi lwt/lwt.cmi
+lwt/win/lwt_unix_impl.cmx: lwt/pqueue.cmx lwt/lwt.cmx
+system/generic/system_impl.cmo: system/system_generic.cmo
+system/generic/system_impl.cmx: system/system_generic.cmx
+system/win/system_impl.cmo: system/system_win.cmo system/system_generic.cmo
+system/win/system_impl.cmx: system/system_win.cmx system/system_generic.cmx

Modified: trunk/src/Makefile.OCaml
===================================================================
--- trunk/src/Makefile.OCaml	2012-08-07 20:06:46 UTC (rev 503)
+++ trunk/src/Makefile.OCaml	2012-08-09 14:06:21 UTC (rev 504)
@@ -226,7 +226,7 @@
           abort.cmo osx.cmo external.cmo \
           props.cmo fileinfo.cmo os.cmo lock.cmo clroot.cmo common.cmo \
           tree.cmo checksum.cmo terminal.cmo \
-          transfer.cmo xferhint.cmo remote.cmo globals.cmo \
+          transfer.cmo xferhint.cmo remote.cmo globals.cmo fswatchold.cmo \
           fpcache.cmo update.cmo copy.cmo stasher.cmo \
 	  files.cmo sortri.cmo recon.cmo transport.cmo \
           strings.cmo uicommon.cmo uitext.cmo test.cmo

Modified: trunk/src/RECENTNEWS
===================================================================
--- trunk/src/RECENTNEWS	2012-08-07 20:06:46 UTC (rev 503)
+++ trunk/src/RECENTNEWS	2012-08-09 14:06:21 UTC (rev 504)
@@ -1,3 +1,15 @@
+CHANGES FROM VERSION 2.46.-1
+
+* Bumped version number: incompatible protocol changes
+* Improvements to the file watching functionality:
+  - retries paths with failures using an exponential backoff algorithm
+  - the information returned by the file watchers are used
+    independently for each replica; thus, when only one replica has
+    changes, Unison will only rescan this replica
+  - when available, used by the graphical UIs to speed up rescanning
+    (can be disabled by setting the new 'watch' preference to false)
+
+-------------------------------
 CHANGES FROM VERSION 2.45.15
 
 * transfer.ml: updated debugging code; in particular, turns an

Added: trunk/src/fswatchold.ml
===================================================================
--- trunk/src/fswatchold.ml	                        (rev 0)
+++ trunk/src/fswatchold.ml	2012-08-09 14:06:21 UTC (rev 504)
@@ -0,0 +1,167 @@
+(* Unison file synchronizer: src/fswatcherold.ml *)
+(* Copyright 1999-2012, Benjamin C. Pierce 
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*)
+
+(* FIX: we should check that the child process has not died and
+   restart it if so... *)
+
+(* FIX: the names of the paths being watched should get included
+   in the name of the watcher's state file *)
+
+let useWatcher =
+  Prefs.createBool "watch" true
+    "!when set, use a file watcher process to detect changes"
+    "Unison uses a file watcher process, when available, to detect filesystem \
+     changes; this is used to speed up update detection, and for continuous \
+     synchronization (\\verb|-repeat watch| preference. Setting this flag to \
+     false disable the use of this process." 
+
+let debug = Util.debug "fswatch"
+
+let watchinterval = 5
+
+let watcherTemp archHash n = Os.fileInUnisonDir (n ^ archHash)
+
+let watchercmd archHash root =
+  let fsmonfile =
+    Filename.concat (Filename.dirname Sys.executable_name) "fsmonitor.py" in
+  if not (Sys.file_exists fsmonfile) then
+    None
+  else begin
+    (* FIX: is the quoting of --follow parameters going to work on Win32?
+         (2/2012: tried adding Uutil.quotes -- maybe this is OK now?) *)
+    (* FIX -- need to find the program using watcherosx preference *)
+    let changefile = watcherTemp archHash "changes" in
+    let statefile = watcherTemp archHash "state" in
+    let paths = Safelist.map Path.toString (Prefs.read Globals.paths) in
+    let followpaths = Pred.extern Path.followPred in
+    let follow = Safelist.map
+                   (fun s -> "--follow '" ^ Uutil.quotes s ^ "'")
+                   followpaths in
+  (* BCP (per Josh Berdine, 5/2012): changed startup command from this...
+       let cmd = Printf.sprintf "fsmonitor.py %s --outfile %s --statefile %s %s %s\n"
+     ... to this: *)
+    let cmd = Printf.sprintf "python \"%s\" \"%s\" --outfile \"%s\" --statefile \"%s\" %s %s\n"
+                fsmonfile
+                root
+                (System.fspathToPrintString changefile)
+                (System.fspathToPrintString statefile)
+                (String.concat " " follow)
+                (String.concat " " paths) in
+    debug (fun() -> Util.msg "watchercmd = %s\n" cmd);
+    Some (changefile,cmd)
+  end
+
+module StringSet= Set.Make (String)
+module RootMap = Map.Make (String)
+type watcherinfo = {file: System.fspath;
+                    mutable ch:Pervasives.in_channel option;
+                    chars: Buffer.t;
+                    mutable lines: string list}
+let watchers : watcherinfo RootMap.t ref = ref RootMap.empty
+
+let trim_duplicates l =
+  let rec loop l = match l with
+    [] -> l
+  | [s] -> l
+  | s1::s2::rest ->
+      if Util.startswith s1 s2 || Util.startswith s2 s1 then
+        loop (s2::rest)
+      else
+        s1 :: (loop (s2::rest)) in
+  loop (Safelist.sort String.compare l)  
+
+let readAvailableLinesFromWatcher wi =
+  let ch = match wi.ch with Some(c) -> c | None -> assert false in 
+  let rec loop () =
+    match try Some(input_char ch) with End_of_file -> None with
+      None ->
+        ()
+    | Some(c) ->
+        if c = '\n' then begin
+          wi.lines <- Buffer.contents wi.chars :: wi.lines;
+          Buffer.clear wi.chars;
+          loop ()
+        end else begin
+          Buffer.add_char wi.chars c;
+          loop ()
+        end in
+    loop ()
+
+let readChanges wi =
+  if wi.ch = None then
+    (* Watcher channel not built yet *)
+    if System.file_exists wi.file then begin
+      (* Build it and go *)
+      let c = System.open_in_bin wi.file in
+      wi.ch <- Some c;
+      readAvailableLinesFromWatcher wi;
+    end else begin
+      (* Wait for change file to be built *)
+      debug (fun() -> Util.msg
+        "Waiting for change file %s\n"
+        (System.fspathToPrintString wi.file))
+    end
+  else
+    (* Watcher running and channel built: go ahead and read *)
+    readAvailableLinesFromWatcher wi
+
+let getChanges archHash =
+    let wi = RootMap.find archHash !watchers in
+    readChanges wi;
+    let res = wi.lines in
+    wi.lines <- [];
+    List.map Path.fromString (trim_duplicates res)
+
+let start archHash fspath =
+  if not (Prefs.read useWatcher) then
+    false
+  else if not (RootMap.mem archHash !watchers) then begin
+    (* Watcher process not running *)
+    match watchercmd archHash (Fspath.toString fspath) with
+      Some (changefile,cmd) ->
+        debug (fun() -> Util.msg
+                 "Starting watcher on fspath %s\n"
+                 (Fspath.toDebugString fspath));
+        let _ = System.open_process_out cmd in
+        let wi = {file = changefile; ch = None;
+                  lines = []; chars = Buffer.create 80} in
+        watchers := RootMap.add archHash wi !watchers;
+        true
+    | None ->
+        false
+  end else begin
+    (* If already running, discard all pending changes *)
+    ignore (getChanges archHash);
+    true
+  end
+
+let wait archHash =
+  if not (RootMap.mem archHash !watchers) then
+    raise (Util.Fatal "No file monitoring helper program found")
+  else begin
+    let wi = RootMap.find archHash !watchers in
+    let rec loop () =
+      readChanges wi;
+      if wi.lines = [] then begin
+        debug (fun() -> Util.msg "Sleeping for %d seconds...\n" watchinterval);
+        Lwt.bind (Lwt_unix.sleep (float watchinterval)) (fun () ->
+        loop ())
+      end else
+        Lwt.return ()
+    in
+    loop ()
+  end

Added: trunk/src/fswatchold.mli
===================================================================
--- trunk/src/fswatchold.mli	                        (rev 0)
+++ trunk/src/fswatchold.mli	2012-08-09 14:06:21 UTC (rev 504)
@@ -0,0 +1,4 @@
+
+val start : string -> Fspath.t -> bool
+val getChanges : string -> Path.t list
+val wait : string -> unit Lwt.t

Modified: trunk/src/mkProjectInfo.ml
===================================================================
--- trunk/src/mkProjectInfo.ml	2012-08-07 20:06:46 UTC (rev 503)
+++ trunk/src/mkProjectInfo.ml	2012-08-09 14:06:21 UTC (rev 504)
@@ -5,8 +5,8 @@
 
 let projectName = "unison"
 let majorVersion = 2
-let minorVersion = 45
-let pointVersionOrigin = 487 (* Revision that corresponds to point version 0 *)
+let minorVersion = 46
+let pointVersionOrigin = 504 (* Revision that corresponds to point version 0 *)
 
 (* Documentation:
    This is a program to construct a version of the form Major.Minor.Point,
@@ -78,3 +78,4 @@
 
 
 
+

Modified: trunk/src/os.ml
===================================================================
--- trunk/src/os.ml	2012-08-07 20:06:46 UTC (rev 503)
+++ trunk/src/os.ml	2012-08-09 14:06:21 UTC (rev 504)
@@ -50,6 +50,10 @@
     if s = "" then tempFileSuffixFixed
     else "." ^ s ^ tempFileSuffixFixed
 
+let isTempFile file =
+  Util.endswith file tempFileSuffixFixed &&
+  Util.startswith file tempFilePrefix
+
 (*****************************************************************************)
 (*                      QUERYING THE FILESYSTEM                              *)
 (*****************************************************************************)
@@ -124,10 +128,7 @@
 (*             removeBackupIfUnwanted fspath newPath; *)
 (*             false *)
 (*           end  *)
-       else if
-         Util.endswith file tempFileSuffixFixed &&
-         Util.startswith file tempFilePrefix
-       then begin
+       else if isTempFile file then begin
          if Util.endswith file !tempFileSuffix then begin
            let p = Path.child path filename in
            let i = Fileinfo.get false fspath p in

Modified: trunk/src/os.mli
===================================================================
--- trunk/src/os.mli	2012-08-07 20:06:46 UTC (rev 503)
+++ trunk/src/os.mli	2012-08-09 14:06:21 UTC (rev 504)
@@ -5,6 +5,7 @@
 
 val tempPath : ?fresh:bool -> Fspath.t -> Path.local -> Path.local
 val tempFilePrefix : string
+val isTempFile : string -> bool
 val includeInTempNames : string -> unit
 
 val exists : Fspath.t -> Path.local -> bool

Modified: trunk/src/test.ml
===================================================================
--- trunk/src/test.ml	2012-08-07 20:06:46 UTC (rev 503)
+++ trunk/src/test.ml	2012-08-09 14:06:21 UTC (rev 504)
@@ -214,7 +214,7 @@
 
 let sync ?(verbose=false) () = 
   let (reconItemList, _, _) =
-    Recon.reconcileAll (Update.findUpdates()) in
+    Recon.reconcileAll (Update.findUpdates None) in
   if verbose then begin
     Util.msg "Sync result:\n";
     displayRis reconItemList

Modified: trunk/src/uigtk2.ml
===================================================================
--- trunk/src/uigtk2.ml	2012-08-07 20:06:46 UTC (rev 503)
+++ trunk/src/uigtk2.ml	2012-08-09 14:06:21 UTC (rev 504)
@@ -125,6 +125,7 @@
                    mutable bytesToTransfer : Uutil.Filesize.t;
                    mutable whatHappened : (Util.confirmation * string option) option}
 let theState = ref [||]
+let unsynchronizedPaths = ref None
 
 module IntSet = Set.Make (struct type t = int let compare = compare end)
 
@@ -3368,7 +3369,7 @@
     let findUpdates () =
       let t = Trace.startTimer "Checking for updates" in
       Trace.status "Looking for changes";
-      let updates = Update.findUpdates () in
+      let updates = Update.findUpdates ~wantWatcher:() !unsynchronizedPaths in
       Trace.showTimer t;
       updates in
     let reconcile updates =
@@ -3396,6 +3397,8 @@
                          bytesToTransfer = Uutil.Filesize.zero;
                          whatHappened = None })
             reconItemList);
+    unsynchronizedPaths :=
+      Some (List.map (fun ri -> ri.path1) reconItemList, []);
     current := IntSet.empty;
     displayMain();
     progressBarPulse := false; sync_action := None; displayGlobalProgress 0.;
@@ -3645,6 +3648,10 @@
         if skippedCount = 0 then [] else
         [Printf.sprintf "%d skipped" skippedCount]
       in
+      unsynchronizedPaths :=
+        Some (List.map (fun (si, _, _) -> si.ri.path1)
+                (failureList @ partialList @ skippedList),
+              []);
       Trace.status
         (Printf.sprintf "Synchronization complete         %s"
            (String.concat ", " (failures @ partials @ skipped)));
@@ -3891,6 +3898,7 @@
   let loadProfile p reload =
     debug (fun()-> Util.msg "Loading profile %s..." p);
     Trace.status "Loading profile";
+    unsynchronizedPaths := None;
     Uicommon.initPrefs p
       (fun () -> if not reload then displayWaitMessage ())
       getFirstRoot getSecondRoot termInteract;
@@ -4123,9 +4131,13 @@
            let confirmBigDeletes = Prefs.read Globals.confirmBigDeletes in
            Prefs.set Globals.paths failedpaths;
            Prefs.set Globals.confirmBigDeletes false;
+           (* Modifying global paths does not play well with filesystem
+              monitoring, so we disable it. *)
+           unsynchronizedPaths := None;
            detectCmd();
            Prefs.set Globals.paths paths;
-           Prefs.set Globals.confirmBigDeletes confirmBigDeletes)
+           Prefs.set Globals.confirmBigDeletes confirmBigDeletes;
+           unsynchronizedPaths := None)
        "Re_check Unsynchronized Items");
 
   ignore (fileMenu#add_separator ());

Modified: trunk/src/uimacbridge.ml
===================================================================
--- trunk/src/uimacbridge.ml	2012-08-07 20:06:46 UTC (rev 503)
+++ trunk/src/uimacbridge.ml	2012-08-09 14:06:21 UTC (rev 504)
@@ -17,6 +17,7 @@
                    mutable whatHappened : Util.confirmation option;
                    mutable statusMessage : string option };;
 let theState = ref [| |];;
+let unsynchronizedPaths = ref None;;
 
 let unisonDirectory() = System.fspathToPrintString Os.unisonDir
 ;;
@@ -126,6 +127,7 @@
   (* Load the profile and command-line arguments *)
   (* Restore prefs to their default values, if necessary *)
   if not !firstTime then Prefs.resetToDefaults();
+  unsynchronizedPaths := None;
 
   (* Tell the preferences module the name of the profile *)
   Prefs.profileName := Some(profileName);
@@ -232,7 +234,7 @@
   let t = Trace.startTimer "Checking for updates" in
   let findUpdates () =
     Trace.status "Looking for changes";
-    let updates = Update.findUpdates () in
+    let updates = Update.findUpdates ~wantWatcher:() !unsynchronizedPaths in
     Trace.showTimer t;
     updates in
   let reconcile updates = Recon.reconcileAll updates in
@@ -252,6 +254,8 @@
                    whatHappened = None; statusMessage = None })
       reconItemList in
   theState := Array.of_list stateItemList;
+  unsynchronizedPaths :=
+    Some (List.map (fun ri -> ri.path1) reconItemList, []);
   if dangerousPaths <> [] then begin
     Prefs.set Globals.batch false;
     Util.warn (Uicommon.dangerousPathMsg dangerousPaths)
@@ -388,25 +392,72 @@
     Update.commitUpdates();
     Trace.showTimer t;
 
+    let failureList =
+      Array.fold_right
+        (fun si l ->
+           match si.whatHappened with
+             Some (Util.Failed err) ->
+               (si, [err], "transport failure") :: l
+           | _ ->
+               l)
+        !theState []
+    in
+    let failureCount = List.length failureList in
     let failures =
-      let count =
-        Array.fold_left
-          (fun l si ->
-             l + (match si.whatHappened with Some(Util.Failed(_)) -> 1 | _ -> 0))
-          0 !theState in
-      if count = 0 then "" else
-        Printf.sprintf "%d failure%s" count (if count=1 then "" else "s") in
+      if failureCount = 0 then [] else
+      [Printf.sprintf "%d failure%s"
+         failureCount (if failureCount = 1 then "" else "s")]
+    in
+    let partialList =
+      Array.fold_right
+        (fun si l ->
+           match si.whatHappened with
+             Some Util.Succeeded
+             when partiallyProblematic si.ri &&
+                  not (problematic si.ri) ->
+               let errs =
+                 match si.ri.replicas with
+                   Different diff -> diff.errors1 @ diff.errors2
+                 | _              -> assert false
+               in
+               (si, errs,
+                "partial transfer (errors during update detection)") :: l
+           | _ ->
+               l)
+        !theState []
+    in
+    let partialCount = List.length partialList in
+    let partials =
+      if partialCount = 0 then [] else
+      [Printf.sprintf "%d partially transferred" partialCount]
+    in
+    let skippedList =
+      Array.fold_right
+        (fun si l ->
+           match si.ri.replicas with
+             Problem err ->
+               (si, [err], "error during update detection") :: l
+           | Different diff when diff.direction = Conflict ->
+               (si, [],
+                if diff.default_direction = Conflict then
+                  "conflict"
+                else "skipped") :: l
+           | _ ->
+               l)
+        !theState []
+    in
+    let skippedCount = List.length skippedList in
     let skipped =
-      let count =
-        Array.fold_left
-          (fun l si ->
-             l + (if problematic si.ri then 1 else 0))
-          0 !theState in
-      if count = 0 then "" else
-        Printf.sprintf "%d skipped" count in
+      if skippedCount = 0 then [] else
+      [Printf.sprintf "%d skipped" skippedCount]
+    in
+    unsynchronizedPaths :=
+      Some (List.map (fun (si, _, _) -> si.ri.path1)
+              (failureList @ partialList @ skippedList),
+            []);
     Trace.status
-      (Printf.sprintf "Synchronization complete         %s%s%s"
-         failures (if failures=""||skipped="" then "" else ", ") skipped);
+      (Printf.sprintf "Synchronization complete         %s"
+         (String.concat ", " (failures @ partials @ skipped)));
   end;;
 Callback.register "unisonSynchronize" unisonSynchronize;;
 

Modified: trunk/src/uimacbridgenew.ml
===================================================================
--- trunk/src/uimacbridgenew.ml	2012-08-07 20:06:46 UTC (rev 503)
+++ trunk/src/uimacbridgenew.ml	2012-08-09 14:06:21 UTC (rev 504)
@@ -18,6 +18,7 @@
                    mutable whatHappened : Util.confirmation option;
                    mutable statusMessage : string option };;
 let theState = ref [| |];;
+let unsynchronizedPaths = ref None;;
 
 let unisonDirectory() = System.fspathToString Os.unisonDir
 ;;
@@ -230,6 +231,7 @@
   (* Load the profile and command-line arguments *)
   (* Restore prefs to their default values, if necessary *)
   if not !firstTime then Prefs.resetToDefaults();
+  unsynchronizedPaths := None;
 
   if profileName <> "" then begin
     (* Tell the preferences module the name of the profile *)
@@ -356,7 +358,7 @@
   let t = Trace.startTimer "Checking for updates" in
   let findUpdates () =
     Trace.status "Looking for changes";
-    let updates = Update.findUpdates () in
+    let updates = Update.findUpdates ~wantWatcher:() !unsynchronizedPaths in
     Trace.showTimer t;
     updates in
   let reconcile updates = Recon.reconcileAll updates in
@@ -381,6 +383,8 @@
                    whatHappened = None; statusMessage = None })
       reconItemList in
   theState := Array.of_list stateItemList;
+  unsynchronizedPaths :=
+    Some (List.map (fun ri -> ri.path1) reconItemList, []);
   if dangerousPaths <> [] then begin
     Prefs.set Globals.batch false;
     Util.warn (Uicommon.dangerousPathMsg dangerousPaths)
@@ -615,36 +619,69 @@
     Trace.showTimer t;
     commitUpdates ();
 
+    let failureList =
+      Array.fold_right
+        (fun si l ->
+           match si.whatHappened with
+             Some (Util.Failed err) ->
+               (si, [err], "transport failure") :: l
+           | _ ->
+               l)
+        !theState []
+    in
+    let failureCount = List.length failureList in
     let failures =
-      let count =
-        Array.fold_left
-          (fun l si ->
-             l + (match si.whatHappened with Some(Util.Failed(_)) -> 1 | _ -> 0))
-          0 !theState in
-      if count = 0 then [] else
-        [Printf.sprintf "%d failure%s" count (if count=1 then "" else "s")] in
+      if failureCount = 0 then [] else
+      [Printf.sprintf "%d failure%s"
+         failureCount (if failureCount = 1 then "" else "s")]
+    in
+    let partialList =
+      Array.fold_right
+        (fun si l ->
+           match si.whatHappened with
+             Some Util.Succeeded
+             when partiallyProblematic si.ri &&
+                  not (problematic si.ri) ->
+               let errs =
+                 match si.ri.replicas with
+                   Different diff -> diff.errors1 @ diff.errors2
+                 | _              -> assert false
+               in
+               (si, errs,
+                "partial transfer (errors during update detection)") :: l
+           | _ ->
+               l)
+        !theState []
+    in
+    let partialCount = List.length partialList in
     let partials =
-      let count =
-        Array.fold_left
-          (fun l si ->
-             l + match si.whatHappened with
-                   Some Util.Succeeded
-                   when partiallyProblematic si.ri &&
-                        not (problematic si.ri) ->
-                     1
-                 | _ ->
-                     0)
-          0 !theState in
-      if count = 0 then [] else
-        [Printf.sprintf "%d partially transferred" count] in
+      if partialCount = 0 then [] else
+      [Printf.sprintf "%d partially transferred" partialCount]
+    in
+    let skippedList =
+      Array.fold_right
+        (fun si l ->
+           match si.ri.replicas with
+             Problem err ->
+               (si, [err], "error during update detection") :: l
+           | Different diff when diff.direction = Conflict ->
+               (si, [],
+                if diff.default_direction = Conflict then
+                  "conflict"
+                else "skipped") :: l
+           | _ ->
+               l)
+        !theState []
+    in
+    let skippedCount = List.length skippedList in
     let skipped =
-      let count =
-        Array.fold_left
-          (fun l si ->
-             l + (if problematic si.ri then 1 else 0))
-          0 !theState in
-      if count = 0 then [] else
-        [Printf.sprintf "%d skipped" count] in
+      if skippedCount = 0 then [] else
+      [Printf.sprintf "%d skipped" skippedCount]
+    in
+    unsynchronizedPaths :=
+      Some (List.map (fun (si, _, _) -> si.ri.path1)
+              (failureList @ partialList @ skippedList),
+            []);
     Trace.status
       (Printf.sprintf "Synchronization complete         %s"
          (String.concat ", " (failures @ partials @ skipped)));

Modified: trunk/src/uitext.ml
===================================================================
--- trunk/src/uitext.ml	2012-08-07 20:06:46 UTC (rev 503)
+++ trunk/src/uitext.ml	2012-08-09 14:06:21 UTC (rev 504)
@@ -654,7 +654,7 @@
     end 
   end
 
-let synchronizeOnce () =
+let synchronizeOnce ?wantWatcher ?skipRecentFiles pathsOpt =
   let showStatus path =
     if path = "" then Util.set_infos "" else
     let max_len = 70 in
@@ -675,7 +675,7 @@
   debug (fun() -> Util.msg "temp: Globals.paths = %s\n"
            (String.concat " "
               (Safelist.map Path.toString (Prefs.read Globals.paths))));
-  let updates = Update.findUpdates() in
+  let updates = Update.findUpdates ?wantWatcher pathsOpt in
 
   Uutil.setUpdateStatusPrinter None;
   Util.set_infos "";
@@ -698,192 +698,79 @@
     (exitStatus, failedPaths)
   end
 
-let originalValueOfPathsPreference = ref [] 
-
 (* ----------------- Filesystem watching mode ---------------- *)
 
-(* FIX: we should check that the child process has not died and
-   restart it if so... *)
+let watchinterval = 1.    (* Minimal interval between two synchronizations *)
+let retrydelay = 5.       (* Minimal delay to retry failed paths *)
+let maxdelay = 30. *. 60. (* Maximal delay to retry failed paths *)
 
-(* FIX: the names of the paths being watched should get included
-   in the name of the watcher's state file *)
+module PathMap = Map.Make (Path)
 
-let watchinterval = 5
-
-let watcherTemp r n =
-  let s = n ^ (Update.archiveHash (Fspath.canonize (Some r))) in
-  Os.fileInUnisonDir s
-
-let watchercmd r =
-  (* FIX: is the quoting of --follow parameters going to work on Win32?
-       (2/2012: tried adding Uutil.quotes -- maybe this is OK now?) *)
-  (* FIX -- need to find the program using watcherosx preference *)
-  let root = Fspath.toString (snd r) in
-  let changefile = watcherTemp root "changes" in
-  let statefile = watcherTemp root "state" in
-  let paths = Safelist.map Path.toString !originalValueOfPathsPreference in
-  let followpaths = Pred.extern Path.followPred in
-  let follow = Safelist.map
-                 (fun s -> "--follow '" ^ Uutil.quotes s ^ "'")
-                 followpaths in
-(* BCP (per Josh Berdine, 5/2012): changed startup command from this...
-     let cmd = Printf.sprintf "fsmonitor.py %s --outfile %s --statefile %s %s %s\n"
-   ... to this: *)
-  let fsmonfile = Filename.concat (Filename.dirname Sys.executable_name) "fsmonitor.py" in
-  let cmd = Printf.sprintf "python \"%s\" \"%s\" --outfile \"%s\" --statefile \"%s\" %s %s\n"
-              fsmonfile
-              root
-              (System.fspathToPrintString changefile)
-              (System.fspathToPrintString statefile)
-              (String.concat " " follow)
-              (String.concat " " paths) in
-  debug (fun() -> Util.msg "watchercmd = %s\n" cmd);
-  (changefile,cmd)
-
-module RootMap = Map.Make (struct type t = Common.root
-                                  let compare = Pervasives.compare
-                           end)
-(* Using string concatenation to accumulate characters is
-   a bit inefficient, but it's not clear how much it matters in the
-   grand scheme of things.  Current experience suggests that this
-   implementation performs well enough. *)
-type watcherinfo = {file: System.fspath;
-                    ch:Pervasives.in_channel option ref;
-                    chars: string ref;
-                    lines: string list ref}
-let watchers : watcherinfo RootMap.t ref = ref RootMap.empty 
-
-let trim_duplicates l =
-  let rec loop l = match l with
-    [] -> l
-  | [s] -> l
-  | s1::s2::rest ->
-      if Util.startswith s1 s2 || Util.startswith s2 s1 then
-        loop (s2::rest)
-      else
-        s1 :: (loop (s2::rest)) in
-  loop (Safelist.sort String.compare l)  
-
-let getAvailableLinesFromWatcher wi =
-  let ch = match !(wi.ch) with Some(c) -> c | None -> assert false in 
-  let rec loop () =
-    match try Some(input_char ch) with End_of_file -> None with
-      None ->
-        let res = !(wi.lines) in
-        wi.lines := [];
-        trim_duplicates res
-    | Some(c) ->
-        if c = '\n' then begin
-          wi.lines := !(wi.chars) :: !(wi.lines);
-          wi.chars := "";
-          loop ()
-        end else begin
-          wi.chars := (!(wi.chars)) ^ (String.make 1 c);
-          loop ()
-        end in
-    loop ()
-
-let suckOnWatcherFileLocal r =
-  Util.convertUnixErrorsToFatal
-    "Reading changes from watcher process "
-    (fun () ->
-       (* Make sure there's a watcher running *)
-       try
-         let wi = RootMap.find r !watchers in
-         if !(wi.ch) = None then
-           (* Watcher channel not built yet *)
-           if System.file_exists wi.file then begin
-             (* Build it and go *)
-             let c = System.open_in_bin wi.file in
-             wi.ch := Some(c);
-             getAvailableLinesFromWatcher wi
-           end else begin
-             (* Wait for change file to be built *)
-             debug (fun() -> Util.msg
-               "Waiting for change file %s\n"
-               (System.fspathToPrintString wi.file));
-             []
-           end
-         else begin
-           (* Watcher running and channel built: go ahead and read *)
-           getAvailableLinesFromWatcher wi
-         end 
-       with Not_found -> begin
-         (* Watcher process not running *)
-         let (changefile,cmd) = watchercmd r in
-         debug (fun() -> Util.msg
-                  "Starting watcher on root %s\n" (Common.root2string r));
-         let _ = System.open_process_out cmd in
-         let wi = {file = changefile; ch = ref None;
-                   lines = ref []; chars = ref ""} in
-         watchers := RootMap.add r wi !watchers;
-         []
-      end)
-
-let suckOnWatcherFileRoot: Common.root -> Common.root -> (string list) Lwt.t =
+let waitForChangesRoot: Common.root -> unit -> unit Lwt.t =
   Remote.registerRootCmd
-    "suckOnWatcherFile"
-    (fun (fspath, r) ->
-      Lwt.return (suckOnWatcherFileLocal r))
+    "waitForChanges"
+    (fun (fspath, _) -> Fswatchold.wait (Update.archiveHash fspath))
 
-let suckOnWatcherFiles () =
-  Safelist.concat
-    (Lwt_unix.run (
-      Globals.allRootsMap (fun r -> suckOnWatcherFileRoot r r)))
+let waitForChanges t =
+  let dt = t -. Unix.gettimeofday () in
+  if dt > 0. then begin
+    let timeout = if dt <= maxdelay then [Lwt_unix.sleep dt] else [] in
+    Lwt_unix.run
+      (Globals.allRootsMap (fun r -> Lwt.return (waitForChangesRoot r ()))
+         >>= fun l ->
+       Lwt.choose (timeout @ l))
+  end
 
-let shouldNotIgnore p =
-  let rec test prefix rest =
-    if Globals.shouldIgnore prefix then
-      false
-    else match (Path.deconstruct rest) with
-        None -> true
-      | Some(n,rest') ->
-          test (Path.child prefix n) rest'
-    in 
-  test Path.empty (Path.fromString p) 
-
 let synchronizePathsFromFilesystemWatcher () =
-  (* Make sure the confirmbigdeletes preference is turned off.  If it's on,
-     then all deletions will fail because every deletion will count as
-     a "big deletion"! *)
-  Prefs.set Globals.confirmBigDeletes false;
+  let rec loop isStart delayInfo =
+    let t = Unix.gettimeofday () in
+    let (delayedPaths, readyPaths) =
+      PathMap.fold
+        (fun p (t', _) (delayed, ready) ->
+           if t' <= t then (delayed, p :: ready) else (p :: delayed, ready))
+        delayInfo ([], [])
+    in
+    let (exitStatus, failedPaths) =
+      synchronizeOnce ~wantWatcher:() ~skipRecentFiles:()
+        (if isStart then None else Some (readyPaths, delayedPaths))
+    in
+    (* After a failure, we retry at once, then use an exponential backoff *)
+    let delayInfo =
+      Safelist.fold_left
+        (fun newDelayInfo p ->
+           PathMap.add p
+             (try
+                let (t', d) = PathMap.find p delayInfo in
+                if t' > t then (t', d) else
+                let d = max retrydelay (min maxdelay (2. *. d)) in
+                (t +. d, d)
+              with Not_found ->
+                (t, 0.))
+             newDelayInfo)
+        PathMap.empty
+        (Safelist.append delayedPaths failedPaths)
+    in
+    Lwt_unix.run (Lwt_unix.sleep watchinterval);
+    let nextTime =
+      PathMap.fold (fun _ (t, d) t' -> min t t') delayInfo 1e20 in
+    waitForChanges nextTime;
+    loop false delayInfo
+  in
+  loop true PathMap.empty
 
-  let rec loop failedPaths = 
-    let newpathsraw = suckOnWatcherFiles () in
-    debug (fun () -> Util.msg
-      "Changed paths: %s\n" (String.concat " " newpathsraw));
-    let newpaths = Safelist.filter shouldNotIgnore newpathsraw in
-    if newpaths <> [] then
-      display (Printf.sprintf "Changed paths:  %s%s\n"
-                 (if newpaths=[] then "" else "\n  ")
-                 (String.concat "\n  " newpaths));
-    let p = failedPaths @ (Safelist.map Path.fromString newpaths) in
-    if p <> [] then begin
-      Prefs.set Globals.paths p;
-      let (exitStatus,newFailedPaths) = synchronizeOnce() in 
-      debug (fun() -> Util.msg "Sleeping for %d seconds...\n" watchinterval);
-      Unix.sleep watchinterval;
-      loop newFailedPaths 
-    end else begin
-      debug (fun() -> Util.msg "Nothing changed: sleeping for %d seconds...\n"
-               watchinterval);
-      Unix.sleep watchinterval;
-      loop []
-    end in
-  loop []
-
 (* ----------------- Repetition ---------------- *)
 
-let synchronizeUntilNoFailures () =
-  let rec loop triesLeft =
-    let (exitStatus,failedPaths) = synchronizeOnce() in
+let synchronizeUntilNoFailures repeatMode =
+  let rec loop triesLeft pathsOpt =
+    let (exitStatus, failedPaths) =
+      synchronizeOnce
+        ?wantWatcher:(if repeatMode then Some () else None) pathsOpt in
     if failedPaths <> [] && triesLeft <> 0 then begin
-      loop (triesLeft - 1)
+      loop (triesLeft - 1) (Some (failedPaths, []))
     end else begin
-      Prefs.set Globals.paths !originalValueOfPathsPreference;
       exitStatus
     end in
-  loop (Prefs.read Uicommon.retry)
+  loop (Prefs.read Uicommon.retry) None
 
 let rec synchronizeUntilDone () =
   let repeatinterval =
@@ -898,7 +785,7 @@
                            ^Prefs.read Uicommon.repeat
                            ^") should be either a number or 'watch'\n")) in
 
-  let exitStatus = synchronizeUntilNoFailures() in
+  let exitStatus = synchronizeUntilNoFailures(repeatinterval >= 0) in
   if repeatinterval < 0 then
     exitStatus
   else begin
@@ -958,10 +845,6 @@
     setWarnPrinter();
     Trace.statusFormatter := formatStatus;
 
-    (* Save away the user's path preferences in case they are needed for
-       restarting/repeating *)
-    originalValueOfPathsPreference := Prefs.read Globals.paths;
-
     let exitStatus = synchronizeUntilDone() in
 
     (* Put the terminal back in "sane" mode, if necessary, and quit. *)

Modified: trunk/src/update.ml
===================================================================
--- trunk/src/update.ml	2012-08-07 20:06:46 UTC (rev 503)
+++ trunk/src/update.ml	2012-08-09 14:06:21 UTC (rev 504)
@@ -1074,7 +1074,66 @@
 	     (Os.myCanonicalHostName ()))))
     (Prefs.read mountpoints)
 
+(***********************************************************************
+                           Set of paths
+************************************************************************)
 
+type pathTree = PathTreeLeaf
+              | PathTreeNode of pathTree NameMap.t
+
+let rec addPathToTree path tree =
+  match Path.deconstruct path, tree with
+    None, _ | _, Some PathTreeLeaf ->
+      PathTreeLeaf
+  | Some (nm, p), None ->
+      PathTreeNode (NameMap.add nm (addPathToTree p None) NameMap.empty)
+  | Some (nm, p), Some (PathTreeNode children) ->
+      let t = try Some (NameMap.find nm children) with Not_found -> None in
+      PathTreeNode (NameMap.add nm (addPathToTree p t) children)
+
+let rec removePathFromTree path tree =
+  match Path.deconstruct path, tree with
+    None, _ ->
+      None
+  | Some (nm, p), PathTreeLeaf ->
+      Some tree
+  | Some (nm, p), PathTreeNode children ->
+      try
+        let t = NameMap.find nm children in
+        match removePathFromTree p t with
+          None ->
+            let newChildren = NameMap.remove nm children in
+            if NameMap.is_empty children then None else
+            Some (PathTreeNode newChildren)
+        | Some t ->
+            Some (PathTreeNode (NameMap.add nm t children))
+      with Not_found ->
+        Some tree
+  
+let pathTreeOfList l =
+  Safelist.fold_left (fun t p -> Some (addPathToTree p t)) None l
+
+let removePathsFromTree l treeOpt =
+  Safelist.fold_left
+    (fun t p ->
+       match t with
+         None   -> None
+       | Some t -> removePathFromTree p t)
+    treeOpt l
+
+let rec getSubTree path tree =
+  match Path.deconstruct path, tree with
+    None, _ ->
+      Some tree
+  | Some (nm, p), PathTreeLeaf ->
+      Some PathTreeLeaf
+  | Some (nm, p), PathTreeNode children ->
+      try
+        let t = NameMap.find nm children in
+        getSubTree p t
+      with Not_found ->
+        None
+
 (***********************************************************************
                            UPDATE DETECTION
 ************************************************************************)
@@ -1126,6 +1185,7 @@
     { fastCheck : bool;
       dirFastCheck : bool;
       dirStamp : Props.dirChangedStamp;
+      archHash : string;
       showStatus : bool }
 
 (** Status display **)
@@ -1423,9 +1483,11 @@
         let path' = Path.child path nm in
         debugverbose (fun () -> Util.msg
           "buildUpdateChildren(handleChild): %s\n" (Path.toString path'));
-        (* BCP 6/10: Added check for ignored path, but I'm not completely
-           sure this is the right place for it: *)
         if Globals.shouldIgnore path' then begin
+          (* We have to ignore paths which are in the archive but no
+             longer exists in the filesystem. Note that we cannot
+             reach this point for files that exists on the filesystem
+             ([hasIgnoredChildren] below would have been true). *)
           debugignore (fun()->Util.msg "buildUpdateChildren: ignoring path %s\n"
                                 (Path.toString path'));
           archive
@@ -1628,6 +1690,98 @@
   with
     Util.Transient(s) -> None, Error(s)
 
+(* Compute the updates for the tree of paths [tree] against archive. *)
+let rec buildUpdatePathTree archive fspath here tree scanInfo =
+  match tree, archive with
+    PathTreeNode children, ArchiveDir (archDesc, archChildren) ->
+      let curChildren =
+        lazy (List.fold_left (fun m (nm, st) -> NameMap.add nm st m)
+                NameMap.empty (getChildren fspath here))
+      in
+      let updates = ref [] in
+      let archUpdated = ref false in
+      let newChi = ref archChildren in
+      let handleChild nm archive status tree' =
+        let path' = Path.child here nm in
+        if Os.isTempFile (Name.toString nm) || Globals.shouldIgnore path' then
+          archive
+        else begin
+          match status with
+            `Ok | `Abs ->
+              let (arch,uiChild) =
+                buildUpdatePathTree archive fspath path' tree' scanInfo in
+              if uiChild <> NoUpdates then
+                updates := (nm, uiChild) :: !updates;
+              begin match arch with
+                None      -> archive
+              | Some arch -> archUpdated := true; arch
+              end
+          | `Dup ->
+              let uiChild =
+                Error
+                  ("Two or more files on a case-sensitive system have names \
+                    identical except for case.  They cannot be synchronized \
+                    to a case-insensitive file system.  (File '" ^
+                   Path.toString path' ^ "')")
+              in
+              updates := (nm, uiChild) :: !updates;
+              archive
+          | `BadEnc ->
+              let uiChild =
+                Error ("The file name is not encoded in Unicode.  (File '"
+                       ^ Path.toString path' ^ "')")
+              in
+              updates := (nm, uiChild) :: !updates;
+              archive
+          | `BadName ->
+              let uiChild =
+                Error
+                  ("The name of this Unix file is not allowed under Windows.  \
+                    (File '" ^ Path.toString path' ^ "')")
+              in
+              updates := (nm, uiChild) :: !updates;
+              archive
+        end
+      in
+      NameMap.iter
+        (fun nm tree' ->
+           let inArchive = NameMap.mem nm archChildren in
+           let arch =
+             if tree' = PathTreeLeaf || not inArchive then begin
+               let (nm', st) =
+                 try
+                   NameMap.findi nm (Lazy.force curChildren)
+                 with Not_found -> try
+                   (fst (NameMap.findi nm archChildren), `Abs)
+                 with Not_found ->
+                   (nm, `Abs)
+               in
+               let arch =
+                 try NameMap.find nm archChildren with Not_found -> NoArchive
+               in
+               handleChild nm' arch st tree'
+             end else begin
+               let (nm', arch) = NameMap.findi nm archChildren in
+               handleChild nm' arch `Ok tree'
+             end
+           in
+           if inArchive then newChi := NameMap.add nm arch !newChi)
+        children;
+      (begin if !archUpdated then
+          Some (ArchiveDir (archDesc, !newChi))
+        else
+          None
+       end,
+       if !updates <> [] then
+         (* The Recon module relies on the updates to be sorted *)
+         Updates (Dir (archDesc, Safelist.rev !updates, PropsSame, false),
+                  oldInfoOf archive)
+       else
+         NoUpdates)
+  | _ ->
+      showStatus scanInfo here;
+      buildUpdateRec archive fspath here scanInfo
+
 (* Compute the updates for [path] against archive.  Also returns an
    archive, which is the old archive with time stamps updated
    appropriately (i.e., for those files whose contents remain
@@ -1635,12 +1789,11 @@
    contents.  The directory permissions along the path are also
    collected, in case we need to build the directory hierarchy
    on one side. *)
-let rec buildUpdate archive fspath fullpath here path dirStamp scanInfo =
+let rec buildUpdate archive fspath fullpath here path pathTree scanInfo =
   match Path.deconstruct path with
     None ->
-      showStatus scanInfo here;
       let (arch, ui) =
-        buildUpdateRec archive fspath here scanInfo in
+        buildUpdatePathTree archive fspath here pathTree scanInfo in
       (begin match arch with
          None      -> archive
        | Some arch -> arch
@@ -1707,8 +1860,8 @@
               let otherChildren = NameMap.remove name children in
               let (arch, updates, localPath, props) =
                 buildUpdate
-                  archChild fspath fullpath (Path.child here name') path'
-                  dirStamp scanInfo
+                  archChild fspath fullpath (Path.child here name')
+                  path' pathTree scanInfo
               in
               let children =
                 if arch = NoArchive then otherChildren else
@@ -1720,8 +1873,8 @@
           | _ ->
               let (arch, updates, localPath, props) =
                 buildUpdate
-                  NoArchive fspath fullpath (Path.child here name') path'
-                  dirStamp scanInfo
+                  NoArchive fspath fullpath (Path.child here name')
+                  path' pathTree scanInfo
               in
               assert (arch = NoArchive);
               (archive, updates, localPath,
@@ -1766,10 +1919,16 @@
             (Proplist.add rsrcKey newRsrc props)));
     stamp
 
+(* This contains the list of synchronized paths and the directory stamps
+   used by the previous update detection, when a watcher process is used.
+   This make it possible to know when the state of the watcher process
+   needs to be reset. *)
+let previousFindOptions = Hashtbl.create 7
+
 (* for the given path, find the archive and compute the list of update
    items; as a side effect, update the local archive w.r.t. time-stamps for
    unchanged files *)
-let findLocal fspath pathList:
+let findLocal wantWatcher fspath pathList subpaths :
       (Path.local * Common.updateItem * Props.t list) list =
   debug (fun() -> Util.msg
     "findLocal %s (%s)\n" (Fspath.toDebugString fspath)
@@ -1793,24 +1952,79 @@
          as Windows does not update directory modification times
          on FAT filesystems. *)
       dirFastCheck = useFastChecking () && Util.osType = `Unix;
-      dirStamp = dirStamp;
+      dirStamp = dirStamp; archHash = archiveHash fspath;
       showStatus = not !Trace.runningasserver }
   in
   let (cacheFilename, _) = archiveName fspath FPCache in
   let cacheFile = Os.fileInUnisonDir cacheFilename in
   Fpcache.init scanInfo.fastCheck (Prefs.read ignoreArchives) cacheFile;
+  let unchangedOptions =
+    try
+      Hashtbl.find previousFindOptions scanInfo.archHash
+      = (scanInfo.dirStamp, pathList)
+    with Not_found ->
+      false
+  in
+  let paths =
+    match subpaths with
+      Some (unsynchronizedPaths, blacklistedPaths) when unchangedOptions ->
+        let (>>) x f = f x in
+        let paths =
+          Fswatchold.getChanges scanInfo.archHash
+          (* We do not really need to filter here (they are filtered also
+             by [buildUpdatePathTree], but that might reduce greatly and
+             cheaply number of paths to consider... *)
+          >> List.filter (fun path -> not (Globals.shouldIgnore path))
+        in
+        let filterPaths paths subpaths =
+          let number_list l =
+            let i = ref (-1) in
+            Safelist.map (fun x -> incr i; (!i, x)) l
+          in
+          paths >> (* We number paths, to be able to recover their
+                      initial order. *)
+                   number_list
+                >> (* We put longest paths first, in order to deal
+                      correctly with nested paths (tough that might be
+                      overkill...) *)
+                   List.sort (fun (_, p1) (_, p2) -> Path.compare p2 p1)
+                >> (* We extract the set of changed paths included in
+                      each synchronized path *)
+                   List.fold_left
+                     (fun (l, tree) (i, p) ->
+                        match tree with
+                          None ->
+                            ((i, (p, None)) :: l, None)
+                        | Some tree ->
+                            ((i, (p, getSubTree p tree)) :: l,
+                             removePathFromTree p tree))
+                     ([], pathTreeOfList subpaths)
+                >> fst
+                >> (* Finally, we restaure the initial order *)
+                   List.sort (fun (i1, _) (i2, _) -> compare i1 i2)
+                >> List.map snd
+        in
+        filterPaths pathList (Safelist.append unsynchronizedPaths paths)
+    | _ ->
+        if wantWatcher && Fswatchold.start scanInfo.archHash fspath then
+          Hashtbl.replace previousFindOptions
+            scanInfo.archHash (scanInfo.dirStamp, pathList)
+        else
+          Hashtbl.remove previousFindOptions scanInfo.archHash;
+        Safelist.map (fun p -> (p, Some PathTreeLeaf)) pathList
+  in
   let (archive, updates) =
     Safelist.fold_right
-      (fun path (arch, upd) ->
-         if Globals.shouldIgnore path then
-           (arch, (translatePathLocal fspath path, NoUpdates, []) :: upd)
-         else
-           let (arch', ui, localPath, props) =
-             buildUpdate
-               arch fspath path Path.empty path dirStamp scanInfo
-           in
-           arch', (localPath, ui, props) :: upd)
-      pathList (archive, [])
+      (fun (path, pathTreeOpt) (arch, upd) ->
+         match pathTreeOpt with
+           Some pathTree when not (Globals.shouldIgnore path) ->
+             let (arch', ui, localPath, props) =
+               buildUpdate arch fspath path Path.empty path pathTree scanInfo
+             in
+             (arch', (localPath, ui, props) :: upd)
+         | _ ->
+             (arch, (translatePathLocal fspath path, NoUpdates, []) :: upd))
+      paths (archive, [])
   in
   Fpcache.finish ();
 (*
@@ -1824,10 +2038,10 @@
 let findOnRoot =
   Remote.registerRootCmd
     "find"
-    (fun (fspath, pathList) ->
-       Lwt.return (findLocal fspath pathList))
+    (fun (fspath, (wantWatcher, pathList, subpaths)) ->
+       Lwt.return (findLocal wantWatcher fspath pathList subpaths))
 
-let findUpdatesOnPaths pathList =
+let findUpdatesOnPaths ?wantWatcher pathList subpaths =
   Lwt_unix.run
     (loadArchives true >>= (fun (ok, checksums) ->
      begin if ok then Lwt.return checksums else begin
@@ -1850,7 +2064,7 @@
      let t = Trace.startTimer "Collecting changes" in
      Globals.allRootsMapWithWaitingAction (fun r ->
        debug (fun() -> Util.msg "findOnRoot %s\n" (root2string r));
-       findOnRoot r pathList)
+       findOnRoot r (wantWatcher <> None, pathList, subpaths))
        (fun (host, _) ->
          begin match host with
            Remote _ -> Uutil.showUpdateStatus "";
@@ -1870,10 +2084,10 @@
      Trace.status "";
      Lwt.return result))))
 
-let findUpdates () =
+let findUpdates ?wantWatcher subpaths =
   (* TODO: We should filter the paths to remove duplicates (including prefixes)
      and ignored paths *)
-  findUpdatesOnPaths (Prefs.read Globals.paths)
+  findUpdatesOnPaths ?wantWatcher (Prefs.read Globals.paths) subpaths
 
 
 (*****************************************************************************)
@@ -2253,7 +2467,7 @@
   (* ...and check that this is a good description of what's out in the world *)
   let scanInfo =
     { fastCheck = false; dirFastCheck = false;
-      dirStamp = Props.changedDirStamp;
+      dirStamp = Props.changedDirStamp; archHash = "" (* Not used *);
       showStatus = false } in
   let (_, uiNew) = buildUpdateRec archive fspath localPath scanInfo in
   markPossiblyUpdatedRec fspath pathInArchive uiNew;

Modified: trunk/src/update.mli
===================================================================
--- trunk/src/update.mli	2012-08-07 20:06:46 UTC (rev 503)
+++ trunk/src/update.mli	2012-08-09 14:06:21 UTC (rev 504)
@@ -18,10 +18,16 @@
 (* Retrieve the actual names of the roots *)
 val getRootsName : unit -> string 
 
-(* Structures describing dirty files/dirs (1 per path given in the -path preference) *)
+(* Perform update detection. Optionally, takes as input the list of
+   paths known not to be synchronized and a list of paths not to
+   check. Returns structures describing dirty files/dirs (1 per path
+   given in the -path preference). An option controls whether we
+   would like to use the external filesytem monitoring process. *)
 val findUpdates :
-  unit -> ((Path.local * Common.updateItem * Props.t list) *
-           (Path.local * Common.updateItem * Props.t list)) list
+  ?wantWatcher:unit ->
+  (Path.t list * Path.t list) option ->
+  ((Path.local * Common.updateItem * Props.t list) *
+      (Path.local * Common.updateItem * Props.t list)) list
 
 (* Take a tree of equal update contents and update the archive accordingly. *)
 val markEqual :



More information about the Unison-hackers mailing list