Re: treating some directories atomically, and ignoring socket files

Julian Squires <[email protected]>
Newsgroups gmane.network.unison.devel
Message-ID <CAEbLoBKobC2P=XBfjdOPj4r9=A4uAt6fu+YdStfPBYuDweM8BA@mail.gmail.com>
Thanks for the feedback, everyone.  I like the idea of the "up" action
in the text UI, but implementing it right now was outside the scope of
what I was working on.

I've attached patches that implement an atomic option, so you can
specify, e.g., "-atomic Name\ .git -atomic Name\ .svn", and these
directories will be treated indivisibly.  Presumably one can use the
pattern matching facilities of this kind of predicate to match
packages on OS X but I haven't investigated it.

I have no doubt there are improvements that can be made to how I have
implemented this, so I welcome your comments.

-- 
Julian Squires

_______________________________________________
Unison-hackers mailing list
[email protected]
http://lists.seas.upenn.edu/mailman/listinfo/unison-hackers
0001-Add-atomic-option-as-a-global-predicate.patch (text/x-diff, 1.4 KB)
From 58a04f261b1dc2a2362d8dd859c5f952fd3f8ba9 Mon Sep 17 00:00:00 2001
From: Julian Squires <[email protected]>
Date: Tue, 13 Jan 2015 07:44:42 -0500
Subject: [PATCH 1/3] Add atomic option as a global predicate

---
 src/globals.ml  | 6 +++++-
 src/globals.mli | 1 +
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/globals.ml b/src/globals.ml
index a601e49..382c2e4 100644
--- a/src/globals.ml
+++ b/src/globals.ml
@@ -258,7 +258,11 @@ let ignorenotPred =
      and then using {\\tt ignorenot} to select particular paths to be 
      synchronized will not work.  Instead, you should use the {\\tt path}
      preference to choose particular paths to synchronize.")
-    
+
+let atomic = Pred.create "atomic" ~advanced:true
+  ("This preference specifies paths for directories whose \
+     contents will be considered as a group rather than individually.")
+
 let shouldIgnore p =
   let p = Path.toString p in
   (Pred.test ignorePred p) && not (Pred.test ignorenotPred p) 
diff --git a/src/globals.mli b/src/globals.mli
index f542bf5..b7e7f73 100644
--- a/src/globals.mli
+++ b/src/globals.mli
@@ -80,6 +80,7 @@ val shouldIgnore : 'a Path.path -> bool
 val shouldMerge : 'a Path.path -> bool
 val ignorePred : Pred.t
 val ignorenotPred : Pred.t
+val atomic : Pred.t
 
 (* Be careful calling this to add new patterns to be ignored: Its
    value does NOT persist when a new profile is loaded, so it has to
-- 
2.1.3
0002-Add-acceptance-test-for-git-directory-atomicity.patch (text/x-diff, 2.5 KB)
From f9e2a32290ad94a5e37e490f1a49ca327cdfadea Mon Sep 17 00:00:00 2001
From: Julian Squires <[email protected]>
Date: Thu, 18 Dec 2014 18:15:16 -0500
Subject: [PATCH 2/3] Add acceptance test for git directory atomicity

The client would like the contents of .git directories to be copied as a
whole, not merged.
---
 src/test.ml | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/src/test.ml b/src/test.ml
index 2f02f5b..453cace 100644
--- a/src/test.ml
+++ b/src/test.ml
@@ -322,6 +322,40 @@ let test() =
      running fast enough that the whole thing happens within a second, then the
      update will be missed! *)
 
+  (* Test that .git is treated atomically. *)
+  runtest "Atomicity of certain directories 1" ["atomic = Name .git";
+                                                "force = newer"] (fun() ->
+      let orig = (Dir ["foo", Dir [".git", Dir ["a", File "foo";
+                                                "b", File "bar";
+                                                "c", File "baz"]]]) in
+      put R1 orig; put R2 orig; sync();
+      let expected = (Dir ["foo", Dir [".git", Dir ["a", File "modified on R1";
+                                                    "b", File "bar";
+                                                    "c", File "modified on R1"]]]) in
+      put R2 (Dir ["foo", Dir [".git",
+                               Dir ["a", File "foo";
+                                    "b", File "modified on R2";
+                                    "c", File "modified on R2"]]]);
+      put R1 expected;
+      sync ();
+      check "1" R2 expected;
+      check "2" R1 expected
+    );
+
+  runtest "Atomicity of certain directories 2" ["atomic = Name .git"] (fun() ->
+      let a = (Dir ["foo", Dir [".git", Dir ["a", File "foo";
+                                             "b", File "bar";
+                                             "c", File "baz";
+                                             "d", File "quux"]]]) in
+      let b = (Dir ["foo", Dir [".git", Dir ["a", File "foo";
+                                             "b", File "bar";
+                                             "c", File "baz";
+                                             "e", File "quux"]]]) in
+      put R1 a; put R2 b; sync();
+      check "1" R1 a;
+      check "2" R2 b
+    );
+
   (* Check for the bug reported by Ralf Lehmann *)
   if not bothRootsLocal then 
     runtest "backups 1 (remote)" ["backup = Name *"] (fun() -> 
-- 
2.1.3
0003-Add-handling-of-atomic-directories.patch (text/x-diff, 4.6 KB)
From 51daee4d2577bbb2a3a76a78948f0f9890c277a7 Mon Sep 17 00:00:00 2001
From: Julian Squires <[email protected]>
Date: Wed, 14 Jan 2015 08:18:00 -0500
Subject: [PATCH 3/3] Add handling of atomic directories

If there have been any updates of a directory we consider atomic, we
mark it in conflict so that it will be dealt with as a single recon item
encapsulating all the necessary update items.
---
 src/recon.ml | 76 +++++++++++++++++++++++++++++++++++-------------------------
 1 file changed, 45 insertions(+), 31 deletions(-)

diff --git a/src/recon.ml b/src/recon.ml
index e4f42bb..519d8e2 100644
--- a/src/recon.ml
+++ b/src/recon.ml
@@ -580,39 +580,53 @@ let rec reconcile
       (add_equal counter equals (Absent, Absent), unequals)
   | (Updates (Dir (desc1, children1, propsChanged1, _) as uc1, prevState1),
      Updates (Dir (desc2, children2, propsChanged2, _) as uc2, prevState2)) ->
-       (* See if the directory itself should have a reconItem *)
-       let dirResult =
-         if propsChanged1 = PropsSame && propsChanged2 = PropsSame then
-           (equals, unequals)
-         else if Props.similar desc1 desc2 then
-           let uc1 = Dir (desc1, [], PropsSame, false) in
-           let uc2 = Dir (desc2, [], PropsSame, false) in
-           (add_equal counter equals (uc1, uc2), unequals)
-         else
-           let action =
-             if propsChanged1 = PropsSame then Replica2ToReplica1
-             else if propsChanged2 = PropsSame then Replica1ToReplica2
-             else Conflict "properties changed on both sides" in
-           (equals,
-            Tree.add unequals
-              (Different
-                 {rc1 = update2replicaContent path false ui1 [] uc1 `DIRECTORY;
-                  rc2 = update2replicaContent path false ui2 [] uc2 `DIRECTORY;
+       let is_atomic_case = match path |> Path.finalName with
+        | Some n -> Pred.test Globals.atomic (Name.toString n)
+        | None -> false
+       in
+       if is_atomic_case then
+         let action = Conflict "atomic directory" in
+         (equals,
+          Tree.add unequals
+            (Different
+                 {rc1 = update2replicaContent path true ui1 [] uc1 `DIRECTORY;
+                  rc2 = update2replicaContent path true ui2 [] uc2 `DIRECTORY;
                   direction = action; default_direction = action;
                   errors1 = []; errors2 = []}))
-       in
-       (* Apply reconcile on children. *)
-       Safelist.fold_left
-         (fun (equals, unequals) (name1,ui1,name2,ui2) ->
-           let (eq, uneq) =
-             reconcile
-               allowPartial (Path.child path name1) ui1 [] ui2 [] counter
-               (Tree.enter equals (name1, name2))
-               (Tree.enter unequals (name1, name2))
-           in
-           (Tree.leave eq, Tree.leave uneq))
-         dirResult
-         (combineChildren children1 children2)
+       else
+         (* See if the directory itself should have a reconItem *)
+         let dirResult =
+           if propsChanged1 = PropsSame && propsChanged2 = PropsSame then
+             (equals, unequals)
+           else if Props.similar desc1 desc2 then
+             let uc1 = Dir (desc1, [], PropsSame, false) in
+             let uc2 = Dir (desc2, [], PropsSame, false) in
+             (add_equal counter equals (uc1, uc2), unequals)
+           else
+             let action =
+               if propsChanged1 = PropsSame then Replica2ToReplica1
+               else if propsChanged2 = PropsSame then Replica1ToReplica2
+               else Conflict "properties changed on both sides" in
+             (equals,
+              Tree.add unequals
+                (Different
+                   {rc1 = update2replicaContent path false ui1 [] uc1 `DIRECTORY;
+                    rc2 = update2replicaContent path false ui2 [] uc2 `DIRECTORY;
+                    direction = action; default_direction = action;
+                    errors1 = []; errors2 = []}))
+         in
+         (* Apply reconcile on children. *)
+         Safelist.fold_left
+           (fun (equals, unequals) (name1,ui1,name2,ui2) ->
+              let (eq, uneq) =
+                reconcile
+                  allowPartial (Path.child path name1) ui1 [] ui2 [] counter
+                  (Tree.enter equals (name1, name2))
+                  (Tree.enter unequals (name1, name2))
+              in
+              (Tree.leave eq, Tree.leave uneq))
+           dirResult
+           (combineChildren children1 children2)
   | (Updates (File (desc1,contentsChanged1) as uc1, prev),
      Updates (File (desc2,contentsChanged2) as uc2, _)) ->
        begin match contentsChanged1, contentsChanged2 with
-- 
2.1.3
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.