Recent changes to this wiki:

Added a comment
diff --git a/doc/bugs/tasty_test_limiting_broken_by_concurrency/comment_1_e043a71b60999cf97bd872d8b32fcac9._comment b/doc/bugs/tasty_test_limiting_broken_by_concurrency/comment_1_e043a71b60999cf97bd872d8b32fcac9._comment
new file mode 100644
index 0000000000..592b8a45fa
--- /dev/null
+++ b/doc/bugs/tasty_test_limiting_broken_by_concurrency/comment_1_e043a71b60999cf97bd872d8b32fcac9._comment
@@ -0,0 +1,16 @@
+[[!comment format=mdwn
+ username="jkniiv"
+ avatar="http://cdn.libravatar.org/avatar/05fd8b33af7183342153e8013aa3713d"
+ subject="comment 1"
+ date="2022-05-24T02:46:17Z"
+ content="""
+Well done. Btw if somebody (like me) wonders why their tasty awk patterns '/like this/' don't work with `git-annex test -p`
+in Windows Git Bash, one has to remember that Git Bash is based on Cygwin/Msys and there's a Unix -> Windows
+path conversion at work. You can stop that conversion by prepending a space to the awk pattern ' /like this/'
+or by setting the env var MSYS_NO_PATHCONV=1. So basically the above should be formulated as follows in this arguably hobbled environment:
+
+[[!format sh \"\"\"
+$ MSYS_NO_PATHCONV=1 git-annex test -p '/concurrent get of dup key regression/ || /Init Tests/'
+\"\"\"]]
+
+"""]]

Added a comment
diff --git a/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5/comment_2_5c7ec0d8520f957ec4f6a4df30941cd8._comment b/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5/comment_2_5c7ec0d8520f957ec4f6a4df30941cd8._comment
new file mode 100644
index 0000000000..3d80885926
--- /dev/null
+++ b/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5/comment_2_5c7ec0d8520f957ec4f6a4df30941cd8._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="jkniiv"
+ avatar="http://cdn.libravatar.org/avatar/05fd8b33af7183342153e8013aa3713d"
+ subject="comment 2"
+ date="2022-05-23T19:41:27Z"
+ content="""
+That did seem to do the trick as the testsuite passes fully again. Thanks for the quick fix, Joey!
+"""]]

fix git-annex test -p
test: When limiting tests to run with -p, work around tasty limitation by
automatically including dependent tests.
This fixes a reversion because it didn't used to use dependencies and
forced tasty to run the init tests first. That changed when parallelizing
the test suite.
It will sometimes do a little more work than strictly required,
because it adds init tests deps when limited to eg quickcheck tests,
which don't depend on them. But this only adds a few seconds work.
Sponsored-by: Dartmouth College's Datalad project
diff --git a/CHANGELOG b/CHANGELOG
index 6a23e899ff..38774988f8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -19,6 +19,8 @@ git-annex (10.20220505) UNRELEASED; urgency=medium
   * Improve an error message displayed in that situation.
   * Prevent git-annex init incorrectly reinitializing the repository in
     that situation.
+  * test: When limiting tests to run with -p, work around tasty limitation
+    by automatically including dependent tests.
 
  -- Joey Hess <id@joeyh.name>  Thu, 05 May 2022 15:08:07 -0400
 
diff --git a/Test.hs b/Test.hs
index 62e92e8d45..29897b27e0 100644
--- a/Test.hs
+++ b/Test.hs
@@ -252,7 +252,7 @@ testRemote testvariants remotetype setupremote =
 {- These tests set up the test environment, but also test some basic parts
  - of git-annex. They are always run before the repoTests. -}
 initTests :: TestTree
-initTests = testGroup "Init Tests"
+initTests = testGroup initTestsName
 	[ testCase "init" test_init
 	, testCase "add" test_add
 	]
@@ -339,7 +339,7 @@ repoTests note numparts = map mk $ sep
 	]
   where
 	mk l = testGroup groupname (initTests : map adddep l)
-	adddep = Test.Tasty.after AllSucceed (groupname ++ ".Init Tests")
+	adddep = Test.Tasty.after AllSucceed (groupname ++ "." ++ initTestsName)
 	groupname = "Repo Tests " ++ note
 	sep = sep' (replicate numparts [])
 	sep' (p:ps) (l:ls) = sep' (ps++[l:p]) ls
diff --git a/Test/Framework.hs b/Test/Framework.hs
index 1ddec7a5bc..2d8e0c6e2e 100644
--- a/Test/Framework.hs
+++ b/Test/Framework.hs
@@ -15,6 +15,7 @@ import Test.Tasty.HUnit
 import Test.Tasty.Options
 import Test.Tasty.Ingredients.Rerun
 import Test.Tasty.Ingredients.ConsoleReporter
+import qualified Test.Tasty.Patterns.Types as TP
 import Options.Applicative.Types
 import Control.Concurrent
 import Control.Concurrent.Async
@@ -724,10 +725,12 @@ parallelTestRunner' numjobs opts mkts
 	| otherwise = go =<< Utility.Env.getEnv subenv
   where
 	subenv = "GIT_ANNEX_TEST_SUBPROCESS"
+
 	-- Make more parts than there are jobs, because some parts
 	-- are larger, and this allows the smaller parts to be packed
 	-- in more efficiently, speeding up the test suite overall.
 	numparts = numjobs * 2
+
 	worker rs nvar a = do
 		(n, m) <- atomically $ do
 			(n, m) <- readTVar nvar
@@ -738,6 +741,7 @@ parallelTestRunner' numjobs opts mkts
 			else do
 				r <- a n
 				worker (r:rs) nvar a
+	
 	go Nothing = withConcurrentOutput $ do
 		ensuredir tmpdir
 		crippledfilesystem <- fst <$> Annex.Init.probeCrippledFileSystem'
@@ -753,7 +757,7 @@ parallelTestRunner' numjobs opts mkts
 		args <- getArgs
 		pp <- Annex.Path.programPath
 		termcolor <- hSupportsANSIColor stdout
-		let ps = if useColor (lookupOption (tastyOptionSet opts)) termcolor
+		let ps = if useColor (lookupOption tastyopts) termcolor
 			then "--color=always":args
 			else "--color=never":args
 		let runone n = do
@@ -783,16 +787,29 @@ parallelTestRunner' numjobs opts mkts
 		Just (n, crippledfilesystem, adjustedbranchok) -> setTestEnv $ do
 			let ts = mkts numparts crippledfilesystem adjustedbranchok opts
 			let t = topLevelTestGroup [ ts !! (n - 1) ]
-			case tryIngredients ingredients (tastyOptionSet opts) t of
+			case tryIngredients ingredients tastyopts t of
 				Nothing -> error "No tests found!?"
 				Just act -> ifM act
 					( exitSuccess
 					, exitFailure
 					)
+	
+	tastyopts = case lookupOption (tastyOptionSet opts) of
+		-- Work around limitation of tasty; when tests to run
+		-- are limited to a pattern, it does not include their
+		-- dependencies. So, add another pattern including the
+		-- init tests, which are a dependency of most tests.
+		TestPattern (Just p) -> 
+			setOption (TestPattern (Just (TP.Or p (TP.ERE initTestsName))))
+				(tastyOptionSet opts)
+		TestPattern Nothing -> tastyOptionSet opts
 
 topLevelTestGroup :: [TestTree] -> TestTree
 topLevelTestGroup = testGroup "Tests"
 
+initTestsName :: String
+initTestsName = "Init Tests"
+
 tastyParser :: [TestTree] -> ([String], Parser Test.Tasty.Options.OptionSet)
 #if MIN_VERSION_tasty(1,3,0)
 tastyParser ts = go
diff --git a/doc/bugs/tasty_test_limiting_broken_by_concurrency.mdwn b/doc/bugs/tasty_test_limiting_broken_by_concurrency.mdwn
index 73e322ae85..d557dac36e 100644
--- a/doc/bugs/tasty_test_limiting_broken_by_concurrency.mdwn
+++ b/doc/bugs/tasty_test_limiting_broken_by_concurrency.mdwn
@@ -1,14 +1,19 @@
 The changes to make `git-annex test` concurrent have
 broken using eg `git-annex test -p 'concurrent get of dup key regression'`
 
-The problem is that tasty is being run with a subset of tests in each
-runner, so most of them don't know about the test they're being limited to
-perform.
-
-There either needs to be a way to disable concurrency (eg, run all tests
-in one runner with -J1), or the code detect when tasty is limited, and
-automatically disable concurrency.
-
-Also, it looks like the repo setup test is not being run, even though it's
+It looks like the repo setup test is not being run, even though it's
 supposed to be a dependency of the test it was limited to.
 --[[Joey]]
+
+> Oh, that seems to be a limitation of tasty. From its docs:
+	
+	If Test B depends on Test A, remember that either of them may be 
+	filtered out using the --pattern option. Collecting the dependency
+	info happens after filtering. Therefore, if Test A is filtered out,
+	Test B will run unconditionally, and if Test B is filtered out,
+	it simply won't run.
+
+> This works: `git-annex test -p '/concurrent get of dup key regression/ || /Init Tests/'`
+> 
+> Ok, I was able to work around this by having git-annex test add the latter
+> pattern automatically. [[done]] --[[Joey]]

revert windows-specific locking changes that broke tests
This reverts windows-specific parts of 5a98f2d50913682c4ebe0e0c4ce695c450a96091
There were no code paths in common between windows and unix, so this
will return Windows to the old behavior.
The problem that the commit talks about has to do with multiple different
locations where git-annex can store annex object files, but that is not
too relevant to Windows anyway, because on windows the filesystem is always
treated as criplled and/or symlinks are not supported, so it will only
use one object location. It would need to be using a repo populated
in another OS to have the other object location in use probably.
Then a drop and get could possibly lead to a dangling lock file.
And, I was not able to actually reproduce that situation happening
before making that commit, even when I forced a race. So making these
changes on windows was just begging trouble..
I suspect that the change that caused the reversion is in
Annex/Content/Presence.hs. It checks if the content file exists,
and then called modifyContentDirWhenExists, which seems like it would
not fail, but if something deleted the content file at that point,
that call would fail. Which would result in an exception being thrown,
which should not normally happen from a call to inAnnexSafe. That was a
windows-specific change; the unix side did not have an equivilant
change.
Sponsored-by: Dartmouth College's Datalad project
diff --git a/Annex/Content.hs b/Annex/Content.hs
index 62471f7305..c054d45951 100644
--- a/Annex/Content.hs
+++ b/Annex/Content.hs
@@ -177,7 +177,7 @@ posixLocker takelock lockfile = do
 winLocker :: (LockFile -> IO (Maybe LockHandle)) -> ContentLocker
 winLocker takelock _ (Just lockfile) = 
 	let lck = do
-		modifyContentDirWhenExists lockfile $
+		modifyContentDir lockfile $
 			void $ liftIO $ tryIO $
 				writeFile (fromRawFilePath lockfile) ""
 		liftIO $ takelock lockfile
diff --git a/Annex/Content/Presence.hs b/Annex/Content/Presence.hs
index ff12c865c2..9f44e3d840 100644
--- a/Annex/Content/Presence.hs
+++ b/Annex/Content/Presence.hs
@@ -113,7 +113,7 @@ inAnnexSafe key = inAnnex' (fromMaybe True) (Just False) go key
 	 - remove the lock file to clean up after ourselves. -}
 	checklock (Just lockfile) contentfile =
 		ifM (liftIO $ doesFileExist (fromRawFilePath contentfile))
-			( modifyContentDirWhenExists lockfile $ liftIO $
+			( modifyContentDir lockfile $ liftIO $
 				lockShared lockfile >>= \case
 					Nothing -> return is_locked
 					Just lockhandle -> do
diff --git a/doc/bugs/tasty_test_limiting_broken_by_concurrency.mdwn b/doc/bugs/tasty_test_limiting_broken_by_concurrency.mdwn
new file mode 100644
index 0000000000..73e322ae85
--- /dev/null
+++ b/doc/bugs/tasty_test_limiting_broken_by_concurrency.mdwn
@@ -0,0 +1,14 @@
+The changes to make `git-annex test` concurrent have
+broken using eg `git-annex test -p 'concurrent get of dup key regression'`
+
+The problem is that tasty is being run with a subset of tests in each
+runner, so most of them don't know about the test they're being limited to
+perform.
+
+There either needs to be a way to disable concurrency (eg, run all tests
+in one runner with -J1), or the code detect when tasty is limited, and
+automatically disable concurrency.
+
+Also, it looks like the repo setup test is not being run, even though it's
+supposed to be a dependency of the test it was limited to.
+--[[Joey]]
diff --git a/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5.mdwn b/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5.mdwn
index 7e0702d2b8..a359ca8bee 100644
--- a/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5.mdwn
+++ b/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5.mdwn
@@ -213,3 +213,5 @@ Windows 10 version 21H2 (build 19044.1706), 64 bit.
 Git Annex is great. I use it several times a week with my multigigabyte backups, where it gives structure to my image-based backup routines, so you could say I'm a believer. :)
 
 [[!meta author=jkniiv]]
+
+> [[fixed|done]] --[[Joey]]
diff --git a/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5/comment_1_af091a5799ac0445033c05173e25a4af._comment b/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5/comment_1_af091a5799ac0445033c05173e25a4af._comment
new file mode 100644
index 0000000000..0328e72957
--- /dev/null
+++ b/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5/comment_1_af091a5799ac0445033c05173e25a4af._comment
@@ -0,0 +1,16 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2022-05-23T16:57:34Z"
+ content="""
+Thanks for another fine bug report.
+
+I took a look at that commit, and it seems that reverting it for Windows
+makes the most sense. There were two places where it changed behavior on
+Windows, that were both separated from the code paths for other OS's.
+
+I've made that change, which should fix it. I'm going to close this bug
+report, but please follow up if you still see the issue.
+
+(Also, I've opened [[tasty_test_limiting_broken_by_concurrency]]
+"""]]

some of the test log output I should've added; formatting
diff --git a/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5.mdwn b/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5.mdwn
index 08d255458f..7e0702d2b8 100644
--- a/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5.mdwn
+++ b/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5.mdwn
@@ -7,16 +7,192 @@ found that [[!commit 5a98f2d50913682c4ebe0e0c4ce695c450a96091]] is the first bad
 ### What steps will reproduce the problem?
 
 `stack setup && stack build`. Then copy git-annex.exe to `C:\annx` and in (Git) Bash say:
-```
-./git-annex test -p 'Repo Tests' 2>&1 | tee git-annex.test--p-Repo_Tests.LOG~202
-grep -E '(concurrent get of dup key regression|import):' git-annex.test--p-Repo_Tests.LOG~202
-```
+
+[[!format sh """
+$ ./git-annex test -p 'Repo Tests' 2>&1 | tee git-annex.test--p-Repo_Tests.LOG~202
+$ grep -E '(concurrent get of dup key regression|import):' git-annex.test--p-Repo_Tests.LOG~202
+"""]]
 
 Observe two FAILs.
 
 (It is unfortunate that you can't call for instance `./git-annex.exe test -p 'concurrent get of dup key regression'`
 directly because of an unrelated setup issue with Tasty. I guess this would warrant a separate bug report.)
 
+### Please provide any additional information below.
+
+_Under Windows with 10.20220505-g5a98f2d50:_
+
+[[!format sh """
+jkniiv@AINESIS MINGW64 /c/annx
+$ cat git-annex.test--p-Repo_Tests.LOG~202
+
+All 0 tests passed (0.00s)
+Tests
+  Repo Tests v8 adjusted unlocked branch
+    Init Tests
+      init:                                         OK (10.30s)
+      add:                                          OK (14.20s)
+    borg remote:                                    OK
+    uninit:                                         OK (15.54s)
+    conflict resolution (mixed directory and file): OK (154.59s)
+    concurrent get of dup key regression:           FAIL (18.41s)
+      .\\Test\\Framework.hs:70:
+      drop with dup failed (transcript follows)
+      drop foo ok
+      drop foo2
+      failed
+      (recording state in git...)
+      git-annex: content is locked
+      drop: 1 failed
+
+      Use -p '/Repo Tests/&&/concurrent get of dup key regression/' to rerun this test only.
+    migrate (via gitattributes):                    OK (35.88s)
+    fsck (basics):                                  OK (33.18s)
+    copy:                                           OK (29.05s)
+    drop (no remote):                               OK (18.30s)
+    shared clone:                                   OK (8.98s)
+
+1 out of 11 tests failed (338.52s)
+
+All 0 tests passed (0.03s)
+Tests
+  Repo Tests v8 adjusted unlocked branch
+    Init Tests
+      init:                          OK (10.26s)
+      add:                           gpg testing not implemented on Windows
+OK (13.95s)
+    crypto:                          OK
+    uninit (in git-annex branch):    OK (12.13s)
+    conflict resolution symlink bit: OK
+    union merge regression:          OK (148.89s)
+    unused:                          OK (59.95s)
+    fsck (bare):                     OK (5.63s)
+    lock:                            OK (29.89s)
+    drop (with remote):              OK (22.18s)
+    log:                             OK (10.16s)
+    add dup:                         OK (15.61s)
+
+All 12 tests passed (328.74s)
+Tests
+  Repo Tests v8 adjusted unlocked branch
+    Init Tests
+      init:                                               OK (10.27s)
+      add:                                                OK (14.07s)
+    rsync remote:                                         OK (8.80s)
+    conflict resolution (mixed locked and unlocked file): OK (70.62s)
+    conflict resolution (adjusted branch):                OK (92.89s)
+    version:                                              OK (10.99s)
+    conversion annexed to git:                            OK (18.28s)
+    fix:                                                  OK (9.54s)
+    move (ssh remote):                                    OK
+    unannex (no copy):                                    OK (12.49s)
+    export_import:                                        OK (88.90s)
+
+All 11 tests passed (336.95s)
+Tests
+  Repo Tests v8 adjusted unlocked branch
+    Init Tests
+      init:                                OK (9.80s)
+      add:                                 OK (13.22s)
+    add subdirs:                           OK (21.85s)
+    hook remote:                           OK (9.82s)
+    conflict resolution (nonannexed file): OK (133.39s)
+    transition propagation:                OK (139.19s)
+    merge:                                 OK (11.89s)
+    fsck --from remote:                    OK (10.01s)
+    edit (pre-commit):                     OK (15.37s)
+    get (ssh remote):                      OK
+    import:                                FAIL (20.87s)
+      .\\Test\\Framework.hs:70:
+      drop failed (transcript follows)
+      drop import1/f ok
+      drop import2/f
+      failed
+      drop import5/f
+      failed
+      (recording state in git...)
+      git-annex: content is locked
+      git-annex: content is locked
+      drop: 2 failed
+
+      Use -p '/Repo Tests/&&/import/' to rerun this test only.
+    ignore deleted files:                  OK (8.43s)
+
+1 out of 12 tests failed (393.92s)
+Tests
+  Repo Tests v8 adjusted unlocked branch
+    Init Tests
+      init:                                OK (10.27s)
+      add:                                 OK (14.14s)
+    bup remote:                            OK (8.85s)
+    map:                                   OK (12.69s)
+    conflict resolution movein regression: OK (92.81s)
+    sync:                                  OK (58.83s)
+    migrate:                               OK (35.32s)
+    trust:                                 OK (30.37s)
+    move (numcopies):                      OK (40.73s)
+    unannex (with copy):                   OK (14.55s)
+    export_import_subdir:                  OK (56.87s)
+
+All 11 tests passed (375.49s)
+Tests
+  Repo Tests v8 adjusted unlocked branch
+    Init Tests
+      init:                                       OK (8.97s)
+      add:                                        OK (13.79s)
+    preferred content:                            OK (63.02s)
+    upgrade:                                      OK (10.77s)
+    conflict resolution (uncommitted local file): OK (101.39s)
+    adjusted branch merge regression:             OK (19.12s)
+    describe:                                     OK (12.57s)
+    fsck (local untrusted):                       OK (26.13s)
+    lock --force:                                 OK (16.35s)
+    drop (untrusted remote):                      OK (24.97s)
+    view:                                         OK (15.62s)
+    add extras:                                   OK (13.99s)
+
+All 12 tests passed (326.79s)
+Tests
+  Repo Tests v8 adjusted unlocked branch
+    Init Tests
+      init:                                   OK (10.67s)
+      add:                                    OK (13.44s)
+    addurl:                                   OK (16.86s)
+    directory remote:                         OK (32.54s)
+    conflict resolution (nonannexed symlink): OK (40.41s)
+    conflict resolution:                      OK (86.32s)
+    info:                                     OK (13.57s)
+    conversion git to annexed:                OK (19.97s)
+    partial commit:                           OK (20.86s)
+    move:                                     OK (41.33s)
+    reinject:                                 OK (16.08s)
+    metadata:                                 OK (23.76s)
+
+All 12 tests passed (335.87s)
+Tests
+  Repo Tests v8 adjusted unlocked branch
+    Init Tests
+      init:                             OK (9.19s)
+      add:                              OK (13.38s)
+    required_content:                   OK (31.54s)
+    whereis:                            OK (25.92s)
+    conflict resolution (removed file): OK (203.83s)
+    adjusted branch subtree regression: OK (30.38s)
+    find:                               OK (26.15s)
+    fsck (remote untrusted):            OK (18.86s)
+    edit (no pre-commit):               OK (17.01s)
+    get:                                OK (13.68s)
+    magic:                              OK (5.86s)
+    readonly remote:                    OK
+
+All 12 tests passed (395.88s)
+  (Failures above could be due to a bug in git-annex, or an incompatibility
+   with utilities, such as git, installed on this system.)
+
+# End of transcript.
+"""]]
+
+
 ### What version of git-annex are you using? On what operating system?
 

(Diff truncated)
bug: commit 5a98f2d50 causes two failures to appear in repo tests on Windows
diff --git a/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5.mdwn b/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5.mdwn
new file mode 100644
index 0000000000..08d255458f
--- /dev/null
+++ b/doc/bugs/windows__58___two_repo_tests_fail_after_commit_5a98f2d5.mdwn
@@ -0,0 +1,39 @@
+### Please describe the problem.
+
+Recently I found that two repo tests, namely 'concurrent get of dup key regression'
+and 'import', fail due to locking problems. I bisected this on my Windows laptop and
+found that [[!commit 5a98f2d50913682c4ebe0e0c4ce695c450a96091]] is the first bad commit.
+
+### What steps will reproduce the problem?
+
+`stack setup && stack build`. Then copy git-annex.exe to `C:\annx` and in (Git) Bash say:
+```
+./git-annex test -p 'Repo Tests' 2>&1 | tee git-annex.test--p-Repo_Tests.LOG~202
+grep -E '(concurrent get of dup key regression|import):' git-annex.test--p-Repo_Tests.LOG~202
+```
+
+Observe two FAILs.
+
+(It is unfortunate that you can't call for instance `./git-annex.exe test -p 'concurrent get of dup key regression'`
+directly because of an unrelated setup issue with Tasty. I guess this would warrant a separate bug report.)
+
+### What version of git-annex are you using? On what operating system?
+
+[[!format sh """
+git-annex version: 10.20220505-g5a98f2d50
+build flags: Assistant Webapp Pairing TorrentParser Feeds Testsuite S3 WebDAV
+dependency versions: aws-0.22 bloomfilter-2.0.1.0 cryptonite-0.29 DAV-1.3.4 feed-1.3.2.0 ghc-8.10.7 http-client-0.7.9 persistent-sqlite-2.13.0.3 torrent-10000.1.1 uuid-1.3.15 yesod-1.6.1.2
+key/value backends: SHA256E SHA256 SHA512E SHA512 SHA224E SHA224 SHA384E SHA384 SHA3_256E SHA3_256 SHA3_512E SHA3_512 SHA3_224E SHA3_224 SHA3_384E SHA3_384 SKEIN256E SKEIN256 SKEIN512E SKEIN512 BLAKE2B256E BLAKE2B256 BLAKE2B512E BLAKE2B512 BLAKE2B160E BLAKE2B160 BLAKE2B224E BLAKE2B224 BLAKE2B384E BLAKE2B384 BLAKE2BP512E BLAKE2BP512 BLAKE2S256E BLAKE2S256 BLAKE2S160E BLAKE2S160 BLAKE2S224E BLAKE2S224 BLAKE2SP256E BLAKE2SP256 BLAKE2SP224E BLAKE2SP224 SHA1E SHA1 MD5E MD5 WORM URL X*
+remote types: git gcrypt p2p S3 bup directory rsync web bittorrent webdav adb tahoe glacier ddar git-lfs httpalso borg hook external
+operating system: mingw32 x86_64
+supported repository versions: 8 9 10
+upgrade supported from repository versions: 2 3 4 5 6 7 8 9 10
+"""]]
+
+Windows 10 version 21H2 (build 19044.1706), 64 bit.
+
+### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)
+
+Git Annex is great. I use it several times a week with my multigigabyte backups, where it gives structure to my image-based backup routines, so you could say I'm a believer. :)
+
+[[!meta author=jkniiv]]

Added a comment
diff --git a/doc/internals/lockdown/comment_5_3d7ab5c8e813b21812b5132c708aad68._comment b/doc/internals/lockdown/comment_5_3d7ab5c8e813b21812b5132c708aad68._comment
new file mode 100644
index 0000000000..e48f9ae20a
--- /dev/null
+++ b/doc/internals/lockdown/comment_5_3d7ab5c8e813b21812b5132c708aad68._comment
@@ -0,0 +1,13 @@
+[[!comment format=mdwn
+ username="nick.guenther@e418ed3c763dff37995c2ed5da4232a7c6cee0a9"
+ nickname="nick.guenther"
+ avatar="http://cdn.libravatar.org/avatar/9e85c6ca61c3f877fef4f91c2bf6e278"
+ subject="comment 5"
+ date="2022-05-20T18:09:45Z"
+ content="""
+Thanks for your time and your advice, joey. We'll stick with the hook for now then.
+
+I realized I didn't include versions: on my gitolite server I have git-annex 10.20220504. However, that's only because I hacked the environment up so I could use the [conda-forge](https://anaconda.org/conda-forge/git-annex) build as the system git-annex. Because the version in Ubuntu 22.04 is 8.20210223.
+
+That's not really a stable way to run a system, so I am leaning towards reverting and just living with the bug for a while.
+"""]]

response
diff --git a/doc/internals/lockdown/comment_4_0253bf642148ea853f6d1c8580e09db2._comment b/doc/internals/lockdown/comment_4_0253bf642148ea853f6d1c8580e09db2._comment
new file mode 100644
index 0000000000..ffa3d6f0f5
--- /dev/null
+++ b/doc/internals/lockdown/comment_4_0253bf642148ea853f6d1c8580e09db2._comment
@@ -0,0 +1,17 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""Re: How to disable lockdown in bare repos?"""
+ date="2022-05-20T17:08:29Z"
+ content="""
+I think that the annex.freezecontent-command approach is fine. The hook
+runs after git-annex changes permissions, and it can add them back if you
+want. It is supported since 8.20210630
+
+I agree it would be better if gitolite could be modified to only set the
+write bits before deleting the repository. It seems to me that gitolite
+demonstratably has a bug, because you show it fail to delete everything but
+apparently behave as if it succeeded. Perhaps setting the write bits 
+could be justified to the gitolite developers as a way to make it more robust
+when removing a repository, in case some permission problem prevents deleting
+the content of a directory.
+"""]]

comment
diff --git a/doc/bugs/get_is_busy_doing_nothing/comment_1_ea9363fac84619b72d6c9f2a3a5c2d6d._comment b/doc/bugs/get_is_busy_doing_nothing/comment_1_ea9363fac84619b72d6c9f2a3a5c2d6d._comment
new file mode 100644
index 0000000000..9e4e3b6fe6
--- /dev/null
+++ b/doc/bugs/get_is_busy_doing_nothing/comment_1_ea9363fac84619b72d6c9f2a3a5c2d6d._comment
@@ -0,0 +1,15 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2022-05-18T20:53:33Z"
+ content="""
+git-annex has stall detection, but it's not enabled by default, because how
+slow a "stall" is can vary quite a bit. Setting annex.stalldetection would
+probably help.
+
+Plenty of http clients can stall out if there's a network problem. For
+example, wget that and pull the ethernet cable; you'll see wget hang for at
+least as long as it took me to write this comment. Since it's just waiting
+for a read, it will block until the TCP connection closes, and situations can
+cause dead TCP connections to not close for a long time.
+"""]]

comment
diff --git a/doc/bugs/v8_conflict_resolution___40__adjusted_branch__41___teststuck/comment_2_dcc764525370338fbf9b6f2728e3e5ac._comment b/doc/bugs/v8_conflict_resolution___40__adjusted_branch__41___teststuck/comment_2_dcc764525370338fbf9b6f2728e3e5ac._comment
new file mode 100644
index 0000000000..aea3cf2772
--- /dev/null
+++ b/doc/bugs/v8_conflict_resolution___40__adjusted_branch__41___teststuck/comment_2_dcc764525370338fbf9b6f2728e3e5ac._comment
@@ -0,0 +1,9 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 2"""
+ date="2022-05-18T20:47:12Z"
+ content="""
+I've fixed that setEnv problem now, so the test suite is free of strange
+segfault behavior, hopefully. And hopefully that will also avoid this bug
+from happening more.
+"""]]

datalad metadata
diff --git a/doc/bugs/v8_conflict_resolution___40__adjusted_branch__41___teststuck.mdwn b/doc/bugs/v8_conflict_resolution___40__adjusted_branch__41___teststuck.mdwn
index c87880ef95..9424769a7b 100644
--- a/doc/bugs/v8_conflict_resolution___40__adjusted_branch__41___teststuck.mdwn
+++ b/doc/bugs/v8_conflict_resolution___40__adjusted_branch__41___teststuck.mdwn
@@ -55,3 +55,6 @@ result-smaug-699/handle-result.yaml-123-3c5c350a-success/result-smaug-699/git-an
 ```
 
 I don't know if that was somehow a fluke that may be testing of git-annex is in  general close to an hour on smaug, or it was too busy -- but unlikely.  I am adding Elapsed time reporting https://github.com/datalad/git-annex/pull/126
+
+[[!meta author=yoh]]
+[[!tag projects/datalad]]

avoid setEnv in test framework when tasty is running
setEnv is not thread safe and could cause a getEnv by another thread to
segfault, or perhaps other had behavior. This is particularly a problem
when using tasty, because tasty runs the test in a thread, and a getEnv
in another thread.
The use of top-level TMVars is ugly, but ok because only 1 test actually
runs at a time per process. Because it has to chdir into the test repo.
The setEnv that remains happens before tasty is running.
Sponsored-by: Dartmouth College's Datalad project
diff --git a/Test/Framework.hs b/Test/Framework.hs
index 94b06284cf..13bfc87929 100644
--- a/Test/Framework.hs
+++ b/Test/Framework.hs
@@ -24,6 +24,7 @@ import System.Environment (getArgs)
 import System.Console.Concurrent
 import System.Console.ANSI
 import GHC.Conc
+import System.IO.Unsafe (unsafePerformIO)
 
 import Common
 import Types.Test
@@ -260,14 +261,42 @@ ensuredir d = do
 	e <- doesDirectoryExist d
 	unless e $
 		createDirectory d
-	
-{- Prevent global git configs from affecting the test suite. -}
-isolateGitConfig :: IO a -> IO a
-isolateGitConfig a = Utility.Tmp.Dir.withTmpDir "testhome" $ \tmphome -> do
+
+{- This is the only place in the test suite that can use setEnv.
+ - Using it elsewhere can conflict with tasty's use of getEnv, which can
+ - happen concurrently with a test case running, and would be a problem
+ - since setEnv is not thread safe. This is run before tasty. -}
+setTestEnv :: IO a -> IO a
+setTestEnv a = Utility.Tmp.Dir.withTmpDir "testhome" $ \tmphome -> do
 	tmphomeabs <- fromRawFilePath <$> absPath (toRawFilePath tmphome)
+	{- Prevent global git configs from affecting the test suite. -}
 	Utility.Env.Set.setEnv "HOME" tmphomeabs True
 	Utility.Env.Set.setEnv "XDG_CONFIG_HOME" tmphomeabs True
 	Utility.Env.Set.setEnv "GIT_CONFIG_NOSYSTEM" "1" True
+	
+	-- Ensure that the same git-annex binary that is running
+	-- git-annex test is at the front of the PATH.
+	p <- Utility.Env.getEnvDefault "PATH" ""
+	pp <- Annex.Path.programPath
+	Utility.Env.Set.setEnv "PATH" (takeDirectory pp ++ [searchPathSeparator] ++ p) True
+	
+	-- Avoid git complaining if it cannot determine the user's
+	-- email address, or exploding if it doesn't know the user's name.
+	Utility.Env.Set.setEnv "GIT_AUTHOR_EMAIL" "test@example.com" True
+	Utility.Env.Set.setEnv "GIT_AUTHOR_NAME" "git-annex test" True
+	Utility.Env.Set.setEnv "GIT_COMMITTER_EMAIL" "test@example.com" True
+	Utility.Env.Set.setEnv "GIT_COMMITTER_NAME" "git-annex test" True
+	-- force gpg into batch mode for the tests
+	Utility.Env.Set.setEnv "GPG_BATCH" "1" True
+	-- Make git and git-annex access ssh remotes on the local
+	-- filesystem, without using ssh at all.
+	Utility.Env.Set.setEnv "GIT_SSH_COMMAND" "git-annex test --fakessh --" True
+	Utility.Env.Set.setEnv "GIT_ANNEX_USE_GIT_SSH" "1" True
+
+	-- Record top directory.
+	currdir <- getCurrentDirectory
+	Utility.Env.Set.setEnv "TOPDIR" currdir True
+	
 	a
 
 removeDirectoryForCleanup :: FilePath -> IO ()
@@ -455,7 +484,7 @@ data TestMode = TestMode
 	, adjustedUnlockedBranch :: Bool
 	, annexVersion :: Types.RepoVersion.RepoVersion
 	, keepFailures :: Bool
-	} deriving (Read, Show)
+	} deriving (Show)
 
 testMode :: TestOptions -> Types.RepoVersion.RepoVersion -> TestMode
 testMode opts v = TestMode
@@ -471,47 +500,32 @@ hasUnlockedFiles m = unlockedFiles m || adjustedUnlockedBranch m
 withTestMode :: TestMode -> TestTree -> TestTree
 withTestMode testmode = withResource prepare release . const
   where
-	prepare = do
-		setTestMode testmode
-		setmainrepodir =<< newmainrepodir
+	prepare = setTestMode testmode
 	release _ = noop
 
+{- The current test mode is stored here while a test is running.
+ -
+ - Only one test can be running at a time by a process; running a
+ - test also involves chdir into a test repository.
+ -}
+{-# NOINLINE currentTestMode #-}
+currentTestMode :: TMVar TestMode
+currentTestMode = unsafePerformIO newEmptyTMVarIO
+
+currentMainRepoDir :: TMVar FilePath
+currentMainRepoDir = unsafePerformIO newEmptyTMVarIO
+
 setTestMode :: TestMode -> IO ()
 setTestMode testmode = do
-	currdir <- getCurrentDirectory
-	p <- Utility.Env.getEnvDefault "PATH" ""
-	pp <- Annex.Path.programPath
-
-	mapM_ (\(var, val) -> Utility.Env.Set.setEnv var val True)
-		-- Ensure that the same git-annex binary that is running
-		-- git-annex test is at the front of the PATH.
-		[ ("PATH", takeDirectory pp ++ [searchPathSeparator] ++ p)
-		, ("TOPDIR", currdir)
-		-- Avoid git complaining if it cannot determine the user's
-		-- email address, or exploding if it doesn't know the user's
-		-- name.
-		, ("GIT_AUTHOR_EMAIL", "test@example.com")
-		, ("GIT_AUTHOR_NAME", "git-annex test")
-		, ("GIT_COMMITTER_EMAIL", "test@example.com")
-		, ("GIT_COMMITTER_NAME", "git-annex test")
-		-- force gpg into batch mode for the tests
-		, ("GPG_BATCH", "1")
-		-- Make git and git-annex access ssh remotes on the local
-		-- filesystem, without using ssh at all.
-		, ("GIT_SSH_COMMAND", "git-annex test --fakessh --")
-		, ("GIT_ANNEX_USE_GIT_SSH", "1")
-		, ("TESTMODE", show testmode)
-		]
-				
-runFakeSsh :: [String] -> IO ()
-runFakeSsh ("-n":ps) = runFakeSsh ps
-runFakeSsh (_host:cmd:[]) =
-	withCreateProcess (shell cmd) $
-		\_ _ _ pid -> exitWith =<< waitForProcess pid
-runFakeSsh ps = error $ "fake ssh option parse error: " ++ show ps
+	atomically $ do
+		_ <- tryTakeTMVar currentTestMode
+		putTMVar currentTestMode testmode
+	setmainrepodir =<< newmainrepodir
 
 getTestMode :: IO TestMode
-getTestMode = Prelude.read <$> Utility.Env.getEnvDefault "TESTMODE" ""
+getTestMode = atomically (tryReadTMVar currentTestMode) >>= \case
+	Just tm -> return tm
+	Nothing -> error "getTestMode without setTestMode"
 
 setupTestMode :: IO ()
 setupTestMode = do
@@ -528,12 +542,15 @@ changeToTopDir t = do
 tmpdir :: String
 tmpdir = ".t"
 
-mainrepodir :: IO FilePath
-mainrepodir = Utility.Env.getEnvDefault "MAINREPODIR"
-	(giveup "MAINREPODIR not set")
-
 setmainrepodir :: FilePath -> IO ()
-setmainrepodir d = Utility.Env.Set.setEnv "MAINREPODIR" d True
+setmainrepodir mrd = atomically $ do
+	_ <- tryTakeTMVar currentMainRepoDir
+	putTMVar currentMainRepoDir mrd
+
+mainrepodir :: IO FilePath
+mainrepodir = atomically (tryReadTMVar currentMainRepoDir) >>= \case
+	Just tm -> return tm
+	Nothing -> error "mainrepodir without setmainrepodir"
 
 newmainrepodir :: IO FilePath
 newmainrepodir = go (0 :: Int)
@@ -676,6 +693,13 @@ make_writeable d = void $
 	Utility.Process.Transcript.processTranscript
 		"chmod" ["-R", "u+w", d] Nothing
 
+runFakeSsh :: [String] -> IO ()
+runFakeSsh ("-n":ps) = runFakeSsh ps
+runFakeSsh (_host:cmd:[]) =
+	withCreateProcess (shell cmd) $
+		\_ _ _ pid -> exitWith =<< waitForProcess pid
+runFakeSsh ps = error $ "fake ssh option parse error: " ++ show ps
+
 {- Tests each TestTree in parallel, and exits with succcess/failure.
  -
  - Tasty supports parallel tests, but this does not use it, because
diff --git a/doc/todo/test_suite_unsafe_use_of_setEnv.mdwn b/doc/todo/test_suite_unsafe_use_of_setEnv.mdwn
index 7327a30c9d..859d9ea428 100644
--- a/doc/todo/test_suite_unsafe_use_of_setEnv.mdwn
+++ b/doc/todo/test_suite_unsafe_use_of_setEnv.mdwn
@@ -19,4 +19,4 @@ There is also Utility.Gpg.testHarness, which sets GNUPGHOME. It seems that
 instead, every place that git-annex is run inside the gpg test harness
 would need to add GNUPGHOME to the environment of the git-annex process.
 
-> Fixed this part to not setEnv. --[[Joey]]
+> [[fixed|done]] --[[Joey]] 

get got stuck
diff --git a/doc/bugs/get_is_busy_doing_nothing.mdwn b/doc/bugs/get_is_busy_doing_nothing.mdwn
new file mode 100644
index 0000000000..ebdeca51d3
--- /dev/null
+++ b/doc/bugs/get_is_busy_doing_nothing.mdwn
@@ -0,0 +1,81 @@
+### Please describe the problem.
+
+spotted that our backup job, running git-annex 10.20220322-g7b64dea, had not exited for a number of days. In htop I see:
+
+
+```
+ 381205 dandi      20   0  169M  4276  4272 S  0.0  0.0  0:05.15 │                    └─ python -m tools.backups2datalad -l DEBUG -J 5 --target /mnt/backup/dandi/dandisets populate dandi-dandisets-dropbox
+ 389485 dandi      20   0 1026G 1140M 16208 S 171.  1.8     155h │                       └─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389486 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 17:33.20 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389488 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 22:07.69 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389490 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 24:49.46 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389494 dandi      20   0 10636  1968  1964 S  0.0  0.0  0:00.00 │                          ├─ git --git-dir=.git --work-tree=. --literal-pathspecs -c annex.retry=3 cat-file --batch
+ 389496 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 13:41.26 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389497 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 10:47.33 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389498 dandi      20   0 1026G 1140M 16208 S 43.1  1.8 36h53:20 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389499 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 27:15.88 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389500 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 32:34.61 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389501 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 25:05.81 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389506 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 26:42.46 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389507 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 28:43.57 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389510 dandi      20   0 10636  1976  1972 S  0.0  0.0  0:00.02 │                          ├─ git --git-dir=.git --work-tree=. --literal-pathspecs -c annex.retry=3 cat-file --batch
+ 389511 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 20:59.64 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389512 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 25:06.37 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389513 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 33:52.81 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389514 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 29:43.56 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389516 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 23:44.52 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389517 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 17:07.65 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389519 dandi      20   0 1026G 1140M 16208 S 43.1  1.8 36h16:00 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389520 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 18:49.32 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389521 dandi      20   0 1026G 1140M 16208 S  0.0  1.8  0:00.02 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389522 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 18:45.09 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389524 dandi      20   0 1026G 1140M 16208 S 41.8  1.8 36h32:16 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389528 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 20:00.35 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389556 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 24:22.72 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 389759 dandi      20   0 1026G 1140M 16208 S  0.0  1.8  0:00.04 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 390392 dandi      20   0 1026G 1140M 16208 S 42.5  1.8 37h16:15 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 391588 dandi      20   0 1026G 1140M 16208 S  0.0  1.8 20:03.40 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 394739 dandi      20   0 1026G 1140M 16208 S  0.0  1.8  0:00.00 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 441349 dandi      20   0 1026G 1140M 16208 S  0.0  1.8  9:46.65 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+ 444725 dandi      20   0 1026G 1140M 16208 S  0.0  1.8  7:42.72 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+1910907 dandi      20   0 1026G 1140M 16208 S  0.0  1.8  0:00.00 │                          ├─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+2277116 dandi      20   0 1026G 1140M 16208 S  0.0  1.8  0:00.00 │                          └─ git-annex get -c annex.retry=3 --jobs 5 --from=web --not --in dandi-dandisets-dropbox --and --not --in here
+
+```
+
+relevant open files in that git repo:
+
+```
+(dandisets) dandi@drogon:/mnt/backup/dandi/dandisets/000233$ lsof -p 389485 | grep 000
+lsof: WARNING: can't stat() btrfs file system /mnt/backup/docker/btrfs
+      Output information may be incomplete.
+git-annex 389485 dandi  cwd       DIR      0,40         266  88559549 /mnt/backup/dandi/dandisets/000233
+git-annex 389485 dandi  mem       REG       9,0       93000   3146348 /lib/x86_64-linux-gnu/libresolv-2.31.so
+git-annex 389485 dandi  mem-r     REG      0,35             118071211 /mnt/backup/dandi/dandisets/000233/.git/annex/keysdb/db-shm (path dev=0,40)
+git-annex 389485 dandi   29u      REG      0,40      139264  88559612 /mnt/backup/dandi/dandisets/000233/.git/annex/keysdb/db
+git-annex 389485 dandi   30u      REG      0,40           0 117504617 /mnt/backup/dandi/dandisets/000233/.git/annex/transfer/download/00000000-0000-0000-0000-000000000001/lck.SHA256E-s124312933165--d769cd7cbec2012515085d63eee594299d7411500c0bb9c13d10c0cd68d5ddce.nwb
+git-annex 389485 dandi   32u      REG      0,40           0 118071210 /mnt/backup/dandi/dandisets/000233/.git/annex/keysdb/db-wal
+git-annex 389485 dandi   34u      REG      0,40       32768 118071211 /mnt/backup/dandi/dandisets/000233/.git/annex/keysdb/db-shm
+git-annex 389485 dandi   50w      REG      0,40 47012344309 117504625 /mnt/backup/dandi/dandisets/000233/.git/annex/tmp/SHA256E-s124312933165--d769cd7cbec2012515085d63eee594299d7411500c0bb9c13d10c0cd68d5ddce.nwb
+```
+
+That key is available from:
+
+```
+(dandisets) dandi@drogon:/mnt/backup/dandi/dandisets/000233$ git annex whereis --key SHA256E-s124312933165--d769cd7cbec2012515085d63eee594299d7411500c0bb9c13d10c0cd68d5ddce.nwb
+whereis SHA256E-s124312933165--d769cd7cbec2012515085d63eee594299d7411500c0bb9c13d10c0cd68d5ddce.nwb (1 copy) 
+        00000000-0000-0000-0000-000000000001 -- web
+
+  web: https://api.dandiarchive.org/api/assets/5304ce04-3a87-400c-a484-aa889156a505/download/
+  web: https://dandiarchive.s3.amazonaws.com/blobs/c84/640/c846401f-10bf-452f-b26a-82e3d905556c?versionId=naDNt0WozdY8oi6M9L6MGhfsvnRuW6RJ
+ok
+```
+where the first one is redirect to the second one. And I think it had not progressed from that 47012344309 size in quite awhile (will check again in an hour).
+
+I would have expected it to fail may be (and possibly `retry` if annex.retry) but not really get stuck.
+
+Is there any diagnostic information I should collect to help troubleshooting the issue, or could I just kill/restart that process?
+
+
+[[!meta author=yoh]]
+[[!tag projects/datalad]]

avoid setEnv while testing gpg
setEnv is not thread safe and could cause a getEnv by another thread to
segfault, or perhaps other had behavior.
Sponsored-by: Dartmouth College's Datalad project
diff --git a/Crypto.hs b/Crypto.hs
index 751c1cd256..6750284ee1 100644
--- a/Crypto.hs
+++ b/Crypto.hs
@@ -3,7 +3,7 @@
  - Currently using gpg; could later be modified to support different
  - crypto backends if neccessary.
  -
- - Copyright 2011-2020 Joey Hess <id@joeyh.name>
+ - Copyright 2011-2022 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -22,7 +22,8 @@ module Crypto (
 	genSharedCipher,
 	genSharedPubKeyCipher,
 	updateCipherKeyIds,
-	decryptCipher,		
+	decryptCipher,
+	decryptCipher',
 	encryptKey,
 	isEncKey,
 	feedFile,
@@ -147,10 +148,13 @@ encryptCipher cmd c cip variant (KeyIds ks) = do
 
 {- Decrypting an EncryptedCipher is expensive; the Cipher should be cached. -}
 decryptCipher :: LensGpgEncParams c => Gpg.GpgCmd -> c -> StorableCipher -> IO Cipher
-decryptCipher _ _ (SharedCipher t) = return $ Cipher t
-decryptCipher _ _ (SharedPubKeyCipher t _) = return $ MacOnlyCipher t
-decryptCipher cmd c (EncryptedCipher t variant _) =
-	mkCipher <$> Gpg.pipeStrict cmd params t
+decryptCipher cmd c cip = decryptCipher' cmd Nothing c cip
+
+decryptCipher' :: LensGpgEncParams c => Gpg.GpgCmd -> Maybe [(String, String)] -> c -> StorableCipher -> IO Cipher
+decryptCipher' _ _ _ (SharedCipher t) = return $ Cipher t
+decryptCipher' _ _ _ (SharedPubKeyCipher t _) = return $ MacOnlyCipher t
+decryptCipher' cmd environ c (EncryptedCipher t variant _) =
+	mkCipher <$> Gpg.pipeStrict' cmd params environ t
   where
 	mkCipher = case variant of
 		Hybrid -> Cipher
diff --git a/Test.hs b/Test.hs
index a8d01bf6df..62e92e8d45 100644
--- a/Test.hs
+++ b/Test.hs
@@ -1643,7 +1643,7 @@ test_uninit = intmpclonerepo $ do
 	git_annex "get" [] "get"
 	annexed_present annexedfile
 	-- any exit status is accepted; does abnormal exit
-	git_annex' (const True) "uninit" [] "uninit"
+	git_annex'' (const True) "uninit" [] Nothing "uninit"
 	checkregularfile annexedfile
 	doesDirectoryExist ".git" @? ".git vanished in uninit"
 
@@ -1758,8 +1758,8 @@ test_borg_remote = when BuildInfo.borg $ do
 	borgdirparent <- fromRawFilePath <$> (absPath . toRawFilePath =<< tmprepodir)
 	let borgdir = borgdirparent </> "borgrepo"
 	intmpclonerepo $ do
-		testProcess "borg" ["init", borgdir, "-e", "none"] (== True) "borg init"
-		testProcess "borg" ["create", borgdir++"::backup1", "."] (== True) "borg create"
+		testProcess "borg" ["init", borgdir, "-e", "none"] Nothing (== True) "borg init"
+		testProcess "borg" ["create", borgdir++"::backup1", "."] Nothing (== True) "borg create"
 
 		git_annex "initremote" (words $ "borg type=borg borgrepo="++borgdir) "initremote"
 		git_annex "sync" ["borg"] "sync borg"
@@ -1769,7 +1769,7 @@ test_borg_remote = when BuildInfo.borg $ do
 		annexed_present annexedfile
 		git_annex_expectoutput "find" ["--in=borg"] []
 		
-		testProcess "borg" ["create", borgdir++"::backup2", "."] (== True) "borg create"
+		testProcess "borg" ["create", borgdir++"::backup2", "."] Nothing (== True) "borg create"
 		git_annex "sync" ["borg"] "sync borg after getting file"
 		git_annex_expectoutput "find" ["--in=borg"] [annexedfile]
 
@@ -1808,9 +1808,7 @@ test_crypto = do
 		let gpgtmp = if length relgpgtmp < length absgpgtmp
 			then relgpgtmp 
 			else absgpgtmp
-		Utility.Gpg.testTestHarness gpgtmp gpgcmd
-			@? "test harness self-test failed"
-		void $ Utility.Gpg.testHarness gpgtmp gpgcmd $ do
+		void $ Utility.Gpg.testHarness gpgtmp gpgcmd $ \environ -> do
 			createDirectory "dir"
 			let initps =
 				[ "foo"
@@ -1821,13 +1819,13 @@ test_crypto = do
 				] ++ if scheme `elem` ["hybrid","pubkey"]
 					then ["keyid=" ++ Utility.Gpg.testKeyId]
 					else []
-			git_annex "initremote" initps "initremote"
-			git_annex_shouldfail "initremote" initps "initremote should not work when run twice in a row"
-			git_annex "enableremote" initps "enableremote"
-			git_annex "enableremote" initps "enableremote when run twice in a row"
-			git_annex "get" [annexedfile] "get of file"
+			git_annex' "initremote" initps (Just environ) "initremote"
+			git_annex_shouldfail' "initremote" initps (Just environ) "initremote should not work when run twice in a row"
+			git_annex' "enableremote" initps (Just environ) "enableremote"
+			git_annex' "enableremote" initps (Just environ) "enableremote when run twice in a row"
+			git_annex' "get" [annexedfile] (Just environ) "get of file"
 			annexed_present annexedfile
-			git_annex "copy" [annexedfile, "--to", "foo"] "copy --to encrypted remote"
+			git_annex' "copy" [annexedfile, "--to", "foo"] (Just environ) "copy --to encrypted remote"
 			(c,k) <- annexeval $ do
 				uuid <- Remote.nameToUUID "foo"
 				rs <- Logs.Remote.readRemoteLog
@@ -1836,18 +1834,18 @@ test_crypto = do
 			let key = if scheme `elem` ["hybrid","pubkey"]
 					then Just $ Utility.Gpg.KeyIds [Utility.Gpg.testKeyId]
 					else Nothing
-			testEncryptedRemote scheme key c [k] @? "invalid crypto setup"
+			testEncryptedRemote environ scheme key c [k] @? "invalid crypto setup"
 	
 			annexed_present annexedfile
-			git_annex "drop" [annexedfile, "--numcopies=2"] "drop"
+			git_annex' "drop" [annexedfile, "--numcopies=2"] (Just environ) "drop"
 			annexed_notpresent annexedfile
-			git_annex "move" [annexedfile, "--from", "foo"] "move --from encrypted remote"
+			git_annex' "move" [annexedfile, "--from", "foo"] (Just environ) "move --from encrypted remote"
 			annexed_present annexedfile
-			git_annex_shouldfail "drop" [annexedfile, "--numcopies=2"] "drop should not be allowed with numcopies=2"
+			git_annex_shouldfail' "drop" [annexedfile, "--numcopies=2"] (Just environ) "drop should not be allowed with numcopies=2"
 			annexed_present annexedfile
 	{- Ensure the configuration complies with the encryption scheme, and
 	 - that all keys are encrypted properly for the given directory remote. -}
-	testEncryptedRemote scheme ks c keys = case Remote.Helper.Encryptable.extractCipher pc of
+	testEncryptedRemote environ scheme ks c keys = case Remote.Helper.Encryptable.extractCipher pc of
 		Just cip@Crypto.SharedCipher{} | scheme == "shared" && isNothing ks ->
 			checkKeys cip Nothing
 		Just cip@(Crypto.EncryptedCipher encipher v ks')
@@ -1860,18 +1858,18 @@ test_crypto = do
 		keysMatch (Utility.Gpg.KeyIds ks') =
 			maybe False (\(Utility.Gpg.KeyIds ks2) ->
 					sort (nub ks2) == sort (nub ks')) ks
-		checkCipher encipher = Utility.Gpg.checkEncryptionStream gpgcmd encipher . Just
+		checkCipher encipher = Utility.Gpg.checkEncryptionStream gpgcmd (Just environ) encipher . Just
 		checkScheme Types.Crypto.Hybrid = scheme == "hybrid"
 		checkScheme Types.Crypto.PubKey = scheme == "pubkey"
 		checkKeys cip mvariant = do
 			dummycfg <- Types.GitConfig.dummyRemoteGitConfig
 			let encparams = (Types.Remote.ParsedRemoteConfig mempty mempty, dummycfg)
-			cipher <- Crypto.decryptCipher gpgcmd encparams cip
+			cipher <- Crypto.decryptCipher' gpgcmd (Just environ) encparams cip
 			files <- filterM doesFileExist $
 				map ("dir" </>) $ concatMap (serializeKeys cipher) keys
 			return (not $ null files) <&&> allM (checkFile mvariant) files
 		checkFile mvariant filename =
-			Utility.Gpg.checkEncryptionFile gpgcmd filename $
+			Utility.Gpg.checkEncryptionFile gpgcmd (Just environ) filename $
 				if mvariant == Just Types.Crypto.PubKey then ks else Nothing
 		serializeKeys cipher = map fromRawFilePath . 
 			Annex.Locations.keyPaths .
diff --git a/Test/Framework.hs b/Test/Framework.hs
index 3bb9c2d1d6..94b06284cf 100644
--- a/Test/Framework.hs
+++ b/Test/Framework.hs
@@ -64,32 +64,40 @@ import qualified Command.Uninit
 
 -- Run a process. The output and stderr is captured, and is only
 -- displayed if the process does not return the expected value.
-testProcess :: String -> [String] -> (Bool -> Bool) -> String -> Assertion
-testProcess command params expectedret faildesc = do
-	(transcript, ret) <- Utility.Process.Transcript.processTranscript command params Nothing
+testProcess :: String -> [String] -> Maybe [(String, String)] -> (Bool -> Bool) -> String -> Assertion
+testProcess command params environ expectedret faildesc = do
+	let p = (proc command params) { env = environ }
+	(transcript, ret) <- Utility.Process.Transcript.processTranscript' p Nothing
 	(expectedret ret) @? (faildesc ++ " failed (transcript follows)\n" ++ transcript)
 
 -- Run git. (Do not use to run git-annex as the one being tested
 -- may not be in path.)
 git :: String -> [String] -> String -> Assertion
-git command params = testProcess "git" (command:params) (== True)
+git command params = testProcess "git" (command:params) Nothing (== True)
 
 -- For when git is expected to fail.
 git_shouldfail :: String -> [String] -> String -> Assertion
-git_shouldfail command params = testProcess "git" (command:params) (== False)
+git_shouldfail command params = testProcess "git" (command:params) Nothing (== False)
 
 -- Run git-annex.
 git_annex :: String -> [String] -> String -> Assertion
-git_annex = git_annex' (== True)
+git_annex command params faildesc = git_annex' command params Nothing faildesc
+
+-- Runs git-annex with some environment.
+git_annex' :: String -> [String] -> Maybe [(String, String)] -> String -> Assertion
+git_annex' = git_annex'' (== True)
 
 -- For when git-annex is expected to fail.
 git_annex_shouldfail :: String -> [String] -> String -> Assertion
-git_annex_shouldfail = git_annex' (== False)
+git_annex_shouldfail command params faildesc = git_annex_shouldfail' command params Nothing faildesc
+
+git_annex_shouldfail' :: String -> [String] -> Maybe [(String, String)] -> String -> Assertion
+git_annex_shouldfail' = git_annex'' (== False)
 
-git_annex' :: (Bool -> Bool) -> String -> [String] -> String -> Assertion
-git_annex' expectedret command params faildesc = do
+git_annex'' :: (Bool -> Bool) -> String -> [String] -> Maybe [(String, String)] -> String -> Assertion
+git_annex'' expectedret command params environ faildesc = do

(Diff truncated)
add comment I made earlier
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_7_0ccaf798086a8c9511d3733b12b24c8b._comment b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_7_0ccaf798086a8c9511d3733b12b24c8b._comment
new file mode 100644
index 0000000000..8b2aa82175
--- /dev/null
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_7_0ccaf798086a8c9511d3733b12b24c8b._comment
@@ -0,0 +1,13 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 7"""
+ date="2022-05-16T19:17:34Z"
+ content="""
+Made `git-annex fsck` move the object files to the preferred location for
+the repository type. 
+
+You can run it with --fast and it should solve your problem. I'm still not
+certain what circumstance led to you having the problem, but unless I hear
+back I'll assume it was something like an old version of git-annex. So will
+close this bug with this as the fix..
+"""]]

comment and open a todo
diff --git a/doc/bugs/v8_conflict_resolution___40__adjusted_branch__41___teststuck/comment_1_d530fb679b76e995c466fe17c3aab830._comment b/doc/bugs/v8_conflict_resolution___40__adjusted_branch__41___teststuck/comment_1_d530fb679b76e995c466fe17c3aab830._comment
new file mode 100644
index 0000000000..0f51814161
--- /dev/null
+++ b/doc/bugs/v8_conflict_resolution___40__adjusted_branch__41___teststuck/comment_1_d530fb679b76e995c466fe17c3aab830._comment
@@ -0,0 +1,11 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2022-05-18T18:05:04Z"
+ content="""
+That test case only runs a few simple git-annex commands, there is not anything
+that takes a long time or that can loop.
+
+One possibility is that [[todo/test_suite_unsafe_use_of_setEnv]] could have
+led to some undefined behavior that caused it to hang.
+"""]]
diff --git a/doc/todo/test_suite_unsafe_use_of_setEnv.mdwn b/doc/todo/test_suite_unsafe_use_of_setEnv.mdwn
new file mode 100644
index 0000000000..3d95a9e99b
--- /dev/null
+++ b/doc/todo/test_suite_unsafe_use_of_setEnv.mdwn
@@ -0,0 +1,20 @@
+The test suite uses setEnv in several places. (No other part of git-annex
+does, basically.) However, setEnv is not thread safe, if another thread
+runs getEnv at the same time it can crash. Or another setEnv, probably.
+
+This actually does segfault the test suite runners from time to time.
+The test suite currently notices if there was a segfault and re-runs it.
+While that works well enough, it's always possible there could be some
+other behavior than just a segfault.
+
+See <https://gitlab.haskell.org/ghc/ghc/-/issues/21243> for a test case and
+some analysis. See also <https://github.com/UnkindPartition/tasty/issues/326>. 
+Tasty uses getEnv, and that can apparently run while a test case is using setEnv.
+
+The main places that setEnv is used are in setTestMode. Fixing it will mean
+plumbing a value all through the test suite. Or perhaps, using a single
+toplevel MVar would be ok, since tests don't run concurrently?
+
+There is also Utility.Gpg.testHarness, which sets GNUPGHOME. It seems that
+instead, every place that git-annex is run inside the gpg test harness
+would need to add GNUPGHOME to the environment of the git-annex process.

initial report on stuck conflict resolution (adjusted branch)
diff --git a/doc/bugs/v8_conflict_resolution___40__adjusted_branch__41___teststuck.mdwn b/doc/bugs/v8_conflict_resolution___40__adjusted_branch__41___teststuck.mdwn
new file mode 100644
index 0000000000..c87880ef95
--- /dev/null
+++ b/doc/bugs/v8_conflict_resolution___40__adjusted_branch__41___teststuck.mdwn
@@ -0,0 +1,57 @@
+### Please describe the problem.
+
+today's build/git-annex test on smaug failed testing due to running out of time (3600 seconds were given):
+
+```
++ source testlib.sh
++ workdir_base /mnt/datasets/datalad/git-annex-build-client
++ WORKDIR_BASE=/mnt/datasets/datalad/git-annex-build-client
++ mkdir -p /mnt/datasets/datalad/git-annex-build-client/698
++ trap 'cd "$WORKDIR_BASE" && chmod -R +w "$BUILDNO" && rm -rf "$BUILDNO"' EXIT
++ cd /mnt/datasets/datalad/git-annex-build-client/698
++ git annex version
+git-annex version: 10.20220504+git37-g514f50e5b-1~ndall+1
+build flags: Assistant Webapp Pairing Inotify DBus DesktopNotify TorrentParser MagicMime Feeds Testsuite S3 WebDAV
+dependency versions: aws-0.22 bloomfilter-2.0.1.0 cryptonite-0.26 DAV-1.3.4 feed-1.3.0.1 ghc-8.8.4 http-client-0.6.4.1 persistent-sqlite-2.10.6.2 torrent-10000.1.1 uuid-1.3.13 yesod-1.6.1.0
+key/value backends: SHA256E SHA256 SHA512E SHA512 SHA224E SHA224 SHA384E SHA384 SHA3_256E SHA3_256 SHA3_512E SHA3_512 SHA3_224E SHA3_224 SHA3_384E SHA3_384 SKEIN256E SKEIN256 SKEIN512E SKEIN512 BLAKE2B256E BLAKE2B256 BLAKE2B512E BLAKE2B512 BLAKE2B160E BLAKE2B160 BLAKE2B224E BLAKE2B224 BLAKE2B384E BLAKE2B384 BLAKE2BP512E BLAKE2BP512 BLAKE2S256E BLAKE2S256 BLAKE2S160E BLAKE2S160 BLAKE2S224E BLAKE2S224 BLAKE2SP256E BLAKE2SP256 BLAKE2SP224E BLAKE2SP224 SHA1E SHA1 MD5E MD5 WORM URL X*
+remote types: git gcrypt p2p S3 bup directory rsync web bittorrent webdav adb tahoe glacier ddar git-lfs httpalso borg hook external
+operating system: linux x86_64
+supported repository versions: 8 9 10
+upgrade supported from repository versions: 0 1 2 3 4 5 6 7 8 9 10
++ timeout 3600 git annex test
+Tests
+  Tasty
+    tasty self-check:                       
+...
+All 6 tests passed (115.95s)
+Tests
+  Tasty
+    tasty self-check:                      OK
+      +++ OK, passed 1 test.
+  Repo Tests v8 adjusted unlocked branch
+    Init Tests
+      init:                                OK (3.21s)
+      add:                                 OK (11.10s)
+    conflict resolution (adjusted branch): + cd /mnt/datasets/datalad/git-annex-build-client
++ chmod -R +w 698
++ rm -rf 698
+```
+
+didn't happen before AFAIK:
+```
+(git)smaug:/mnt/datasets/datalad/ci/git-annex-ci-client-jobs/builds/2022/05[master]git
+$> git grep 'conflict resolution (adj' | grep -v OK
+result-smaug-698/handle-result.yaml-122-ffec0fe3-success/result-smaug-698/git-annex.log:    conflict resolution (adjusted branch): + cd /mnt/datasets/datalad/git-annex-build-client
+```
+
+and was ok on subsequent run:
+
+```
+(git)smaug:/mnt/datasets/datalad/ci/git-annex-ci-client-jobs/builds/2022/05[master]git
+$> git grep 'conflict resolution (adj' result-smaug-699 
+result-smaug-699/handle-result.yaml-123-3c5c350a-success/result-smaug-699/git-annex.log:    conflict resolution (adjusted branch): OK (65.14s)
+result-smaug-699/handle-result.yaml-123-3c5c350a-success/result-smaug-699/git-annex.log:    conflict resolution (adjusted branch): OK (38.77s)
+result-smaug-699/handle-result.yaml-123-3c5c350a-success/result-smaug-699/git-annex.log:    conflict resolution (adjusted branch): OK (51.34s)
+```
+
+I don't know if that was somehow a fluke that may be testing of git-annex is in  general close to an hour on smaug, or it was too busy -- but unlikely.  I am adding Elapsed time reporting https://github.com/datalad/git-annex/pull/126

Added a comment
diff --git a/doc/forum/Different_preferred_content_for_subdirs/comment_3_c6764ea73c13b59645f1b378e77984b6._comment b/doc/forum/Different_preferred_content_for_subdirs/comment_3_c6764ea73c13b59645f1b378e77984b6._comment
new file mode 100644
index 0000000000..9e09b840bb
--- /dev/null
+++ b/doc/forum/Different_preferred_content_for_subdirs/comment_3_c6764ea73c13b59645f1b378e77984b6._comment
@@ -0,0 +1,13 @@
+[[!comment format=mdwn
+ username="MatusGoljer1"
+ avatar="http://cdn.libravatar.org/avatar/8152eed1d594c570563ed46e7fd8356f"
+ subject="comment 3"
+ date="2022-05-16T23:37:55Z"
+ content="""
+Hey Joey, thanks for the response.  The syntax without \" was a big surprise!  Removing the quotes seems to make things work as expected.
+
+Plus it seems a big problem was also an incompatibility of the rclone special remote helper and recent rclone update where they changed some output formats and the helper was reporting incorrect results.  There is a bunch of forks with various fixes, it's a shame the original author disappeared.
+
+Thank you so much for your work on git annex!
+Matus.
+"""]]

make fsck normalize object locations
The purpose of this is to fix situations where the annex object file is
stored in a directory structure other than where annex symlinks point to.
But it will also move object files from the hashdirmixed back to
hashdirlower if the repo configuration makes that the normal location.
It would have been more work to avoid that than to let it do it.
Sponsored-by: Dartmouth College's Datalad project
diff --git a/Annex/Content.hs b/Annex/Content.hs
index 5b5b798dca..62471f7305 100644
--- a/Annex/Content.hs
+++ b/Annex/Content.hs
@@ -64,6 +64,7 @@ module Annex.Content (
 	isKeyUnlockedThin,
 	getKeyStatus,
 	getKeyFileStatus,
+	cleanObjectDirs,
 ) where
 
 import System.IO.Unsafe (unsafeInterleaveIO)
@@ -610,6 +611,11 @@ cleanObjectLoc key cleaner = do
 	cleaner
 	cleanObjectDirs file
 
+{- Given a filename inside the object directory, tries to remove the object
+ - directory, as well as the object hash directories.
+ - 
+ - Does nothing if the object directory is not empty, and does not
+ - throw an exception if it's unable to remove a directory. -}
 cleanObjectDirs :: RawFilePath -> Annex ()
 cleanObjectDirs f = do
 	HashLevels n <- objectHashLevels <$> Annex.getGitConfig
@@ -619,7 +625,8 @@ cleanObjectDirs f = do
 	go file n = do
 		let dir = parentDir file
 		maybe noop (const $ go dir (n-1))
-			<=< catchMaybeIO $ removeDirectory (fromRawFilePath dir)
+			<=< catchMaybeIO $ tryWhenExists $
+				removeDirectory (fromRawFilePath dir)
 
 {- Removes a key's file from .git/annex/objects/ -}
 removeAnnex :: ContentRemovalLock -> Annex ()
diff --git a/Annex/Content/Presence.hs b/Annex/Content/Presence.hs
index 86fde17e44..ff12c865c2 100644
--- a/Annex/Content/Presence.hs
+++ b/Annex/Content/Presence.hs
@@ -19,6 +19,7 @@ module Annex.Content.Presence (
 	isUnmodified',
 	isUnmodifiedCheap,
 	withContentLockFile,
+	contentLockFile,
 ) where
 
 import Annex.Content.Presence.LowLevel
diff --git a/Annex/Locations.hs b/Annex/Locations.hs
index 25dd4a5f75..8cac9a9dac 100644
--- a/Annex/Locations.hs
+++ b/Annex/Locations.hs
@@ -16,6 +16,7 @@ module Annex.Locations (
 	objectDir,
 	objectDir',
 	gitAnnexLocation,
+	gitAnnexLocation',
 	gitAnnexLocationDepth,
 	gitAnnexLink,
 	gitAnnexLinkCanonical,
@@ -172,14 +173,17 @@ gitAnnexLocationDepth config = hashlevels + 1
  - be stored.
  -}
 gitAnnexLocation :: Key -> Git.Repo -> GitConfig -> IO RawFilePath
-gitAnnexLocation key r config = gitAnnexLocation' key r config
+gitAnnexLocation = gitAnnexLocation' R.doesPathExist
+
+gitAnnexLocation' :: (RawFilePath -> IO Bool) -> Key -> Git.Repo -> GitConfig -> IO RawFilePath
+gitAnnexLocation' checker key r config = gitAnnexLocation'' key r config
 	(annexCrippledFileSystem config)
 	(coreSymlinks config)
-	R.doesPathExist
+	checker
 	(Git.localGitDir r)
 
-gitAnnexLocation' :: Key -> Git.Repo -> GitConfig -> Bool -> Bool -> (RawFilePath -> IO Bool) -> RawFilePath -> IO RawFilePath
-gitAnnexLocation' key r config crippled symlinkssupported checker gitdir
+gitAnnexLocation'' :: Key -> Git.Repo -> GitConfig -> Bool -> Bool -> (RawFilePath -> IO Bool) -> RawFilePath -> IO RawFilePath
+gitAnnexLocation'' key r config crippled symlinkssupported checker gitdir
 	{- Bare repositories default to hashDirLower for new
 	 - content, as it's more portable. But check all locations. -}
 	| Git.repoIsLocalBare r = checkall annexLocationsBare
@@ -207,7 +211,7 @@ gitAnnexLink file key r config = do
 	currdir <- R.getCurrentDirectory
 	let absfile = absNormPathUnix currdir file
 	let gitdir = getgitdir currdir
-	loc <- gitAnnexLocation' key r config False False (\_ -> return True) gitdir
+	loc <- gitAnnexLocation'' key r config False False (\_ -> return True) gitdir
 	toInternalGitPath <$> relPathDirToFile (parentDir absfile) loc
   where
 	getgitdir currdir
diff --git a/CHANGELOG b/CHANGELOG
index 70aeedebba..817dd32687 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -12,6 +12,8 @@ git-annex (10.20220505) UNRELEASED; urgency=medium
   * Special remotes using exporttree=yes and/or importtree=yes now
     checksum content while it is being retrieved, instead of in a separate
     pass at the end.
+  * fsck: Fix situations where the annex object file is stored in a
+    directory structure other than where annex symlinks point to.
 
  -- Joey Hess <id@joeyh.name>  Thu, 05 May 2022 15:08:07 -0400
 
diff --git a/Command/Fsck.hs b/Command/Fsck.hs
index 4de157943f..f34640b087 100644
--- a/Command/Fsck.hs
+++ b/Command/Fsck.hs
@@ -1,6 +1,6 @@
 {- git-annex command
  -
- - Copyright 2010-2021 Joey Hess <id@joeyh.name>
+ - Copyright 2010-2022 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -16,9 +16,11 @@ import qualified Remote
 import qualified Types.Backend
 import qualified Backend
 import Annex.Content
+import Annex.Content.Presence
 import Annex.Content.Presence.LowLevel
 import Annex.Perms
 import Annex.Link
+import Annex.Version
 import Logs.Location
 import Logs.Trust
 import Logs.Activity
@@ -134,6 +136,7 @@ perform key file backend numcopies = do
 	check
 		-- order matters
 		[ fixLink key file
+		, fixObjectLocation key
 		, verifyLocationLog key keystatus ai
 		, verifyRequiredContent key ai
 		, verifyAssociatedFiles key keystatus file
@@ -243,6 +246,58 @@ fixLink key file = do
 			addAnnexLink want file
 		| otherwise = noop
 
+{- A repository that supports symlinks and is not bare may have in the past
+ - been bare, or not supported symlinks. If so, the object may be located
+ - in a directory other than the one where annex symlinks point to. Moves
+ - the object in that case.
+ -
+ - Also if a repository has been converted to bare, or moved to a crippled
+ - filesystem not supporting symlinks, the object file will be moved
+ - to the other location.
+ -}
+fixObjectLocation :: Key -> Annex Bool
+fixObjectLocation key = do
+#ifdef mingw32_HOST_OS
+	-- Windows does not allow locked files to be renamed, but annex
+	-- links are also not used on Windows.
+	return True
+#else
+	loc <- calcRepo (gitAnnexLocation key)
+	idealloc <- calcRepo (gitAnnexLocation' (const (pure True)) key)
+	if loc == idealloc
+		then return True
+		else ifM (liftIO $ R.doesPathExist loc)
+			( moveobjdir loc idealloc
+				`catchNonAsync` \_e -> return True
+			, return True
+			)
+  where
+	moveobjdir src dest = do
+		let srcdir = parentDir src
+		let destdir = parentDir dest
+		showNote "normalizing object location"
+		-- When the content file is moved, it will
+		-- appear to other processes as if it has been removed.
+		-- That should never happen to a process that has used
+		-- lockContentShared, so avoid it by locking the content
+		-- for removal, although it's not really being removed.
+		lockContentForRemoval key (return True) $ \_lck -> do
+			-- Thaw the content directory to allow renaming it.
+			thawContentDir src
+			createAnnexDirectory (parentDir destdir)
+			liftIO $ renameDirectory
+				(fromRawFilePath srcdir)
+				(fromRawFilePath destdir)
+			-- Since the directory was moved, lockContentForRemoval
+			-- will not be able to remove the lock file it
+			-- made. So, remove the lock file here.
+			mlockfile <- contentLockFile key =<< getVersion
+			liftIO $ maybe noop (removeWhenExistsWith R.removeLink) mlockfile
+			freezeContentDir dest
+			cleanObjectDirs src
+			return True
+#endif
+
 {- Checks that the location log reflects the current status of the key,
  - in this repository only. -}
 verifyLocationLog :: Key -> KeyStatus -> ActionItem -> Annex Bool
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn b/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn
index d9a5db3987..024e1b2f7e 100644
--- a/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn
@@ -5,3 +5,5 @@ Checking out `master` branch is not sufficient since `.git/annex/objects` uses d
 [[!tag projects/datalad]]
 

(Diff truncated)
avoid creating content directory when locking content
If the content directory does not exist, then it does not make sense to
lock the content file, as it also does not exist, and so it's ok for the
lock operation to fail.
This avoids potential races where the content file exists but is then
deleted/renamed, while another process sees that it exists and goes to
lock it, resulting in a dangling lock file in an otherwise empty object
directory.
Also renamed modifyContent to modifyContentDir since it is not only
necessarily used for modifying content files, but also other files in
the content directory.
Sponsored-by: Dartmouth College's Datalad project
diff --git a/Annex/Content.hs b/Annex/Content.hs
index 141f8e206a..c81dc5bff6 100644
--- a/Annex/Content.hs
+++ b/Annex/Content.hs
@@ -169,13 +169,13 @@ type ContentLocker = RawFilePath -> Maybe LockFile -> (Annex (Maybe LockHandle),
 posixLocker :: (Maybe FileMode -> LockFile -> Annex (Maybe LockHandle)) -> LockFile -> Annex (Maybe LockHandle)
 posixLocker takelock lockfile = do
 	mode <- annexFileMode
-	modifyContent lockfile $
+	modifyContentDirWhenExists lockfile $
 		takelock (Just mode) lockfile
 #else
 winLocker :: (LockFile -> IO (Maybe LockHandle)) -> ContentLocker
 winLocker takelock _ (Just lockfile) = 
 	let lck = do
-		modifyContent lockfile $
+		modifyContentDirWhenExists lockfile $
 			void $ liftIO $ tryIO $
 				writeFile (fromRawFilePath lockfile) ""
 		liftIO $ takelock lockfile
@@ -407,7 +407,7 @@ moveAnnex key af src = ifM (checkSecureHashes' key)
   where
 	storeobject dest = ifM (liftIO $ R.doesPathExist dest)
 		( alreadyhave
-		, adjustedBranchRefresh af $ modifyContent dest $ do
+		, adjustedBranchRefresh af $ modifyContentDir dest $ do
 			liftIO $ moveFile
 				(fromRawFilePath src)
 				(fromRawFilePath dest)
@@ -452,7 +452,7 @@ linkToAnnex :: Key -> RawFilePath -> Maybe InodeCache -> Annex LinkAnnexResult
 linkToAnnex key src srcic = ifM (checkSecureHashes' key)
 	( do
 		dest <- calcRepo (gitAnnexLocation key)
-		modifyContent dest $ linkAnnex To key src srcic dest Nothing
+		modifyContentDir dest $ linkAnnex To key src srcic dest Nothing
 	, return LinkAnnexFailed
 	)
 
@@ -528,7 +528,7 @@ linkAnnex fromto key src (Just srcic) dest destmode =
 unlinkAnnex :: Key -> Annex ()
 unlinkAnnex key = do
 	obj <- calcRepo (gitAnnexLocation key)
-	modifyContent obj $ do
+	modifyContentDir obj $ do
 		secureErase obj
 		liftIO $ removeWhenExistsWith R.removeLink obj
 
diff --git a/Annex/Content/Presence.hs b/Annex/Content/Presence.hs
index 48c8b92873..86fde17e44 100644
--- a/Annex/Content/Presence.hs
+++ b/Annex/Content/Presence.hs
@@ -112,7 +112,7 @@ inAnnexSafe key = inAnnex' (fromMaybe True) (Just False) go key
 	 - remove the lock file to clean up after ourselves. -}
 	checklock (Just lockfile) contentfile =
 		ifM (liftIO $ doesFileExist (fromRawFilePath contentfile))
-			( modifyContent lockfile $ liftIO $
+			( modifyContentDirWhenExists lockfile $ liftIO $
 				lockShared lockfile >>= \case
 					Nothing -> return is_locked
 					Just lockhandle -> do
diff --git a/Annex/Perms.hs b/Annex/Perms.hs
index 4cecc48e79..5e3de2d16a 100644
--- a/Annex/Perms.hs
+++ b/Annex/Perms.hs
@@ -26,7 +26,8 @@ module Annex.Perms (
 	createContentDir,
 	freezeContentDir,
 	thawContentDir,
-	modifyContent,
+	modifyContentDir,
+	modifyContentDirWhenExists,
 	withShared,
 	hasFreezeHook,
 	hasThawHook,
@@ -290,15 +291,24 @@ createContentDir dest = do
 	dir = parentDir dest
 
 {- Creates the content directory for a file if it doesn't already exist,
- - or thaws it if it does, then runs an action to modify the file, and
- - finally, freezes the content directory. -}
-modifyContent :: RawFilePath -> Annex a -> Annex a
-modifyContent f a = do
+ - or thaws it if it does, then runs an action to modify a file in the
+ - directory, and finally, freezes the content directory. -}
+modifyContentDir :: RawFilePath -> Annex a -> Annex a
+modifyContentDir f a = do
 	createContentDir f -- also thaws it
 	v <- tryNonAsync a
 	freezeContentDir f
 	either throwM return v
 
+{- Like modifyContentDir, but avoids creating the content directory if it
+ - ddoes not already exist. In that case, the action will probably fail. -}
+modifyContentDirWhenExists :: RawFilePath -> Annex a -> Annex a
+modifyContentDirWhenExists f a = do
+	thawContentDir f
+	v <- tryNonAsync a
+	freezeContentDir f
+	either throwM return v
+
 hasFreezeHook :: Annex Bool
 hasFreezeHook = isJust . annexFreezeContentCommand <$> Annex.getGitConfig
 
diff --git a/Command/Fix.hs b/Command/Fix.hs
index 109b17389e..581cb7ced2 100644
--- a/Command/Fix.hs
+++ b/Command/Fix.hs
@@ -78,7 +78,7 @@ breakHardLink file key obj = do
 			error "unable to break hard link"
 		thawContent tmp'
 		Database.Keys.storeInodeCaches key [tmp']
-		modifyContent obj $ freezeContent obj
+		modifyContentDir obj $ freezeContent obj
 	next $ return True
 
 makeHardLink :: RawFilePath -> Key -> CommandPerform
diff --git a/Command/Lock.hs b/Command/Lock.hs
index 620da40133..e0d4b8f885 100644
--- a/Command/Lock.hs
+++ b/Command/Lock.hs
@@ -77,13 +77,13 @@ perform file key = do
 	breakhardlink obj = whenM (catchBoolIO $ (> 1) . linkCount <$> liftIO (R.getFileStatus obj)) $ do
 		mfc <- withTSDelta (liftIO . genInodeCache file)
 		unlessM (sameInodeCache obj (maybeToList mfc)) $ do
-			modifyContent obj $ replaceGitAnnexDirFile (fromRawFilePath obj) $ \tmp -> do
+			modifyContentDir obj $ replaceGitAnnexDirFile (fromRawFilePath obj) $ \tmp -> do
 				unlessM (checkedCopyFile key obj (toRawFilePath tmp) Nothing) $
 					giveup "unable to lock file"
 			Database.Keys.storeInodeCaches key [obj]
 
 	-- Try to repopulate obj from an unmodified associated file.
-	repopulate obj = modifyContent obj $ do
+	repopulate obj = modifyContentDir obj $ do
 		g <- Annex.gitRepo
 		fs <- map (`fromTopFilePath` g)
 			<$> Database.Keys.getAssociatedFiles key
diff --git a/Upgrade/V5/Direct.hs b/Upgrade/V5/Direct.hs
index 9903f84d07..49665e8773 100644
--- a/Upgrade/V5/Direct.hs
+++ b/Upgrade/V5/Direct.hs
@@ -97,7 +97,7 @@ associatedFilesRelative key = do
 removeAssociatedFiles :: Key -> Annex ()
 removeAssociatedFiles key = do
 	mapping <- calcRepo $ gitAnnexMapping key
-	modifyContent mapping $
+	modifyContentDir mapping $
 		liftIO $ removeWhenExistsWith R.removeLink mapping
 
 {- Checks if a file in the tree, associated with a key, has not been modified.
@@ -124,7 +124,7 @@ recordedInodeCache key = withInodeCacheFile key $ \f ->
 {- Removes an inode cache. -}
 removeInodeCache :: Key -> Annex ()
 removeInodeCache key = withInodeCacheFile key $ \f ->
-	modifyContent f $
+	modifyContentDir f $
 		liftIO $ removeWhenExistsWith R.removeLink f
 
 withInodeCacheFile :: Key -> (RawFilePath -> Annex a) -> Annex a
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_6_e8ab74bb4951fe7f0c71630bb278ad6f._comment b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_6_e8ab74bb4951fe7f0c71630bb278ad6f._comment
new file mode 100644
index 0000000000..3b17573088
--- /dev/null
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_6_e8ab74bb4951fe7f0c71630bb278ad6f._comment
@@ -0,0 +1,26 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 6"""
+ date="2022-05-16T15:24:43Z"
+ content="""
+If fsck locks the content for removal, then moves it to the preferred
+location, how is that any different from git-annex first dropping content
+and then very quickly retrieving another copy and storing it in the other
+location? The only difference is timing, but things like being suspended
+and resumed can affect timing.
+
+So, if there is a problem with fsck doing that, there would also be a more
+general problem, that could occur in other circumstances, even if only
+rarely.
+
+One way to see the general problem happen would be to have two processes
+trying to drop the same object. One process finds the object location, then
+stalls. Meanwhile, the second process drops the object. Then the first
+process resumes, and locks for removal. Per comment #5 this will result in
+a dangling lock file in the object directory. I have not managed to get
+this to happen yet though.
+
+A fix for the general problem is to make it not create the
+object directory when opening the object lock file. So I've made that
+change.
+"""]]

Added a comment: How to disable lockdown in bare repos?
diff --git a/doc/internals/lockdown/comment_3_caf6d5318703d188a2135737093d8323._comment b/doc/internals/lockdown/comment_3_caf6d5318703d188a2135737093d8323._comment
new file mode 100644
index 0000000000..7ddbca9a40
--- /dev/null
+++ b/doc/internals/lockdown/comment_3_caf6d5318703d188a2135737093d8323._comment
@@ -0,0 +1,421 @@
+[[!comment format=mdwn
+ username="nick.guenther@e418ed3c763dff37995c2ed5da4232a7c6cee0a9"
+ nickname="nick.guenther"
+ avatar="http://cdn.libravatar.org/avatar/9e85c6ca61c3f877fef4f91c2bf6e278"
+ subject="How to disable lockdown in bare repos?"
+ date="2022-05-15T21:30:50Z"
+ content="""
+I've set up a project server for my team with annexes in most repos. I'm using [gitolite](https://gitolite.com) with its [git-annex-shell](https://github.com/sitaramc/gitolite/blob/master/src/commands/git-annex-shell) plugin. It's been going well for a year, and my team finds git-annex very useful for managing our large projects, so we have a large debt to you for that :)
+
+## Problem
+
+But when my users delete repos, the repos aren't fully deleted because any `annex/objects/*/*/SHA256-*/SHA256-*` file is locked down.
+
+
+### Gitolite
+
+For example:
+
+<details><summary><code>test-git-annex-write</code></summary>
+
+```
+
+test-gitea-annex-write() {
+REPO=$1; shift
+
+
+(set -e; cd $(mktemp -d)
+  git init
+  echo '# testing' > README.md && git add README.md && git commit -m \"Initial commit\"
+  git annex init
+  dd if=/dev/urandom of=large.bin bs=1M count=16 && git annex add large.bin && git commit -m \"Annex a file\"
+
+  git remote add origin \"$REPO\"
+  git config annex.jobs 1
+  git annex sync --content origin
+  git annex sync --content origin  # it only uploads the branch, but doesn't upload content, if I only do this once
+)
+}
+```
+
+</details>
+
+<details><summary>Create+Upload: <code>test-gitea-annex-write git@data:datasets/test-jank.git</code></summary>
+
+```
+$ test-gitea-annex-write git@data:datasets/test-jank.git
+Initialized empty Git repository in /tmp/tmp.ak87a0yp1e/.git/
+[master (root-commit) 0a7be36] Initial commit
+ 1 file changed, 1 insertion(+)
+ create mode 100644 README.md
+init  ok
+(recording state in git...)
+16+0 records in
+16+0 records out
+16777216 bytes (17 MB, 16 MiB) copied, 0.0608327 s, 276 MB/s
+add large.bin 
+ok                                
+(recording state in git...)
+[master 4a55ea5] Annex a file
+ 1 file changed, 1 insertion(+)
+ create mode 120000 large.bin
+
+  You have enabled concurrency, but git-annex is not able to use ssh connection caching. This may result in multiple ssh processes prompting for passwords at the same time.
+
+  annex.sshcaching is not set to true
+
+  Unable to parse git config from origin
+FATAL: autocreate denied
+
+fatal: Could not read from remote repository.
+
+Please make sure you have the correct access rights
+and the repository exists.
+
+
+  You have enabled concurrency, but git-annex is not able to use ssh connection caching. This may result in multiple ssh processes prompting for passwords at the same time.
+  annex.sshcaching is not set to true
+
+  Unable to parse git config from origin
+FATAL: autocreate denied
+
+fatal: Could not read from remote repository.
+
+Please make sure you have the correct access rights
+and the repository exists.
+On branch master
+nothing to commit, working tree clean
+commit ok
+pull origin 
+  You have enabled concurrency, but git-annex is not able to use ssh connection caching. This may result in multiple ssh processes prompting for passwords at the same time.
+
+  annex.sshcaching is not set to true
+FATAL: autocreate denied
+
+fatal: Could not read from remote repository.
+
+Please make sure you have the correct access rights
+and the repository exists.
+ok
+push origin 
+  You have enabled concurrency, but git-annex is not able to use ssh connection caching. This may result in multiple ssh processes prompting for passwords at the same time.
+
+  annex.sshcaching is not set to true
+hint: Using 'master' as the name for the initial branch. This default branch name
+hint: is subject to change. To configure the initial branch name to use in all
+hint: of your new repositories, which will suppress this warning, call:
+hint: 
+hint:   git config --global init.defaultBranch <name>
+hint: 
+hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
+hint: 'development'. The just-created branch can be renamed via this command:
+hint: 
+hint:   git branch -m <name>
+Initialized empty Git repository in /srv/git/repositories/datasets/test-jank.git/
+ok
+
+  You have enabled concurrency, but git-annex is not able to use ssh connection caching. This may result in multiple ssh processes prompting for passwords at the same time.
+
+  annex.sshcaching is not set to true
+On branch master
+nothing to commit, working tree clean
+commit ok
+pull origin 
+  You have enabled concurrency, but git-annex is not able to use ssh connection caching. This may result in multiple ssh processes prompting for passwords at the same time.
+
+  annex.sshcaching is not set to true
+ok
+copy large.bin 
+  You have enabled concurrency, but git-annex is not able to use ssh connection caching. This may result in multiple ssh processes prompting for passwords at the same time.
+
+  annex.sshcaching is not set to true
+(to origin...) ok
+pull origin 
+  You have enabled concurrency, but git-annex is not able to use ssh connection caching. This may result in multiple ssh processes prompting for passwords at the same time.
+
+  annex.sshcaching is not set to true
+ok
+(recording state in git...)
+push origin 
+  You have enabled concurrency, but git-annex is not able to use ssh connection caching. This may result in multiple ssh processes prompting for passwords at the same time.
+
+  annex.sshcaching is not set to true
+ok
+```
+
+</details>
+
+<details><summary>Delete the repo</summary>
+
+```
+$ ssh git@data D unlock datasets/test-jank
+'datasets/test-jank' is now unlocked
+$ ssh git@data D rm datasets/test-jank
+rm: cannot remove 'datasets/test-jank.git/annex/objects/968/4c0/SHA256E-s16777216--9d8ccb3ebe399a8f6801cde009e03a867151ea4e4bc609848abbd29dd335688f.bin/SHA256E-s16777216--9d8ccb3ebe399a8f6801cde009e03a867151ea4e4bc609848abbd29dd335688f.bin': Permission denied
+'datasets/test-jank' is now gone!
+```
+
+</details>
+
+Notice the \"Permission denied\" error -- but gitolite *thinks* its work is done:
+
+```
+$ ssh git@data info | grep test-jank
+$
+```
+
+but if I try to recreate the same repo, it fails:
+
+<details><summary><code>test-gitea-annex-write git@data:datasets/test-jank.git</code></summary>
+
+```
+$ test-gitea-annex-write git@data:datasets/test-jank.git
+Initialized empty Git repository in /tmp/tmp.IBSNFKRgRg/.git/
+[master (root-commit) e11344b] Initial commit
+ 1 file changed, 1 insertion(+)
+ create mode 100644 README.md
+init  ok
+(recording state in git...)
+16+0 records in
+16+0 records out
+16777216 bytes (17 MB, 16 MiB) copied, 0.0631523 s, 266 MB/s
+add large.bin 
+ok                                
+(recording state in git...)
+[master 0cedc5c] Annex a file
+ 1 file changed, 1 insertion(+)
+ create mode 120000 large.bin
+
+  You have enabled concurrency, but git-annex is not able to use ssh connection caching. This may result in multiple ssh processes prompting for passwords at the same time.
+
+  annex.sshcaching is not set to true
+
+  Unable to parse git config from origin
+FATAL: R any datasets/test-jank nguenther DENIED by fallthru

(Diff truncated)
Added a comment: Using fuse
diff --git a/doc/forum/__34__du__34___equivalent_on_an_annex__63__/comment_11_e3f82edf70d28f00d30cfd98f189cd86._comment b/doc/forum/__34__du__34___equivalent_on_an_annex__63__/comment_11_e3f82edf70d28f00d30cfd98f189cd86._comment
new file mode 100644
index 0000000000..1c79810089
--- /dev/null
+++ b/doc/forum/__34__du__34___equivalent_on_an_annex__63__/comment_11_e3f82edf70d28f00d30cfd98f189cd86._comment
@@ -0,0 +1,12 @@
+[[!comment format=mdwn
+ username="wzhd"
+ avatar="http://cdn.libravatar.org/avatar/1795a91af84f4243a3bf0974bc8d79fe"
+ subject="Using fuse"
+ date="2022-05-14T03:10:18Z"
+ content="""
+Wrote a bare minimum [fuse fs](https://codeberg.org/wzhd/annexize/) so that du-like utilities like ncdu, gt5, gdu can be used.
+
+It reads each symlink target, try to get a number after `SHA256E-s`, and pretends it's regular file with that size. `git-annex add`ed files don't need to be locally available.
+
+Files can be deleted but no other operations are implemented.
+"""]]

Added a comment: shell helper
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_7_e2d45ac485456ed9cb86f8e6bd23617c._comment b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_7_e2d45ac485456ed9cb86f8e6bd23617c._comment
new file mode 100644
index 0000000000..e9eb6decf1
--- /dev/null
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_7_e2d45ac485456ed9cb86f8e6bd23617c._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="yarikoptic"
+ avatar="http://cdn.libravatar.org/avatar/f11e9c84cb18d26a1748c33b48c924b4"
+ subject="shell helper"
+ date="2022-05-13T16:51:22Z"
+ content="""
+FWIW made this shell helper to migrate all keys into desired layout: [https://raw.githubusercontent.com/datalad/datalad/maint/tools/convert-git-annex-layout](https://raw.githubusercontent.com/datalad/datalad/maint/tools/convert-git-annex-layout)
+"""]]

Added a comment
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_6_a383c7303f1e942a830d9f730f1c0f00._comment b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_6_a383c7303f1e942a830d9f730f1c0f00._comment
new file mode 100644
index 0000000000..00a24e2cf8
--- /dev/null
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_6_a383c7303f1e942a830d9f730f1c0f00._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="yarikoptic"
+ avatar="http://cdn.libravatar.org/avatar/f11e9c84cb18d26a1748c33b48c924b4"
+ subject="comment 6"
+ date="2022-05-13T15:12:29Z"
+ content="""
+I guess fsck could just lock the entire repo for its duration forbidding any operation?  I would love to be able to migrate the layout also on \"older\" versions of repo/annex without upgrading all the way to 10.  Meanwhile I think I am doomed to write a little helper to do those renames (once, and hopefully never ever again... I might even protect myself by making those top xxx known to me now non-writable at the level of ACL, so the attempt to migrate would lead to an error) 
+"""]]

thoughts
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_5_55b5beaeba0de7bb1efcc2626570d3b5._comment b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_5_55b5beaeba0de7bb1efcc2626570d3b5._comment
index 830c27ba00..6fa9576070 100644
--- a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_5_55b5beaeba0de7bb1efcc2626570d3b5._comment
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_5_55b5beaeba0de7bb1efcc2626570d3b5._comment
@@ -31,8 +31,8 @@ fsck could lock the object file for drop, and then rather than removeing it,
 move it to a holding location. Then it could move the object file
 into the right place the same as `get` does. This should avoid the race.
 Interrupting fsck at the wrong time would leave the object file in this
-holding location though. If it used `.git/annex/tmp`, normal commands
-like `git-annex get` would recover from an interrupted fsck, though
-they would need to do some work to rehash the tmp file. Re-running
-fsck would need to also recover from an interrupted fsck.
+holding location though. Re-running fsck would need to recover from this
+situation. Putting it in `.git/annex/tmp/` might make sense, although
+`git-annex get` does not necessarily recover when the object file is
+located there.
 """]]

thoughts
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_5_55b5beaeba0de7bb1efcc2626570d3b5._comment b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_5_55b5beaeba0de7bb1efcc2626570d3b5._comment
index 53c90224c4..830c27ba00 100644
--- a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_5_55b5beaeba0de7bb1efcc2626570d3b5._comment
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_5_55b5beaeba0de7bb1efcc2626570d3b5._comment
@@ -4,14 +4,35 @@
  date="2022-05-10T16:56:47Z"
  content="""
 As well as moving the object file, fsck will need to move any other associated
-files. It may as well move the whole object directory.
+files, including the object lock file. It may as well move the whole
+object directory.
 
 Locking is a concern for implementing this in fsck. There
 would be a race where another process that is locking the object file
 sees the object file in the old location, so tries to lock it in the old
-location, but by then the object file has been moved. Seems this could
-result in it making a separate lock file in the old object directory (v9+),
-or might even create the object file when trying to lock it (pre v9).
+location, but by then the object file has been moved.
 
-Only making fsck do the move in v9+ solves half of that.
+Experimentally: In v10, moving the object file after it has checked its
+location in preparation for locking for drop results in it making a
+separate lock file in the old object directory. That lock file remains after
+the drop succeeds. In v8/v9, it seems to not create the object
+file when trying to lock it. (Based on reading the code, I though perhaps
+it would!) In v8-v10, moving the object directory in the race when it's locking
+content in place causes the lock to fail; it does not create any lock file
+or object file. 
+
+So, v10 post drop lock file cleanup is the problem. Or at least one
+problem, there could be other points in the race than the one I tested
+that have other behavior. This seems like an ugly race to insert fsck into
+the middle of; it would be much preferable if fsck could somehow avoid
+such races when moving the object directory. But how?
+
+fsck could lock the object file for drop, and then rather than removeing it,
+move it to a holding location. Then it could move the object file
+into the right place the same as `get` does. This should avoid the race.
+Interrupting fsck at the wrong time would leave the object file in this
+holding location though. If it used `.git/annex/tmp`, normal commands
+like `git-annex get` would recover from an interrupted fsck, though
+they would need to do some work to rehash the tmp file. Re-running
+fsck would need to also recover from an interrupted fsck.
 """]]

thoughts
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_3_4375a63a864306c39323fa4b159425d6._comment b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_3_4375a63a864306c39323fa4b159425d6._comment
index 97837f45ba..8257beda68 100644
--- a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_3_4375a63a864306c39323fa4b159425d6._comment
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_3_4375a63a864306c39323fa4b159425d6._comment
@@ -10,7 +10,9 @@ annex.crippledfilesystem=true. Then it does use the bare form of object
 filenames, which is kind of ok since it's not going to be using symlinks in
 that repository.
 
-Also, before 2016, git-annex used those names whenever annex.crippledfilesystem=true,
+Also, before 2016
+([[commit 2d00523609def535588b693a00d4092768e1c3c6]]), 
+git-annex used those names whenever annex.crippledfilesystem=true,
 no matter what core.symlinks was set to. So if the files are that old..
 
 This does seem to point to there needing to be a way to migrate the object
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_4_a0d4ffdf3edadb5381f242f65fa38932._comment b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_4_a0d4ffdf3edadb5381f242f65fa38932._comment
new file mode 100644
index 0000000000..484e3c9b17
--- /dev/null
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_4_a0d4ffdf3edadb5381f242f65fa38932._comment
@@ -0,0 +1,15 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 4"""
+ date="2022-05-10T16:37:21Z"
+ content="""
+The rationalle for using the bare object layout when on a crippled
+filesystem was given in [[!commit f1b0a4b404ed835f1c4a27a92352180be8564f8a]].
+Basically it may be more portable. Not a strong rationalle at all, as the
+later change to not do it when symlinks are supported shows. But
+I don't think worth changing at this point.
+
+So teaching fsck to move object files to the preferred location seems the
+best way. It will also deal with the situation where a bare repository gets
+converted by the user into a non-bare repo.
+"""]]
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_5_55b5beaeba0de7bb1efcc2626570d3b5._comment b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_5_55b5beaeba0de7bb1efcc2626570d3b5._comment
new file mode 100644
index 0000000000..53c90224c4
--- /dev/null
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_5_55b5beaeba0de7bb1efcc2626570d3b5._comment
@@ -0,0 +1,17 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 5"""
+ date="2022-05-10T16:56:47Z"
+ content="""
+As well as moving the object file, fsck will need to move any other associated
+files. It may as well move the whole object directory.
+
+Locking is a concern for implementing this in fsck. There
+would be a race where another process that is locking the object file
+sees the object file in the old location, so tries to lock it in the old
+location, but by then the object file has been moved. Seems this could
+result in it making a separate lock file in the old object directory (v9+),
+or might even create the object file when trying to lock it (pre v9).
+
+Only making fsck do the move in v9+ solves half of that.
+"""]]

followup
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn b/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn
index b007698801..d9a5db3987 100644
--- a/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn
@@ -3,3 +3,5 @@ Checking out `master` branch is not sufficient since `.git/annex/objects` uses d
 
 [[!meta author=yoh]]
 [[!tag projects/datalad]]
+
+[[!meta title="command to migrate object files from hashdirlower to hashdirmixed"]]
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_3_4375a63a864306c39323fa4b159425d6._comment b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_3_4375a63a864306c39323fa4b159425d6._comment
new file mode 100644
index 0000000000..97837f45ba
--- /dev/null
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_3_4375a63a864306c39323fa4b159425d6._comment
@@ -0,0 +1,20 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 3"""
+ date="2022-05-09T19:54:35Z"
+ content="""
+Ok, so the object location usually used in bare repositories.. 
+
+One way that could happen is if core.symlinks=false and
+annex.crippledfilesystem=true. Then it does use the bare form of object
+filenames, which is kind of ok since it's not going to be using symlinks in
+that repository.
+
+Also, before 2016, git-annex used those names whenever annex.crippledfilesystem=true,
+no matter what core.symlinks was set to. So if the files are that old..
+
+This does seem to point to there needing to be a way to migrate the object
+files in a repository to the right names. It might be a reasonable thing
+for git-annex fsck to do, when it sees a symlink to an object file that
+is in the other location.
+"""]]

incremental verification for retrieval from import remotes
Sponsored-by: Dartmouth College's Datalad project
diff --git a/Annex/CopyFile.hs b/Annex/CopyFile.hs
index d33cfaff31..93ab346c7b 100644
--- a/Annex/CopyFile.hs
+++ b/Annex/CopyFile.hs
@@ -1,6 +1,6 @@
 {- Copying files.
  -
- - Copyright 2011-2021 Joey Hess <id@joeyh.name>
+ - Copyright 2011-2022 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -79,40 +79,47 @@ data CopyMethod = CopiedCoW | Copied
  - (eg when isStableKey is false), and doing this avoids getting a
  - corrupted file in such cases.
  -}
-fileCopier :: CopyCoWTried -> FilePath -> FilePath -> MeterUpdate -> Maybe IncrementalVerifier -> Annex CopyMethod
+fileCopier :: CopyCoWTried -> FilePath -> FilePath -> MeterUpdate -> Maybe IncrementalVerifier -> IO CopyMethod
 #ifdef mingw32_HOST_OS
 fileCopier _ src dest meterupdate iv = docopy
 #else
 fileCopier copycowtried src dest meterupdate iv =
-	ifM (liftIO $ tryCopyCoW copycowtried src dest meterupdate)
+	ifM (tryCopyCoW copycowtried src dest meterupdate)
 		( do
-			liftIO $ maybe noop unableIncrementalVerifier iv
+			maybe noop unableIncrementalVerifier iv
 			return CopiedCoW
 		, docopy
 		)
 #endif
   where
-	dest' = toRawFilePath dest
-
 	docopy = do
 		-- The file might have had the write bit removed,
 		-- so make sure we can write to it.
-		void $ liftIO $ tryIO $ allowWrite dest'
-
-		liftIO $ withBinaryFile dest ReadWriteMode $ \hdest ->
-			withBinaryFile src ReadMode $ \hsrc -> do
-				sofar <- compareexisting hdest hsrc zeroBytesProcessed
-				docopy' hdest hsrc sofar
+		void $ tryIO $ allowWrite dest'
 
+		withBinaryFile src ReadMode $ \hsrc ->
+			fileContentCopier hsrc dest meterupdate iv
+		
 		-- Copy src mode and mtime.
-		mode <- liftIO $ fileMode <$> getFileStatus src
-		mtime <- liftIO $ utcTimeToPOSIXSeconds <$> getModificationTime src
-		liftIO $ setFileMode dest mode
-		liftIO $ touch dest' mtime False
+		mode <- fileMode <$> getFileStatus src
+		mtime <- utcTimeToPOSIXSeconds <$> getModificationTime src
+		setFileMode dest mode
+		touch dest' mtime False
 
 		return Copied
 	
-	docopy' hdest hsrc sofar = do
+	dest' = toRawFilePath dest
+
+{- Copies content from a handle to a destination file. Does not
+ - use copy-on-write, and does not copy file mode and mtime.
+ -}
+fileContentCopier :: Handle -> FilePath -> MeterUpdate -> Maybe IncrementalVerifier -> IO ()
+fileContentCopier hsrc dest meterupdate iv =
+	withBinaryFile dest ReadWriteMode $ \hdest -> do
+		sofar <- compareexisting hdest zeroBytesProcessed
+		docopy hdest sofar
+  where
+	docopy hdest sofar = do
 		s <- S.hGet hsrc defaultChunkSize
 		if s == S.empty
 			then return ()
@@ -121,12 +128,12 @@ fileCopier copycowtried src dest meterupdate iv =
 				S.hPut hdest s
 				maybe noop (flip updateIncrementalVerifier s) iv
 				meterupdate sofar'
-				docopy' hdest hsrc sofar'
+				docopy hdest sofar'
 
 	-- Leaves hdest and hsrc seeked to wherever the two diverge,
 	-- so typically hdest will be seeked to end, and hsrc to the same
 	-- position.
-	compareexisting hdest hsrc sofar = do
+	compareexisting hdest sofar = do
 		s <- S.hGet hdest defaultChunkSize
 		if s == S.empty
 			then return sofar
@@ -137,7 +144,7 @@ fileCopier copycowtried src dest meterupdate iv =
 						maybe noop (flip updateIncrementalVerifier s) iv
 						let sofar' = addBytesProcessed sofar (S.length s)
 						meterupdate sofar'
-						compareexisting hdest hsrc sofar'
+						compareexisting hdest sofar'
 					else do
 						seekbefore hdest s
 						seekbefore hsrc s'
diff --git a/Annex/Import.hs b/Annex/Import.hs
index 2e4275fa9a..bc2f860ea0 100644
--- a/Annex/Import.hs
+++ b/Annex/Import.hs
@@ -597,14 +597,14 @@ importKeys remote importtreeconfig importcontent thirdpartypopulated importablec
 		getcontent k = do
 			let af = AssociatedFile (Just f)
 			let downloader p' tmpfile = do
-				k' <- Remote.retrieveExportWithContentIdentifier
+				_ <- Remote.retrieveExportWithContentIdentifier
 					ia loc cid (fromRawFilePath tmpfile)
-					(pure k)
+					(Left k)
 					(combineMeterUpdate p' p)
-				ok <- moveAnnex k' af tmpfile
+				ok <- moveAnnex k af tmpfile
 				when ok $
 					logStatus k InfoPresent
-				return (Just (k', ok))
+				return (Just (k, ok))
 			checkDiskSpaceToGet k Nothing $
 				notifyTransfer Download af $
 					download' (Remote.uuid remote) k af Nothing stdRetry $ \p' ->
@@ -615,9 +615,9 @@ importKeys remote importtreeconfig importcontent thirdpartypopulated importablec
 	-- need to retrieve this file.
 	doimportsmall cidmap db loc cid sz p = do
 		let downloader tmpfile = do
-			k <- Remote.retrieveExportWithContentIdentifier
+			(k, _) <- Remote.retrieveExportWithContentIdentifier
 				ia loc cid (fromRawFilePath tmpfile)
-				(mkkey tmpfile)
+				(Right (mkkey tmpfile))
 				p
 			case keyGitSha k of
 				Just sha -> do
@@ -638,9 +638,9 @@ importKeys remote importtreeconfig importcontent thirdpartypopulated importablec
 	dodownload cidmap db (loc, (cid, sz)) f matcher = do
 		let af = AssociatedFile (Just f)
 		let downloader tmpfile p = do
-			k <- Remote.retrieveExportWithContentIdentifier
+			(k, _) <- Remote.retrieveExportWithContentIdentifier
 				ia loc cid (fromRawFilePath tmpfile)
-				(mkkey tmpfile)
+				(Right (mkkey tmpfile))
 				p
 			case keyGitSha k of
 				Nothing -> do
diff --git a/CHANGELOG b/CHANGELOG
index 506f3cd0e7..fa9a9c106d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,9 @@ git-annex (10.20220505) UNRELEASED; urgency=medium
     data units. Note that the short form is "Mbit" not "Mb" because
     that differs from "MB" only in case, and git-annex parses units
     case-insensitively.
+  * Special remotes using exporttree=yes and/or importtree=yes now
+    checksum content while it is being retrieved, instead of in a separate
+    pass at the end.
 
  -- Joey Hess <id@joeyh.name>  Thu, 05 May 2022 15:08:07 -0400
 
diff --git a/Remote/Adb.hs b/Remote/Adb.hs
index 477d9ea589..081b69cfe8 100644
--- a/Remote/Adb.hs
+++ b/Remote/Adb.hs
@@ -338,15 +338,23 @@ listImportableContentsM serial adir c = adbfind >>= \case
 -- connection is resonably fast, it's probably as good as
 -- git's handling of similar situations with files being modified while
 -- it's updating the working tree for a merge.
-retrieveExportWithContentIdentifierM :: AndroidSerial -> AndroidPath -> ExportLocation -> ContentIdentifier -> FilePath -> Annex Key -> MeterUpdate -> Annex Key
-retrieveExportWithContentIdentifierM serial adir loc cid dest mkkey _p = do
-	retrieve' serial src dest
-	k <- mkkey
-	currcid <- getExportContentIdentifier serial adir loc
-	if currcid == Right (Just cid)
-		then return k
-		else giveup "the file on the android device has changed"
+retrieveExportWithContentIdentifierM :: AndroidSerial -> AndroidPath -> ExportLocation -> ContentIdentifier -> FilePath -> Either Key (Annex Key) -> MeterUpdate -> Annex (Key, Verification)
+retrieveExportWithContentIdentifierM serial adir loc cid dest gk _p = do
+	case gk of
+		Right mkkey -> do
+			go
+			k <- mkkey
+			return (k, UnVerified)
+		Left k -> do
+			v <- verifyKeyContentIncrementally DefaultVerify k
+				(\iv -> tailVerify iv (toRawFilePath dest) go)
+			return (k, v)
   where
+	go = do
+		retrieve' serial src dest
+		currcid <- getExportContentIdentifier serial adir loc
+		when (currcid /= Right (Just cid)) $
+			giveup "the file on the android device has changed"
 	src = androidExportLocation adir loc
 
 storeExportWithContentIdentifierM :: AndroidSerial -> AndroidPath -> FilePath -> Key -> ExportLocation -> [ContentIdentifier] -> MeterUpdate -> Annex ContentIdentifier
diff --git a/Remote/Borg.hs b/Remote/Borg.hs
index af3a010703..5e34256fe9 100644
--- a/Remote/Borg.hs

(Diff truncated)
Added a comment: interaction of out-of-tree symlinks with exporttree
diff --git a/doc/bugs/git-annex-import_imports_outside_of_directory/comment_3_778e176a44aa57e9223aab1a45c931f3._comment b/doc/bugs/git-annex-import_imports_outside_of_directory/comment_3_778e176a44aa57e9223aab1a45c931f3._comment
new file mode 100644
index 0000000000..59910a05ec
--- /dev/null
+++ b/doc/bugs/git-annex-import_imports_outside_of_directory/comment_3_778e176a44aa57e9223aab1a45c931f3._comment
@@ -0,0 +1,19 @@
+[[!comment format=mdwn
+ username="Ilya_Shlyakhter"
+ avatar="http://cdn.libravatar.org/avatar/1647044369aa7747829c38b9dcc84df0"
+ subject="interaction of out-of-tree symlinks with exporttree"
+ date="2022-05-09T19:21:15Z"
+ content="""
+One other scenario where the result might not be what users expect is the following: if a [[directory special remote|/special_remotes/directory]] is configured with both [[`importtree=yes`|git-annex-import]] and [[`exporttree=yes`|git-annex-export]], and the directory contains symlinks pointing outside the tree, then an import followed by an export will replace the symlink in the original directory with a copy of the content.
+
+>a symlink that points outside the git repository, which is not something one often wants to check into a git repository
+
+Such a symlink is already not something one often has :)  But if one does, then the repo is likely for one's own usage, or for the usage by people with access to the shared filesystem where the link works, so adding the link to git as-is makes sense.  Logically, it's likely that the out-of-tree link target represents some separate tree of files that you don't think of as part of the tree (or you'd have put them under the tree); if you did want to import them, you'd make a separate repo for them and import them as a submodule.
+
+Also, what happens if the target tree of the out-of-tree link has a symlink back to the original tree -- could this cause infinite recursion?
+
+The [[`git-annex-import` man page|git-annex-import]] says the command imports \"a tree of files\".  It seems simplest if this description was always strictly true, regardless of what's in the tree.  But if you decide to keep the current default, maybe clarify the web page?
+
+Thanks again for all your work.
+
+"""]]

Added a comment
diff --git a/doc/forum/Issues_with_non-sync_workflow/comment_4_a93d502253cf9f164fbcd23ca755db9d._comment b/doc/forum/Issues_with_non-sync_workflow/comment_4_a93d502253cf9f164fbcd23ca755db9d._comment
new file mode 100644
index 0000000000..87af99bb23
--- /dev/null
+++ b/doc/forum/Issues_with_non-sync_workflow/comment_4_a93d502253cf9f164fbcd23ca755db9d._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="lh"
+ avatar="http://cdn.libravatar.org/avatar/d31bade008d7da493d9bfb37d68fd825"
+ subject="comment 4"
+ date="2022-05-09T19:18:01Z"
+ content="""
+Still curious about how `annex.alwayscommit=false` tracks modifications and whether mucking with a `git-annex` worktree could cause data loss with this option enabled.
+"""]]

Added a comment: hm...
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_2_845206fa688ed4786262fe6d82464d97._comment b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_2_845206fa688ed4786262fe6d82464d97._comment
new file mode 100644
index 0000000000..73b2252b69
--- /dev/null
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_2_845206fa688ed4786262fe6d82464d97._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="yarikoptic"
+ avatar="http://cdn.libravatar.org/avatar/f11e9c84cb18d26a1748c33b48c924b4"
+ subject="hm..."
+ date="2022-05-09T19:10:10Z"
+ content="""
+I will need to figure out/try to reproduce how user ended up with .git/annex/objects in xxx/yyy instead of xx/yy layout...
+"""]]

incremental verification for retrieval from all export remotes
Only for export remotes so far, not export/import.
Sponsored-by: Dartmouth College's Datalad project
diff --git a/Annex/Verify.hs b/Annex/Verify.hs
index 9e4deb93ab..5703bf41e4 100644
--- a/Annex/Verify.hs
+++ b/Annex/Verify.hs
@@ -1,6 +1,6 @@
 {- verification
  -
- - Copyright 2010-2021 Joey Hess <id@joeyh.name>
+ - Copyright 2010-2022 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -17,6 +17,7 @@ module Annex.Verify (
 	isVerifiable,
 	startVerifyKeyContentIncrementally,
 	finishVerifyKeyContentIncrementally,
+	verifyKeyContentIncrementally,
 	IncrementalVerifier(..),
 	tailVerify,
 ) where
@@ -34,6 +35,7 @@ import Types.WorkerPool
 import Types.Key
 
 import Control.Concurrent.STM
+import Control.Concurrent.Async
 import qualified Data.ByteString as S
 #if WITH_INOTIFY
 import qualified System.INotify as INotify
@@ -186,12 +188,17 @@ finishVerifyKeyContentIncrementally (Just iv) =
 		-- Incremental verification was not able to be done.
 		Nothing -> return (True, UnVerified)
 
--- | Reads the file as it grows, and feeds it to the incremental verifier.
+verifyKeyContentIncrementally :: VerifyConfig -> Key -> (Maybe IncrementalVerifier -> Annex ()) -> Annex Verification
+verifyKeyContentIncrementally verifyconfig k a = do
+	miv <- startVerifyKeyContentIncrementally verifyconfig k
+	a miv
+	snd <$> finishVerifyKeyContentIncrementally miv
+
+-- | Runs a writer action that retrieves to a file. In another thread,
+-- reads the file as it grows, and feeds it to the incremental verifier.
 -- 
--- The TMVar must start out empty, and be filled once whatever is
--- writing to the file finishes. Once the writer finishes, this returns
--- quickly. It may not feed the entire content of the file to the
--- incremental verifier.
+-- Once the writer finishes, this returns quickly. It may not feed
+-- the entire content of the file to the incremental verifier.
 --
 -- The file does not need to exist yet when this is called. It will wait
 -- for the file to appear before opening it and starting verification.
@@ -223,9 +230,19 @@ finishVerifyKeyContentIncrementally (Just iv) =
 -- and if the disk is slow, the reader may never catch up to the writer,
 -- and the disk cache may never speed up reads. So this should only be
 -- used when there's not a better way to incrementally verify.
-tailVerify :: IncrementalVerifier -> RawFilePath -> TMVar () -> IO ()
+tailVerify :: Maybe IncrementalVerifier -> RawFilePath -> Annex a -> Annex a
+tailVerify (Just iv) f writer = do
+	finished <- liftIO newEmptyTMVarIO
+	t <- liftIO $ async $ tailVerify' iv f finished
+	let finishtail = do
+		liftIO $ atomically $ putTMVar finished ()
+		liftIO (wait t)
+	writer `finally` finishtail
+tailVerify Nothing _ writer = writer
+
+tailVerify' :: IncrementalVerifier -> RawFilePath -> TMVar () -> IO ()
 #if WITH_INOTIFY
-tailVerify iv f finished = 
+tailVerify' iv f finished = 
 	tryNonAsync go >>= \case
 		Right r -> return r
 		Left _ -> unableIncrementalVerifier iv
@@ -312,5 +329,5 @@ tailVerify iv f finished =
 
 	chunk = 65536
 #else
-tailVerify iv _ _ = unableIncrementalVerifier iv
+tailVerify' iv _ _ = unableIncrementalVerifier iv
 #endif
diff --git a/Remote/Adb.hs b/Remote/Adb.hs
index 09c2aa219e..477d9ea589 100644
--- a/Remote/Adb.hs
+++ b/Remote/Adb.hs
@@ -1,6 +1,6 @@
 {- Remote on Android device accessed using adb.
  -
- - Copyright 2018-2020 Joey Hess <id@joeyh.name>
+ - Copyright 2018-2022 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -22,6 +22,7 @@ import Annex.UUID
 import Utility.Metered
 import Types.ProposedAccepted
 import Annex.SpecialRemote.Config
+import Annex.Verify
 
 import qualified Data.Map as M
 import qualified System.FilePath.Posix as Posix
@@ -256,9 +257,10 @@ storeExportM serial adir src _k loc _p =
 	dest = androidExportLocation adir loc
 
 retrieveExportM :: AndroidSerial -> AndroidPath -> Key -> ExportLocation -> FilePath -> MeterUpdate -> Annex Verification
-retrieveExportM serial adir _k loc dest _p = do
-	retrieve' serial src dest
-	return UnVerified
+retrieveExportM serial adir k loc dest _p = 
+	verifyKeyContentIncrementally AlwaysVerify k $ \iv ->
+		tailVerify iv (toRawFilePath dest) $
+			retrieve' serial src dest
   where
 	src = androidExportLocation adir loc
 
diff --git a/Remote/Directory.hs b/Remote/Directory.hs
index 3164e4d3bf..82b28b81bb 100644
--- a/Remote/Directory.hs
+++ b/Remote/Directory.hs
@@ -36,6 +36,7 @@ import Annex.CopyFile
 import Annex.Content
 import Annex.Perms
 import Annex.UUID
+import Annex.Verify
 import Backend
 import Types.KeySource
 import Types.ProposedAccepted
@@ -317,9 +318,9 @@ storeExportM d cow src _k loc p = do
 	go tmp () = void $ fileCopier cow src tmp p Nothing
 
 retrieveExportM :: RawFilePath -> CopyCoWTried -> Key -> ExportLocation -> FilePath -> MeterUpdate -> Annex Verification
-retrieveExportM d cow _k loc dest p = do
-	void $ fileCopier cow src dest p Nothing
-	return UnVerified
+retrieveExportM d cow k loc dest p = 
+	verifyKeyContentIncrementally AlwaysVerify k $ \iv -> 
+		void $ fileCopier cow src dest p iv
   where
 	src = fromRawFilePath $ exportPath d loc
 
diff --git a/Remote/External.hs b/Remote/External.hs
index 99dfae52fa..7eb5a4be66 100644
--- a/Remote/External.hs
+++ b/Remote/External.hs
@@ -1,6 +1,6 @@
 {- External special remote interface.
  -
- - Copyright 2013-2020 Joey Hess <id@joeyh.name>
+ - Copyright 2013-2022 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -37,6 +37,7 @@ import Config.Cost
 import Annex.Content
 import Annex.Url
 import Annex.UUID
+import Annex.Verify
 import Creds
 
 import Control.Concurrent.STM
@@ -292,9 +293,10 @@ storeExportM external f k loc p = either giveup return =<< go
 	req sk = TRANSFEREXPORT Upload sk f
 
 retrieveExportM :: External -> Key -> ExportLocation -> FilePath -> MeterUpdate -> Annex Verification
-retrieveExportM external k loc d p = do
-	either giveup return =<< go
-	return UnVerified
+retrieveExportM external k loc dest p = do
+	verifyKeyContentIncrementally AlwaysVerify k $ \iv ->
+		tailVerify iv (toRawFilePath dest) $
+			either giveup return =<< go
   where
 	go = handleRequestExport external loc req k (Just p) $ \resp -> case resp of
 		TRANSFER_SUCCESS Download k'
@@ -304,7 +306,7 @@ retrieveExportM external k loc d p = do
 		UNSUPPORTED_REQUEST ->
 			result $ Left "TRANSFEREXPORT not implemented by external special remote"
 		_ -> Nothing
-	req sk = TRANSFEREXPORT Download sk d
+	req sk = TRANSFEREXPORT Download sk dest
 
 checkPresentExportM :: External -> Key -> ExportLocation -> Annex Bool
 checkPresentExportM external k loc = either giveup id <$> go
diff --git a/Remote/Git.hs b/Remote/Git.hs
index 45e79ee348..db3a56f8f4 100644
--- a/Remote/Git.hs
+++ b/Remote/Git.hs
@@ -491,14 +491,12 @@ copyFromRemote r st key file dest meterupdate vc = do
 
 copyFromRemote'' :: Git.Repo -> Remote -> State -> Key -> AssociatedFile -> FilePath -> MeterUpdate -> VerifyConfig -> Annex Verification
 copyFromRemote'' repo r st@(State connpool _ _ _ _) key file dest meterupdate vc
-	| Git.repoIsHttp repo = do
-		iv <- startVerifyKeyContentIncrementally vc key
+	| Git.repoIsHttp repo = verifyKeyContentIncrementally vc key $ \iv -> do
 		gc <- Annex.getGitConfig
 		ok <- Url.withUrlOptionsPromptingCreds $
 			Annex.Content.downloadUrl False key meterupdate iv (keyUrls gc repo r key) dest
 		unless ok $
 			giveup "failed to download content"
-		snd <$> finishVerifyKeyContentIncrementally iv
 	| not $ Git.repoIsUrl repo = guardUsable repo (giveup "cannot access remote") $ do

(Diff truncated)
datalad metadata
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn b/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn
index a31c327fdd..b007698801 100644
--- a/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn
@@ -1,2 +1,5 @@
 A somewhat follow up to https://git-annex.branchable.com/bugs/add_config_var_preventing_adjusted_branch_mode/ where we ended up in adjusted branch mode and want to get back to original indirect mode using the thaw/freeze commands.
 Checking out `master` branch is not sufficient since `.git/annex/objects` uses different layout I guess to ensure that symlinks do not jeopardize actual annex storage on systems without read-only protection.  But we need some command to migrate .git/annex/objects layout. May be it is already there and I just failed to find 
+
+[[!meta author=yoh]]
+[[!tag projects/datalad]]

comment
diff --git a/doc/bugs/mimeencoding_detection_is_not_working/comment_3_b02a321a1fd8aaeca1680cec96055172._comment b/doc/bugs/mimeencoding_detection_is_not_working/comment_3_b02a321a1fd8aaeca1680cec96055172._comment
new file mode 100644
index 0000000000..396d3ed2c9
--- /dev/null
+++ b/doc/bugs/mimeencoding_detection_is_not_working/comment_3_b02a321a1fd8aaeca1680cec96055172._comment
@@ -0,0 +1,12 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 3"""
+ date="2022-05-09T15:21:59Z"
+ content="""
+Thanks, I think you must be right that there is some form of mojibake
+involved here, that is preventing cryllic filenames being sent through
+to libmagic, but only on Windows.
+
+[[todo/windows_support]] talks about filename encoding problems on windows,
+and this is probably one of those.
+"""]]

comment
diff --git a/doc/bugs/add_config_var_preventing_adjusted_branch_mode/comment_2_0c064717d0b85ff4b9df1bada7c6e7ad._comment b/doc/bugs/add_config_var_preventing_adjusted_branch_mode/comment_2_0c064717d0b85ff4b9df1bada7c6e7ad._comment
new file mode 100644
index 0000000000..6010c0a569
--- /dev/null
+++ b/doc/bugs/add_config_var_preventing_adjusted_branch_mode/comment_2_0c064717d0b85ff4b9df1bada7c6e7ad._comment
@@ -0,0 +1,13 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 2"""
+ date="2022-05-09T15:13:39Z"
+ content="""
+It might be worth preventing `git-annex init` when in an existing, already
+initalized repo from entering an adjusted branch. But re-running `git-annex
+init` generally re-does initialization, except for generating a new UUID
+and description. If a repo has been moved to a crippled filesystem,
+I think it would be reasonable for a user to expect re-running git-annex
+init will react to that. (Which can also involve setting annex.pidlock or
+disabling annex.sshcaching.)
+"""]]

comment
diff --git a/doc/bugs/add_config_var_preventing_adjusted_branch_mode/comment_1_d7ca1f4ccb4bc8a72788f6dcf8af9439._comment b/doc/bugs/add_config_var_preventing_adjusted_branch_mode/comment_1_d7ca1f4ccb4bc8a72788f6dcf8af9439._comment
new file mode 100644
index 0000000000..81edca7bea
--- /dev/null
+++ b/doc/bugs/add_config_var_preventing_adjusted_branch_mode/comment_1_d7ca1f4ccb4bc8a72788f6dcf8af9439._comment
@@ -0,0 +1,15 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2022-05-09T15:00:47Z"
+ content="""
+git-annex does not enter adjusted branch mode except on `git-annex
+init` or when you explcitly tell it to. The only exception to this that I
+can find is that upgrading from a v5 repository that was in direct mode
+will enter an adjusted branch.
+
+Switching back from an adjusted branch to master is a simple `git
+checkout`.
+
+These two facts do not argue for a separate config setting IMHO.
+"""]]

comment
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_1_8e9ecd2a3f9fc5cb80785b100aeb6e36._comment b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_1_8e9ecd2a3f9fc5cb80785b100aeb6e36._comment
new file mode 100644
index 0000000000..8f713e9e72
--- /dev/null
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode/comment_1_8e9ecd2a3f9fc5cb80785b100aeb6e36._comment
@@ -0,0 +1,13 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2022-05-09T14:57:55Z"
+ content="""
+But adjusted branches do not affect the location of git-annex object files.
+
+The git-annex adjust man page says to use `git checkout` to switch back,
+and it certianly does work.
+
+If you are having a problem with this, you need to explain what the problem
+is and what is happening...
+"""]]

diff --git a/doc/users/wzhd.mdwn b/doc/users/wzhd.mdwn
new file mode 100644
index 0000000000..9b25e9bb32
--- /dev/null
+++ b/doc/users/wzhd.mdwn
@@ -0,0 +1 @@
+Learned some Haskell because of git-annex and propellor

question/todo about migrating .git/annex/objects
diff --git a/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn b/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn
new file mode 100644
index 0000000000..a31c327fdd
--- /dev/null
+++ b/doc/todo/command_to___34__migrate__34___from_adjusted_mode.mdwn
@@ -0,0 +1,2 @@
+A somewhat follow up to https://git-annex.branchable.com/bugs/add_config_var_preventing_adjusted_branch_mode/ where we ended up in adjusted branch mode and want to get back to original indirect mode using the thaw/freeze commands.
+Checking out `master` branch is not sufficient since `.git/annex/objects` uses different layout I guess to ensure that symlinks do not jeopardize actual annex storage on systems without read-only protection.  But we need some command to migrate .git/annex/objects layout. May be it is already there and I just failed to find 

initial report asking to prevent adjusted branch migration or any other for that sake
diff --git a/doc/bugs/add_config_var_preventing_adjusted_branch_mode.mdwn b/doc/bugs/add_config_var_preventing_adjusted_branch_mode.mdwn
new file mode 100644
index 0000000000..449c60d140
--- /dev/null
+++ b/doc/bugs/add_config_var_preventing_adjusted_branch_mode.mdwn
@@ -0,0 +1,60 @@
+### Please describe the problem.
+
+Somewhat too late for our current usecase since older git-annex would not know about it, but I think could be generalized into adding a configuration variable right away for **any** automated migration. E.g. there is no variable to prevent autoupgrades of the repos (e.g. from v5 to the next one etc), but AFAIK there is none for automated conversion" into `adjusted/master(unlocked)` mode.  
+
+Rationale: With thaw/freeze commands we now can use git-annex in indirect (default) mode on our HPC. But that requires a recent version of git-annex.  User might have some other (older) version of git-annex available system-wide by default, and if the user forgets to switch to new version of git-annex before using it, it might trigger git-annex to realize that it operates on crippled FS, and since not knowing about thaw/freeze -- it just migrates repository to adjusted, which is very undesired. 
+
+
+### What steps will reproduce the problem?
+
+here is a demo of older git-annex going back to adjusted branch mode... yet to discover how else we could have migrated without directly invoking `git annex init`:
+
+```
+[d31548v@discovery7 d31548v]$ mkdir repo
+[d31548v@discovery7 d31548v]$ cd repo
+[d31548v@discovery7 repo]$ git init
+Initialized empty Git repository in /dartfs/rc/lab/D/DBIC/DBIC/d31548v/repo/.git/
+[d31548v@discovery7 repo]$ git config --add annex.thawcontent-command "$HOME/bin-annex/thaw-content %path"
+[d31548v@discovery7 repo]$ git config --add annex.freezecontent-command "$HOME/bin-annex/freeze-content %path"
+[d31548v@discovery7 repo]$ git annex init
+init  ok
+(recording state in git...)
+[d31548v@discovery7 repo]$ echo 123 > 123
+[d31548v@discovery7 repo]$ git annex add 123
+add 123 
+ok                                
+(recording state in git...)
+git comm[d31548v@discovery7 repo]$ git commit -m 'added 123 in indirect mode' 123
+[master (root-commit) 3ceb200] added 123 in indirect mode
+ 1 file changed, 1 insertion(+)
+ create mode 120000 123
+[d31548v@discovery7 repo]$ ls -ld 123
+lrwxr-x--- 1 d31548v rc-DBIC 178 May  6 11:19 123 -> .git/annex/objects/G6/qW/SHA256E-s4--181210f8f9c779c26da1d9b2075bde0127302ee0e3fca38c9a83f5b1dd8e5d3b/SHA256E-s4--181210f8f9c779c26da1d9b2075bde0127302ee0e3fca38c9a83f5b1dd8e5d3b
+[d31548v@discovery7 repo]$ ls -l .git/annex/objects
+total 3
+drwxr-x--- 3 d31548v rc-DBIC 20 May  6 11:19 G6
+[d31548v@discovery7 repo]$ export PATH=/opt/bin:$PATH
+[d31548v@discovery7 repo]$ git annex version | head -n 1
+git-annex version: 8.20200502-g55acb2e52
+[d31548v@discovery7 repo]$ git annex drop 123
+drop 123 
+git-annex: failed to lock content: .git/annex/objects/G6/qW/SHA256E-s4--181210f8f9c779c26da1d9b2075bde0127302ee0e3fca38c9a83f5b1dd8e5d3b/SHA256E-s4--181210f8f9c779c26da1d9b2075bde0127302ee0e3fca38c9a83f5b1dd8e5d3b: openFd: permission denied (Permission denied)
+failed
+git-annex: drop: 1 failed
+[d31548v@discovery7 repo]$ git annex init
+init  
+  Filesystem allows writing to files whose write bit is not set.
+
+  Detected a crippled filesystem.
+
+  Disabling core.symlinks.
+(scanning for unlocked files...)
+
+  Entering an adjusted branch where files are unlocked as this filesystem does not support locked files.
+
+Switched to branch 'adjusted/master(unlocked)'
+ok
+```
+
+[[!meta author=yoh]]
+[[!tag projects/datalad]]

Added a comment
diff --git a/doc/bugs/mimeencoding_detection_is_not_working/comment_2_b9df7eb812a4a2fba4b1273fbffe8375._comment b/doc/bugs/mimeencoding_detection_is_not_working/comment_2_b9df7eb812a4a2fba4b1273fbffe8375._comment
new file mode 100644
index 0000000000..cfaa5e9caa
--- /dev/null
+++ b/doc/bugs/mimeencoding_detection_is_not_working/comment_2_b9df7eb812a4a2fba4b1273fbffe8375._comment
@@ -0,0 +1,41 @@
+[[!comment format=mdwn
+ username="mnaoumov"
+ avatar="http://cdn.libravatar.org/avatar/6e35380ce54f8cdce1e5466df77f2208"
+ subject="comment 2"
+ date="2022-05-05T22:44:01Z"
+ content="""
+Thanks for your response.
+
+Your test for matchexpression doesn't show anything because you put `--mimeencoding=binary` part in it. So obviously expression will match.
+
+```
+$ git annex version
+git-annex version: 10.20220223-g8f6b52b77
+build flags: Assistant Webapp Pairing TorrentParser MagicMime Feeds Testsuite S3 WebDAV
+dependency versions: aws-0.22 bloomfilter-2.0.1.0 cryptonite-0.29 DAV-1.3.4 feed-1.3.2.0 ghc-8.10.7 http-client-0.7.9 persistent-sqlite-2.13.0.3 torrent-10000.1.1 uuid-1.3.15 yesod-1.6.1.2
+key/value backends: SHA256E SHA256 SHA512E SHA512 SHA224E SHA224 SHA384E SHA384 SHA3_256E SHA3_256 SHA3_512E SHA3_512 SHA3_224E SHA3_224 SHA3_384E SHA3_384 SKEIN256E SKEIN256 SKEIN512E SKEIN512 BLAKE2B256E BLAKE2B256 BLAKE2B512E BLAKE2B512 BLAKE2B160E BLAKE2B160 BLAKE2B224E BLAKE2B224 BLAKE2B384E BLAKE2B384 BLAKE2BP512E BLAKE2BP512 BLAKE2S256E BLAKE2S256 BLAKE2S160E BLAKE2S160 BLAKE2S224E BLAKE2S224 BLAKE2SP256E BLAKE2SP256 BLAKE2SP224E BLAKE2SP224 SHA1E SHA1 MD5E MD5 WORM URL X*
+remote types: git gcrypt p2p S3 bup directory rsync web bittorrent webdav adb tahoe glacier ddar git-lfs httpalso borg hook external
+operating system: mingw32 x86_64
+supported repository versions: 8 9 10
+upgrade supported from repository versions: 2 3 4 5 6 7 8 9 10
+local repository version: 10
+```
+
+I prepared a simple script to reproduce the bug
+
+```
+mkdir bug
+cd bug
+git init
+git annex init --version=10
+git annex config --set annex.largefiles 'mimeencoding=binary and largerthan=0'
+wget https://picsum.photos/id/237/200/300.jpg -O hello.jpg
+wget https://picsum.photos/id/237/200/300.jpg -O привет.jpg
+git annex add hello.jpg --verbose
+git annex add привет.jpg --verbose
+```
+
+It behaves as I described before. The file with Cyrillic letters is added as non-binary.
+
+I think it proves that something is not right with MagicMime library dealing with Cyrillic filenames
+"""]]

Added a comment: it is still there
diff --git a/doc/bugs/export__95__import__95__subdir_started_to_fail_some_times/comment_3_6b3e21c80fd75d153282cd4170f4b9d2._comment b/doc/bugs/export__95__import__95__subdir_started_to_fail_some_times/comment_3_6b3e21c80fd75d153282cd4170f4b9d2._comment
new file mode 100644
index 0000000000..cea1fea30b
--- /dev/null
+++ b/doc/bugs/export__95__import__95__subdir_started_to_fail_some_times/comment_3_6b3e21c80fd75d153282cd4170f4b9d2._comment
@@ -0,0 +1,20 @@
+[[!comment format=mdwn
+ username="yarikoptic"
+ avatar="http://cdn.libravatar.org/avatar/f11e9c84cb18d26a1748c33b48c924b4"
+ subject="it is still there"
+ date="2022-05-05T20:22:13Z"
+ content="""
+each month is a submodule, so the most recent one seems to be from 2 weeks ago
+
+```
+(git)smaug:/mnt/datasets/datalad/ci/git-annex[master]builds/2022
+$> git -C 05 grep 'export_import_subdir:.*FAIL'
+
+$> git -C 04 grep 'export_import_subdir:.*FAIL'
+cron-20220424/build-ubuntu.yaml-671-0346631d-failed/1_test-annex (normal, ubuntu-latest).txt:2022-04-24T03:08:45.8548538Z     export_import_subdir:                  FAIL (1.16s)
+cron-20220424/build-ubuntu.yaml-671-0346631d-failed/test-annex (normal, ubuntu-latest)/7_Run tests.txt:2022-04-24T03:08:45.8548535Z     export_import_subdir:                  FAIL (1.16s)
+git -C 04 grep 'export_import_subdir:.*FAIL'  5.08s user 2.04s system 369% cpu 1.927 total
+```
+
+and there were a few in march (do with `-C 03`)
+"""]]

implement dataUnits finally
Added support for "megabit" and related bandwidth units in
annex.stalldetection and everywhere else that git-annex parses data units.
Note that the short form is "Mbit" not "Mb" because that differs from "MB"
only in case, and git-annex parses units case-insensitively. It would be
horrible if two different versions of git-annex parsed the same value
differently, so I don't think "Mb" can be supported.
See comment for bonus sad story from my childhood.
Sponsored-by: Nicholas Golder-Manning
diff --git a/CHANGELOG b/CHANGELOG
index b391c69860..506f3cd0e7 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,13 @@
+git-annex (10.20220505) UNRELEASED; urgency=medium
+
+  * Added support for "megabit" and related bandwidth units
+    in annex.stalldetection and everywhere else that git-annex parses
+    data units. Note that the short form is "Mbit" not "Mb" because
+    that differs from "MB" only in case, and git-annex parses units
+    case-insensitively.
+
+ -- Joey Hess <id@joeyh.name>  Thu, 05 May 2022 15:08:07 -0400
+
 git-annex (10.20220504) upstream; urgency=medium
 
   * Ignore annex.numcopies set to 0 in gitattributes or git config,
diff --git a/Utility/DataUnits.hs b/Utility/DataUnits.hs
index a6c9ffcf19..7fe4a0a4ae 100644
--- a/Utility/DataUnits.hs
+++ b/Utility/DataUnits.hs
@@ -1,6 +1,6 @@
 {- data size display and parsing
  -
- - Copyright 2011 Joey Hess <id@joeyh.name>
+ - Copyright 2011-2022 Joey Hess <id@joeyh.name>
  -
  - License: BSD-2-clause
  -
@@ -22,13 +22,19 @@
  -
  - So, a committee was formed. And it arrived at a committee-like decision,
  - which satisfied noone, confused everyone, and made the world an uglier
- - place. As with all committees, this was meh.
+ - place. As with all committees, this was meh. Or in this case, "mib".
  -
  - And the drive manufacturers happily continued selling drives that are
  - increasingly smaller than you'd expect, if you don't count on your
  - fingers. But that are increasingly too big for anyone to much notice.
  - This caused me to need git-annex.
  -
+ - Meanwhile, over in telecommunications land, they were using entirely
+ - different units that differ only in capitalization sometimes.
+ - (At one point this convinced me that it was a good idea to buy an ISDN
+ - line because 128 kb/s sounded really fast! But it was really only 128
+ - kbit/s...)
+ -
  - Thus, I use units here that I loathe. Because if I didn't, people would
  - be confused that their drives seem the wrong size, and other people would
  - complain at me for not being standards compliant. And we call this
@@ -62,7 +68,7 @@ data Unit = Unit ByteSize Abbrev Name
 	deriving (Ord, Show, Eq)
 
 dataUnits :: [Unit]
-dataUnits = storageUnits ++ memoryUnits
+dataUnits = storageUnits ++ memoryUnits ++ bandwidthUnits
 
 {- Storage units are (stupidly) powers of ten. -}
 storageUnits :: [Unit]
@@ -75,7 +81,7 @@ storageUnits =
 	, Unit (p 3) "GB" "gigabyte"
 	, Unit (p 2) "MB" "megabyte"
 	, Unit (p 1) "kB" "kilobyte" -- weird capitalization thanks to committe
-	, Unit (p 0) "B" "byte"
+	, Unit 1 "B" "byte"
 	]
   where
 	p :: Integer -> Integer
@@ -92,15 +98,33 @@ memoryUnits =
 	, Unit (p 3) "GiB" "gibibyte"
 	, Unit (p 2) "MiB" "mebibyte"
 	, Unit (p 1) "KiB" "kibibyte"
-	, Unit (p 0) "B" "byte"
+	, Unit 1 "B" "byte"
 	]
   where
 	p :: Integer -> Integer
 	p n = 2^(n*10)
 
-{- Bandwidth units are only measured in bits if you're some crazy telco. -}
+{- Bandwidth units are (stupidly) measured in bits, not bytes, and are
+ - (also stupidly) powers of ten. 
+ -
+ - While it's fairly common for "Mb", "Gb" etc to be used, that differs
+ - from "MB", "GB", etc only in case, and readSize is case-insensitive.
+ - So "Mbit", "Gbit" etc are used instead to avoid parsing ambiguity.
+ -}
 bandwidthUnits :: [Unit]
-bandwidthUnits = error "stop trying to rip people off"
+bandwidthUnits =
+	[ Unit (p 8) "Ybit" "yottabit"
+	, Unit (p 7) "Zbit" "zettabit"
+	, Unit (p 6) "Ebit" "exabit"
+	, Unit (p 5) "Pbit" "petabit"
+	, Unit (p 4) "Tbit" "terabit"
+	, Unit (p 3) "Gbit" "gigabit"
+	, Unit (p 2) "Mbit" "megabit"
+	, Unit (p 1) "kbit" "kilobit" -- weird capitalization thanks to committe
+	]
+  where
+	p :: Integer -> Integer
+	p n = (1000^n) `div` 8
 
 {- Do you yearn for the days when men were men and megabytes were megabytes? -}
 oldSchoolUnits :: [Unit]
diff --git a/doc/todo/Mbps.mdwn b/doc/todo/Mbps.mdwn
index 8710bdd7c2..a411df7310 100644
--- a/doc/todo/Mbps.mdwn
+++ b/doc/todo/Mbps.mdwn
@@ -9,3 +9,9 @@ and match up with the other bandwidth displays.
 
 This might make sense as a per-remote configurable value. Allowing
 using MiB/s for a hard drive and Mbps for a network remote. --[[Joey]]
+
+> I've implemented bandwidthUnits now, but it's not used for display yet.
+> It is possible to specify such units in eg annex.stalldetection now.
+> Note that it uses Mbit, not Mb because that is just confusingly close to
+> "MB" and git-annex parses data units case insensitively. So the actual
+> display will end up being "Mbit/s" rather than Mbps probably. --[[Joey]]

close
diff --git a/doc/todo/speed_up___34__standalone_build__34___and__47__or_tests.mdwn b/doc/todo/speed_up___34__standalone_build__34___and__47__or_tests.mdwn
index dda3661390..3476c8e73e 100644
--- a/doc/todo/speed_up___34__standalone_build__34___and__47__or_tests.mdwn
+++ b/doc/todo/speed_up___34__standalone_build__34___and__47__or_tests.mdwn
@@ -37,3 +37,5 @@ OK (78.37s)
 All 994 tests passed (5576.99s)
 
 ```
+
+> [[done]] --[[Joey]]

comment
diff --git a/doc/bugs/export__95__import__95__subdir_started_to_fail_some_times/comment_2_8cfd20ada49f2441ad305b2225a738bb._comment b/doc/bugs/export__95__import__95__subdir_started_to_fail_some_times/comment_2_8cfd20ada49f2441ad305b2225a738bb._comment
new file mode 100644
index 0000000000..ff119adde7
--- /dev/null
+++ b/doc/bugs/export__95__import__95__subdir_started_to_fail_some_times/comment_2_8cfd20ada49f2441ad305b2225a738bb._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 2"""
+ date="2022-05-05T16:03:03Z"
+ content="""
+I checked on smaug, and there have been no new failures of this type
+since this bug was reported.
+"""]]

comment
diff --git a/doc/bugs/mimeencoding_detection_is_not_working/comment_1_dbcdb6ae6fb1d06f4d5cf27cba4c69be._comment b/doc/bugs/mimeencoding_detection_is_not_working/comment_1_dbcdb6ae6fb1d06f4d5cf27cba4c69be._comment
new file mode 100644
index 0000000000..68257631cd
--- /dev/null
+++ b/doc/bugs/mimeencoding_detection_is_not_working/comment_1_dbcdb6ae6fb1d06f4d5cf27cba4c69be._comment
@@ -0,0 +1,20 @@
+[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2022-05-05T14:50:13Z"
+ content="""
+Is your git-annex built with support for mime type 
+detection? Post the output of `git-annex version`
+
+I don't think it's possible that a filename can affect this.
+I'd only believe that if you showed me the same content in a file without a
+cryllic filename being treated differently.
+
+Also, when I try feeding all the data into `git-annex matchexpression`,
+it behaves as expected:
+
+	joey@darkstar:~/lib/tmp> if git-annex matchexpression --file=привет.jpg --size=100 --mimeencoding=binary --largefiles 'mimeencoding=binary and largerthan=0'; then echo match; fi
+	match
+
+You could try the same command to see if your git-annex behaves differently.
+"""]]

update
diff --git a/doc/thanks/list b/doc/thanks/list
index 8182ac7f83..8119ac92c1 100644
--- a/doc/thanks/list
+++ b/doc/thanks/list
@@ -126,3 +126,4 @@ Thomas Haller,
 Michael K., 
 Tobias Ammann, 
 David Oates, 
+k0ld, 

diff --git a/doc/bugs/mimeencoding_detection_is_not_working.mdwn b/doc/bugs/mimeencoding_detection_is_not_working.mdwn
new file mode 100644
index 0000000000..1944fc0d13
--- /dev/null
+++ b/doc/bugs/mimeencoding_detection_is_not_working.mdwn
@@ -0,0 +1,41 @@
+### Please describe the problem.
+
+Mimeencoding detection doesn't work for files with Cyrillic filenaes.
+
+### What steps will reproduce the problem?
+
+I have
+
+```
+git annex config --set annex.largefiles 'mimeencoding=binary and largerthan=0'
+```
+
+So I expect all binary files to be annexed.
+
+But I have some jpg file with Cyrillic letters in filename: привет.jpg
+
+```
+$ file --mime-encoding привет.jpg
+привет.jpg: binary
+
+$ git annex add привет.jpg --verbose
+add привет.jpg (non-large file; adding content to git repository) ok
+(recording state in git...)
+
+$ mv привет.jpg hello.jpg
+
+$ git annex add hello.jpg --verbose
+add hello.jpg
+ok
+(recording state in git...)
+```
+
+### What version of git-annex are you using? On what operating system?
+
+git-annex 10.20220223-g8f6b52b77
+
+Windows 11
+
+### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)
+
+I just started using it and I love it. I like it more than git LFS

add news item for git-annex 10.20220504
diff --git a/doc/news/version_10.20220504.mdwn b/doc/news/version_10.20220504.mdwn
new file mode 100644
index 0000000000..21c729a9e6
--- /dev/null
+++ b/doc/news/version_10.20220504.mdwn
@@ -0,0 +1,17 @@
+git-annex 10.20220504 released with [[!toggle text="these changes"]]
+[[!toggleable text="""  * Ignore annex.numcopies set to 0 in gitattributes or git config,
+    or by git-annex numcopies or by --numcopies, since that
+    configuration would make git-annex easily lose data.
+    Same for mincopies.
+  * assistant: When annex.autocommit is set, notice commits that
+    the user makes manually, and push them out to remotes promptly.
+  * multicast: Support uftp 5.0 by switching from aes256-cbc to
+    aes256-gcm.
+  * Fix test failure on NFS when cleaning up gpg temp directory.
+  * Fix a build failure with ghc 9.2.2.
+    Thanks, gnezdo for the patch.
+  * rsync 3.2.4 broke backwards-compatability by preventing exposing
+    filenames to the shell. Made the rsync and gcrypt special remotes
+    detect this and disable shellescape. Closes: #[1010397](http://bugs.debian.org/1010397)
+  * repair: Avoid treating refs/annex/last-index or other refs that
+    are not commit objects as evidence of repository corruption."""]]
\ No newline at end of file
diff --git a/doc/news/version_8.20211123.mdwn b/doc/news/version_8.20211123.mdwn
deleted file mode 100644
index f3741d50b4..0000000000
--- a/doc/news/version_8.20211123.mdwn
+++ /dev/null
@@ -1,7 +0,0 @@
-git-annex 8.20211123 released with [[!toggle text="these changes"]]
-[[!toggleable text="""  * Bugfix: When -J was enabled, getting files could leak an
-    ever-growing number of git cat-file processes.
-  * Support git's new "ort" resolver, which became the default in git 2.34.0,
-    and broke the test suite and automatic merge resolution of a conflict
-    between an annexed file and a non-annexed file.
-  * importfeed: Display url before starting youtube-dl download."""]]
\ No newline at end of file

fix git-annex repair false positive
Avoid treating refs/annex/last-index or other refs that are not commit
objects as evidence of repository corruption.
The repair code checks to find bad refs by trying to run `git log` on
them, and assumes that no output means something is broken. But git log
on a tree object is empty.
This was worth fixing generally, not as a special case, since it's certainly
possible that other things store tree or other objects in refs.
Sponsored-by: Max Thoursie on Patreon
diff --git a/CHANGELOG b/CHANGELOG
index 6f53e3e1ac..4178427d62 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -14,6 +14,8 @@ git-annex (10.20220323) UNRELEASED; urgency=medium
   * rsync 3.2.4 broke backwards-compatability by preventing exposing
     filenames to the shell. Made the rsync and gcrypt special remotes
     detect this and disable shellescape. Closes: #1010397
+  * repair: Avoid treating refs/annex/last-index or other refs that
+    are not commit objects as evidence of repository corruption.
 
  -- Joey Hess <id@joeyh.name>  Mon, 28 Mar 2022 14:46:10 -0400
 
diff --git a/Git/Repair.hs b/Git/Repair.hs
index 3e5c3fced0..7d47f84614 100644
--- a/Git/Repair.hs
+++ b/Git/Repair.hs
@@ -325,7 +325,11 @@ findUncorruptedCommit missing goodcommits branch r = do
  - the commit. Also adds to a set of commit shas that have been verified to
  - be good, which can be passed into subsequent calls to avoid
  - redundant work when eg, chasing down branches to find the first
- - uncorrupted commit. -}
+ - uncorrupted commit.
+ -
+ - When the sha is not a commit but some other git object, returns
+ - true, but does not add it to the set.
+ -}
 verifyCommit :: MissingObjects -> GoodCommits -> Sha -> Repo -> IO (Bool, GoodCommits)
 verifyCommit missing goodcommits commit r
 	| checkGoodCommit commit goodcommits = return (True, goodcommits)
@@ -337,16 +341,23 @@ verifyCommit missing goodcommits commit r
 			, Param (fromRef commit)
 			] r
 		let committrees = map (parse . decodeBL) ls
-		if any isNothing committrees || null committrees
-			then do
-				void cleanup
-				return (False, goodcommits)
-			else do
-				let cts = catMaybes committrees
-				ifM (cleanup <&&> check cts)
-					( return (True, addGoodCommits (map fst cts) goodcommits)
-					, return (False, goodcommits)
-					)
+		-- git log on an object that is not a commit will
+		-- succeed without any output
+		if null committrees
+			then ifM cleanup
+				( return (True, goodcommits)
+				, return (False, goodcommits)
+				)
+			else if any isNothing committrees
+				then do
+					void cleanup
+					return (False, goodcommits)
+				else do
+					let cts = catMaybes committrees
+					ifM (cleanup <&&> check cts)
+						( return (True, addGoodCommits (map fst cts) goodcommits)
+						, return (False, goodcommits)
+						)
   where
 	parse l = case words l of
 		(commitsha:treesha:[]) -> (,)
diff --git a/doc/bugs/git_annex_repair_failed__58___missing_objects.mdwn b/doc/bugs/git_annex_repair_failed__58___missing_objects.mdwn
index ac11a3a2be..687a81e903 100644
--- a/doc/bugs/git_annex_repair_failed__58___missing_objects.mdwn
+++ b/doc/bugs/git_annex_repair_failed__58___missing_objects.mdwn
@@ -45,4 +45,4 @@ The bug is very easy to reproduce. This happens in a bigger repository of mine,
 
 Using it daily, with small-sized repositories.
 
-
+> [[fixed|done]] --[[Joey]] 
diff --git a/doc/bugs/git_annex_repair_failed__58___missing_objects/comment_3_317bee33fe675a5abb6fe65540a33d25._comment b/doc/bugs/git_annex_repair_failed__58___missing_objects/comment_3_317bee33fe675a5abb6fe65540a33d25._comment
new file mode 100644
index 0000000000..3e971faa3b
--- /dev/null
+++ b/doc/bugs/git_annex_repair_failed__58___missing_objects/comment_3_317bee33fe675a5abb6fe65540a33d25._comment
@@ -0,0 +1,14 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 3"""
+ date="2022-05-04T15:06:31Z"
+ content="""
+Thank you, your "Reproducible Example" worked for me to reproduce it!
+Great work finding this and tracking down an easy way to reproduce it.
+
+So the root of the problem is refs/annex/last-index,
+which contains a tree object, not a commit object. That is unusual for a ref,
+but git-annex has a good reason to record a tree's ref there. 
+
+Fixed
+"""]]

Added a comment: should import follow symlinks?
diff --git a/doc/bugs/git-annex-import_imports_outside_of_directory/comment_2_e74c71a5574a8e99503ac83a27d48533._comment b/doc/bugs/git-annex-import_imports_outside_of_directory/comment_2_e74c71a5574a8e99503ac83a27d48533._comment
new file mode 100644
index 0000000000..b27d15a896
--- /dev/null
+++ b/doc/bugs/git-annex-import_imports_outside_of_directory/comment_2_e74c71a5574a8e99503ac83a27d48533._comment
@@ -0,0 +1,17 @@
+[[!comment format=mdwn
+ username="Ilya_Shlyakhter"
+ avatar="http://cdn.libravatar.org/avatar/1647044369aa7747829c38b9dcc84df0"
+ subject="should import follow symlinks?"
+ date="2022-05-03T19:36:10Z"
+ content="""
+I meant \"security hole\" more in the sense of the user themselves inadvertently importing (and then sharing) more than they meant to.
+E.g. I was importing a large subtree created by others, and had no clue it included symlinks outside the subtree; I only noticed this by accident, when the import started taking too long.
+
+>git-annex import $dir also follows symlinks inside $dir.
+
+In conjunction with the `mv` semantics, this seems risky... had I been using the original directory import, I'd have inadvertently deleted a large dataset (to which there was a symlink in the imported tree) from a place others expect to find it.  The unix `mv` command doesn't even have a flag to follow symlinks (nevermind defaulting to that).
+
+>It at least seems plausible that there might be users who import from ~/disk which is a symlink to /media/somethinglong, and rely on it following the symlink.
+
+It's certainly plausible; question is, should it be the default?   It's not the default in `cp` (you have to use `-L`) or in `tar` (have to use `-h`).  I think most people assume that importing from a directory remote is akin to doing a `cp -r` or `tar cf` from it.
+"""]]

comment
diff --git a/doc/bugs/git-annex-import_imports_outside_of_directory.mdwn b/doc/bugs/git-annex-import_imports_outside_of_directory.mdwn
index d2d18493ae..7a6d18da8e 100644
--- a/doc/bugs/git-annex-import_imports_outside_of_directory.mdwn
+++ b/doc/bugs/git-annex-import_imports_outside_of_directory.mdwn
@@ -25,3 +25,5 @@ Linux ctchpcpx163.merck.com 3.10.0-1160.6.1.el7.x86_64 #1 SMP Tue Nov 17 13:59:1
 ### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)
 
 That I use it enough to run into corner-case issues shows its continued usefulness :)
+
+[[!meta title="git-annex import follows symlinks"]]
diff --git a/doc/bugs/git-annex-import_imports_outside_of_directory/comment_1_376302b0bac94ee81c0dfdcb647f3b3b._comment b/doc/bugs/git-annex-import_imports_outside_of_directory/comment_1_376302b0bac94ee81c0dfdcb647f3b3b._comment
new file mode 100644
index 0000000000..56f6972056
--- /dev/null
+++ b/doc/bugs/git-annex-import_imports_outside_of_directory/comment_1_376302b0bac94ee81c0dfdcb647f3b3b._comment
@@ -0,0 +1,25 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2022-05-03T16:15:38Z"
+ content="""
+`git-annex import $dir` also follows symlinks inside $dir.
+So importing has been behaving this way since long before the directory
+special remote supported importtree.
+
+This is not a security hole, because if an attacker wants to make you 
+import `/foo` when importing `/bar`, and they have write access to bar,
+they are not limited to making a `/bar/foo -> /foo` symlink. They can just
+`cp -a /foo /bar` instead.
+
+I don't really think it would make much sense for any import to import
+symlinks as symlinks. If the symlink points outside the imported directory,
+that would result in a symlink that points outside the git repository,
+which is not something one often wants to check into a git repository.
+
+I don't know if I would really consider this a bug either. It at least
+seems plausible that there might be users who import from `~/disk`
+which is a symlink to `/media/somethinglong`, and rely on it following
+the symlink. I often make symlink aliases for mount points like that,
+though I have not imported from them.
+"""]]

disable shellescape for rsync 3.2.4
rsync 3.2.4 broke backwards-compatability by preventing exposing filenames
to the shell. Made the rsync and gcrypt special remotes detect this and
disable shellescape.
An alternative fix would have been to always set RSYNC_OLD_ARGS=1.
Which would avoid the overhead of probing rsync --help for each affected
remote. But that is really very fast to run, and it seemed better to switch
to the modern code path rather than keeping on using the bad old code path.
Sponsored-by: Tobias Ammann on Patreon
diff --git a/CHANGELOG b/CHANGELOG
index 263da8e6ea..6f53e3e1ac 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -11,6 +11,9 @@ git-annex (10.20220323) UNRELEASED; urgency=medium
   * Fix test failure on NFS when cleaning up gpg temp directory.
   * Fix a build failure with ghc 9.2.2.
     Thanks, gnezdo for the patch.
+  * rsync 3.2.4 broke backwards-compatability by preventing exposing
+    filenames to the shell. Made the rsync and gcrypt special remotes
+    detect this and disable shellescape. Closes: #1010397
 
  -- Joey Hess <id@joeyh.name>  Mon, 28 Mar 2022 14:46:10 -0400
 
diff --git a/Remote/GCrypt.hs b/Remote/GCrypt.hs
index 9662e75d4a..0b120806ad 100644
--- a/Remote/GCrypt.hs
+++ b/Remote/GCrypt.hs
@@ -130,7 +130,8 @@ gen' r u c gc rs = do
 	cst <- remoteCost gc $
 		if repoCheap r then nearlyCheapRemoteCost else expensiveRemoteCost
 	let (rsynctransport, rsyncurl, accessmethod) = rsyncTransportToObjects r gc
-	let rsyncopts = Remote.Rsync.genRsyncOpts c gc rsynctransport rsyncurl
+	protectsargs <- liftIO Remote.Rsync.probeRsyncProtectsArgs
+	let rsyncopts = Remote.Rsync.genRsyncOpts protectsargs c gc rsynctransport rsyncurl
 	let this = Remote 
 		{ uuid = u
 		, cost = cst
diff --git a/Remote/Rsync.hs b/Remote/Rsync.hs
index 81d60311e4..e7e9ff740f 100644
--- a/Remote/Rsync.hs
+++ b/Remote/Rsync.hs
@@ -16,7 +16,8 @@ module Remote.Rsync (
 	withRsyncScratchDir,
 	rsyncRemoteConfigs,
 	genRsyncOpts,
-	RsyncOpts
+	RsyncOpts,
+	probeRsyncProtectsArgs,
 ) where
 
 import Annex.Common
@@ -36,6 +37,7 @@ import Remote.Rsync.RsyncUrl
 import Crypto
 import Utility.Rsync
 import Utility.CopyFile
+import Utility.Process.Transcript
 import Messages.Progress
 import Utility.Metered
 import Types.Transfer
@@ -74,7 +76,8 @@ gen r u rc gc rs = do
 	cst <- remoteCost gc expensiveRemoteCost
 	(transport, url) <- rsyncTransport gc $
 		fromMaybe (giveup "missing rsyncurl") $ remoteAnnexRsyncUrl gc
-	let o = genRsyncOpts c gc transport url
+	protectsargs <- liftIO probeRsyncProtectsArgs
+	let o = genRsyncOpts protectsargs c gc transport url
 	let islocal = rsyncUrlIsPath $ rsyncUrl o
 	return $ Just $ specialRemote c
 		(fileStorer $ store o)
@@ -124,6 +127,18 @@ gen r u rc gc rs = do
 			, remoteStateHandle = rs
 			}
 
+-- | Since 3.2.4, rsync protects filenames from being exposed to the shell.
+newtype RsyncProtectsArgs = RsyncProtectsArgs Bool
+
+probeRsyncProtectsArgs :: IO RsyncProtectsArgs
+probeRsyncProtectsArgs = do
+	(helpoutput, _) <- processTranscript "rsync" ["--help"] Nothing
+	-- The --old-args option was added to disable the new arg
+	-- protection, so use it to detect when that feature is supported
+	-- by rsync, rather than parsing versions.
+	return (RsyncProtectsArgs $ "--old-args" `isInfixOf` helpoutput)
+
+
 -- Things used by genRsyncOpts
 rsyncRemoteConfigs :: [RemoteConfigFieldParser]
 rsyncRemoteConfigs = 
@@ -131,15 +146,17 @@ rsyncRemoteConfigs =
 		(FieldDesc "set to no to avoid usual shell escaping (not recommended)")
 	]
 
-genRsyncOpts :: ParsedRemoteConfig -> RemoteGitConfig -> Annex [CommandParam] -> RsyncUrl -> RsyncOpts
-genRsyncOpts c gc transport url = RsyncOpts
+genRsyncOpts :: RsyncProtectsArgs -> ParsedRemoteConfig -> RemoteGitConfig -> Annex [CommandParam] -> RsyncUrl -> RsyncOpts
+genRsyncOpts (RsyncProtectsArgs protectsargs) c gc transport url = RsyncOpts
 	{ rsyncUrl = url
 	, rsyncOptions = appendtransport $ opts []
 	, rsyncUploadOptions = appendtransport $
 		opts (remoteAnnexRsyncUploadOptions gc)
 	, rsyncDownloadOptions = appendtransport $
 		opts (remoteAnnexRsyncDownloadOptions gc)
-	, rsyncShellEscape = fromMaybe True (getRemoteConfigValue shellEscapeField c)
+	, rsyncShellEscape = if protectsargs
+		then False
+		else fromMaybe True (getRemoteConfigValue shellEscapeField c)
 	}
   where
 	appendtransport l = (++ l) <$> transport
diff --git a/doc/special_remotes/rsync.mdwn b/doc/special_remotes/rsync.mdwn
index 96e452b103..e9f54dc684 100644
--- a/doc/special_remotes/rsync.mdwn
+++ b/doc/special_remotes/rsync.mdwn
@@ -26,13 +26,14 @@ These parameters can be passed to `git annex initremote` to configure rsync:
   by [[git-annex-export]]. It will not be usable as a general-purpose
   special remote.
 
-* `shellescape` - Optional. Set to "no" to avoid shell escaping normally
-  done when using rsync over ssh. That escaping is needed with typical
-  setups, but not with some hosting providers that do not expose rsynced
-  filenames to the shell. You'll know you need this option if `git annex get`
-  from the special remote fails with an error message containing a single
-  quote (`'`) character. If that happens, you can run enableremote
-  setting shellescape=no.
+* `shellescape` - Optional. This has no effect when using rsync 3.2.4 or
+  newer. Set to "no" to avoid shell escaping
+  normally done when using older versions of rsync over ssh. That escaping
+  is needed with typical setups, but not with some hosting providers that do
+  not expose rsynced filenames to the shell. You'll know you need this
+  option if `git annex get` from the special remote fails with an error
+  message containing a single quote (`'`) character. If that happens, you
+  can run enableremote setting shellescape=no.
 
 * `chunk` - Enables [[chunking]] when storing large files.  
   This is typically not a win for rsync, so no need to enable it.

comment
diff --git a/doc/bugs/dropunused_returns_ok_but_doesn__39__t_delete_files/comment_1_61fc9ee219d3042e1e32e35b5086ef4e._comment b/doc/bugs/dropunused_returns_ok_but_doesn__39__t_delete_files/comment_1_61fc9ee219d3042e1e32e35b5086ef4e._comment
new file mode 100644
index 0000000000..173b56c207
--- /dev/null
+++ b/doc/bugs/dropunused_returns_ok_but_doesn__39__t_delete_files/comment_1_61fc9ee219d3042e1e32e35b5086ef4e._comment
@@ -0,0 +1,13 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2022-05-02T18:47:22Z"
+ content="""
+I tried to reproduce this, but dropunused removes the data here.
+So this is not as simple as it being broken, there is something else going
+on.
+
+Can you look in .git/annex/objects/
+and find the object files for one of these
+keys?
+"""]]

comment
diff --git a/doc/design/exporting_trees_to_special_remotes.mdwn b/doc/design/exporting_trees_to_special_remotes.mdwn
index 5d6746f5b2..f786af2cf5 100644
--- a/doc/design/exporting_trees_to_special_remotes.mdwn
+++ b/doc/design/exporting_trees_to_special_remotes.mdwn
@@ -2,8 +2,6 @@ For publishing content from a git-annex repository, it would be useful to
 be able to export a tree of files to a special remote, using the filenames
 and content from the tree.
 
-(See also [[todo/export]] and [[todo/dumb, unsafe, human-readable_backend]])
-
 Note that this document was written with the assumption that only git-annex
 is writing to the special remote. But
 [[importing_trees_from_special_remotes]] invalidates that assumption,
diff --git a/doc/forum/Making_git-annex_preserve_file_name__44___and_directory/comment_4_2c36e30b3e2f6874c63e03307207a6b9._comment b/doc/forum/Making_git-annex_preserve_file_name__44___and_directory/comment_4_2c36e30b3e2f6874c63e03307207a6b9._comment
new file mode 100644
index 0000000000..fc290138c0
--- /dev/null
+++ b/doc/forum/Making_git-annex_preserve_file_name__44___and_directory/comment_4_2c36e30b3e2f6874c63e03307207a6b9._comment
@@ -0,0 +1,11 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 4"""
+ date="2022-05-02T18:35:56Z"
+ content="""
+See this issue:
+<https://github.com/DanielDent/git-annex-remote-rclone/issues/35>
+
+That also has some discussion about git-annex-remote-googledrive 
+seeming to not be allowed by google and how to get around that problem.
+"""]]

comment, retitle
diff --git a/doc/bugs/git_error___34__update__95__ref_failed_for_ref__34__.mdwn b/doc/bugs/git_error___34__update__95__ref_failed_for_ref__34__.mdwn
index dc2ad5a3fe..4b0021bf87 100644
--- a/doc/bugs/git_error___34__update__95__ref_failed_for_ref__34__.mdwn
+++ b/doc/bugs/git_error___34__update__95__ref_failed_for_ref__34__.mdwn
@@ -35,3 +35,4 @@ It will download the content and fail afterwards
 ### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)
 I am not an experienced user but after using non-space-remotes-names I've been able to sync my phone with no major problems following the documentation in this page. So great work!
 
+[[!meta title="importtree remote with spaces in its name causes ref update failure with unncessarily ugly message; could fail earlier with a better message"]]
diff --git a/doc/bugs/git_error___34__update__95__ref_failed_for_ref__34__/comment_1_60ea64baccef82dc03ab300ca9b5e2b5._comment b/doc/bugs/git_error___34__update__95__ref_failed_for_ref__34__/comment_1_60ea64baccef82dc03ab300ca9b5e2b5._comment
new file mode 100644
index 0000000000..e7e96c8569
--- /dev/null
+++ b/doc/bugs/git_error___34__update__95__ref_failed_for_ref__34__/comment_1_60ea64baccef82dc03ab300ca9b5e2b5._comment
@@ -0,0 +1,26 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2022-05-02T18:29:05Z"
+ content="""
+This is similar to configuring git with a remote that has spaces in its
+name:
+
+	joey@darkstar:/tmp/z>git remote add "My Remote" ../x
+	fatal: 'My Remote' is not a valid remote name
+
+Or configuring a remote.foo.fetch with spaces in the name of the rev:
+
+	joey@darkstar:/tmp/y>git config remote.origin.fetch
+	+refs/heads/*:refs/remotes/Redmi Note 8/*
+	joey@darkstar:/tmp/y>git fetch origin
+	fatal: invalid refspec '+refs/heads/*:refs/remotes/Redmi Note 8/*'
+
+Importing from a special remote with importtree=yes is equivilant from
+pulling from a git remote, and needs to update a branch with a similar
+name as origin/master. And with a remote name with spaces, there is
+no legal branch name that will work.
+
+So, this has to fail. It could fail earlier, or with a better error
+message.
+"""]]

close and comment
diff --git a/doc/bugs/build_for_OpenBSD_is_outdated.mdwn b/doc/bugs/build_for_OpenBSD_is_outdated.mdwn
index db950ca301..c435ba531c 100644
--- a/doc/bugs/build_for_OpenBSD_is_outdated.mdwn
+++ b/doc/bugs/build_for_OpenBSD_is_outdated.mdwn
@@ -12,3 +12,5 @@ I'm hoping someone can pick this up and build a more up-to-date version.
 I've used it extensively during my time at university to keep track of materials and assignments. It ever let me down.
 Unfortunately I later lost the data due to multiple corrupted external disks and backup drives with LUKS where I didn't remember the password. Luckily I had no use for it anymore anyway.
 I'm hoping to use it again, this time in an effort to disconnect.
+
+> [[done]] --[[Joey]] 
diff --git a/doc/bugs/build_for_OpenBSD_is_outdated/comment_3_99e6c86785c91b8320ec1e7df1e02e2c._comment b/doc/bugs/build_for_OpenBSD_is_outdated/comment_3_99e6c86785c91b8320ec1e7df1e02e2c._comment
new file mode 100644
index 0000000000..fd5865bae5
--- /dev/null
+++ b/doc/bugs/build_for_OpenBSD_is_outdated/comment_3_99e6c86785c91b8320ec1e7df1e02e2c._comment
@@ -0,0 +1,13 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 3"""
+ date="2022-05-02T18:25:12Z"
+ content="""
+This is not the place to file bug reports about a distribution of git-annex
+by eg OpenBSD or a Linux distribution being out of date. In the future,
+file the bug with the distributor, which is where the changes need to
+happen to update it.
+
+The exception, of course, is if something about git-annex is preventing the
+distributor from upgrading it.
+"""]]

comment
diff --git a/doc/todo/nitpick__58___show_one_column_per_UUID_in_git_annex_lis/comment_1_e6d9efa01ddeb129469b3b8a4205cb3f._comment b/doc/todo/nitpick__58___show_one_column_per_UUID_in_git_annex_lis/comment_1_e6d9efa01ddeb129469b3b8a4205cb3f._comment
new file mode 100644
index 0000000000..dec51638b8
--- /dev/null
+++ b/doc/todo/nitpick__58___show_one_column_per_UUID_in_git_annex_lis/comment_1_e6d9efa01ddeb129469b3b8a4205cb3f._comment
@@ -0,0 +1,16 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2022-05-02T18:22:37Z"
+ content="""
+I think you would be better off using `git-annex whereis` in 
+this kind of situation.
+
+--unique-repos would be a fix specific to your own situation of having
+multiple names for a repo, but it would not help a user who had similar
+difficulty reading the output just because they have a lot of different
+repos. 
+
+The output of `git-annex whereis` is better suited to scaling to a lot of
+repos.
+"""]]

Fix a build failure with ghc 9.2.2
Thanks, gnezdo for the patch.
diff --git a/CHANGELOG b/CHANGELOG
index 5c1ae06361..263da8e6ea 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,8 @@ git-annex (10.20220323) UNRELEASED; urgency=medium
   * multicast: Support uftp 5.0 by switching from aes256-cbc to
     aes256-gcm.
   * Fix test failure on NFS when cleaning up gpg temp directory.
+  * Fix a build failure with ghc 9.2.2.
+    Thanks, gnezdo for the patch.
 
  -- Joey Hess <id@joeyh.name>  Mon, 28 Mar 2022 14:46:10 -0400
 
diff --git a/Types/Export.hs b/Types/Export.hs
index a5cb173e52..2d5419b91c 100644
--- a/Types/Export.hs
+++ b/Types/Export.hs
@@ -21,7 +21,7 @@ import Git.FilePath
 import Utility.Split
 import Utility.FileSystemEncoding
 
-import Data.ByteString.Short as S
+import qualified Data.ByteString.Short as S
 import qualified System.FilePath.Posix as Posix
 import GHC.Generics
 import Control.DeepSeq
diff --git a/doc/bugs/No_viable_build_plan_for_ghc_9.2.2/comment_1_8e7f9605f05880899eba7eae4e6f7ea0._comment b/doc/bugs/No_viable_build_plan_for_ghc_9.2.2/comment_1_8e7f9605f05880899eba7eae4e6f7ea0._comment
new file mode 100644
index 0000000000..7c13f68095
--- /dev/null
+++ b/doc/bugs/No_viable_build_plan_for_ghc_9.2.2/comment_1_8e7f9605f05880899eba7eae4e6f7ea0._comment
@@ -0,0 +1,13 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2022-05-02T18:13:47Z"
+ content="""
+I've applied the patch to git-annex to qualify the export.
+
+That failing test is an unrelated and now fixed bug.
+
+I can't do much more about the dependencies needing updates. Aside from
+maintaining them myself, which I kind of have been with aws to an extent,
+but not enough to be able to cut a new release with the fix.
+"""]]

filter out ExitSuccess
This avoids displaying the unexpected exit codes message when
the list is eg [ExitSuccess, ExitFailure 1].
Sponsored-by: Dartmouth College's Datalad project
diff --git a/Test/Framework.hs b/Test/Framework.hs
index 25be216408..3bb9c2d1d6 100644
--- a/Test/Framework.hs
+++ b/Test/Framework.hs
@@ -744,8 +744,8 @@ parallelTestRunner' numjobs opts mkts
 		exitcodes <- forConcurrently [1..numjobs] $ \_ -> 
 			worker [] nvar runone
 		unless (keepFailuresOption opts) finalCleanup
-		case nub (concat exitcodes) of
-			[ExitSuccess] -> exitSuccess
+		case nub (filter (/= ExitSuccess) (concat exitcodes)) of
+			[] -> exitSuccess
 			[ExitFailure 1] -> do
 				putStrLn "  (Failures above could be due to a bug in git-annex, or an incompatibility"
 				putStrLn "   with utilities, such as git, installed on this system.)"
diff --git a/doc/bugs/sporadic___40____63____41___fail_of_crypto_test__63__.mdwn b/doc/bugs/sporadic___40____63____41___fail_of_crypto_test__63__.mdwn
index 9585b1c030..95220dc79b 100644
--- a/doc/bugs/sporadic___40____63____41___fail_of_crypto_test__63__.mdwn
+++ b/doc/bugs/sporadic___40____63____41___fail_of_crypto_test__63__.mdwn
@@ -34,3 +34,5 @@ cron-20220418/build-ubuntu.yaml-665-0346631d-failed/test-annex (nfs-home, ubuntu
 
 [[!meta author=yoh]]
 [[!tag projects/datalad]]
+
+> [[fixed|done]] --[[Joey]] 
diff --git a/doc/bugs/sporadic___40____63____41___fail_of_crypto_test__63__/comment_3_89e9d65073a58188024290a2b32fda2e._comment b/doc/bugs/sporadic___40____63____41___fail_of_crypto_test__63__/comment_3_89e9d65073a58188024290a2b32fda2e._comment
new file mode 100644
index 0000000000..4467555517
--- /dev/null
+++ b/doc/bugs/sporadic___40____63____41___fail_of_crypto_test__63__/comment_3_89e9d65073a58188024290a2b32fda2e._comment
@@ -0,0 +1,22 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 3"""
+ date="2022-05-02T17:57:21Z"
+ content="""
+Noticed this:
+	
+	Test subprocesses exited with unexpected exit codes: [ExitSuccess,ExitSuccess,ExitSuccess,ExitSuccess,ExitSuccess,ExitSuccess,ExitSuccess,ExitSuccess,ExitSuccess,ExitSuccess,ExitSuccess,ExitSuccess,ExitSuccess,ExitFailure 1,ExitSuccess,ExitSuccess,ExitSuccess,ExitSuccess,ExitSuccess,ExitSuccess]
+
+In this test run: <https://github.com/datalad/git-annex/runs/6251550145?check_suite_focus=true>
+
+There was a test failure indeed there, which was another bug that
+is now fixed. But notice the `ExitFailure 1` in the list of exit codes.
+
+The code around handing exit codes has changed from before, and still had a bug,
+because a list with `ExitFailure 1` will cause that message, incorrectly.
+(Now fixed that.)
+
+But, this seems to show that it's no longer hiding the actual problem
+exit code when a test fails, which it was somehow apparently doing before.
+So I think this bug can be closed as fully fixed now.
+"""]]

avoid using removePathForcibly everywhere, it is unsafe
If the temp directory can somehow contain a hard link, it changes the
mode, which affects all other hard linked files. So, it's too unsafe
to use everywhere in git-annex, since hard links are possible in
multiple ways and it would be very hard to prove that every place that
uses a temp directory cannot possibly put a hard link in it.
Added a call to removeDirectoryForCleanup to test_crypto, which will
fix the problem that commit 17b20a24502aee3bfc5683146c3899a233295aea
was intending to fix, with a much smaller hammer.
Sponsored-by: Dartmouth College's Datalad project
diff --git a/Test.hs b/Test.hs
index d10ef8b4cf..a8d01bf6df 100644
--- a/Test.hs
+++ b/Test.hs
@@ -1794,7 +1794,12 @@ test_crypto = do
 		-- it needs to be able to store the agent socket there,
 		-- which can be problimatic when testing some filesystems.
 		absgpgtmp <- fromRawFilePath <$> absPath (toRawFilePath gpgtmp)
-		testscheme' scheme absgpgtmp
+		res <- testscheme' scheme absgpgtmp
+		-- gpg may still be running and would prevent
+		-- removeDirectoryRecursive from succeeding, so
+		-- force removal of the temp directory.
+		liftIO $ removeDirectoryForCleanup gpgtmp
+		return res
 	testscheme' scheme absgpgtmp = intmpclonerepo $ do
 		-- Since gpg uses a unix socket, which is limited to a
 		-- short path, use whichever is shorter of absolute
diff --git a/Utility/Tmp/Dir.hs b/Utility/Tmp/Dir.hs
index 4deda1297e..904b65a526 100644
--- a/Utility/Tmp/Dir.hs
+++ b/Utility/Tmp/Dir.hs
@@ -69,11 +69,4 @@ removeTmpDir tmpdir = liftIO $ whenM (doesDirectoryExist tmpdir) $ do
 	go tmpdir
 #endif
   where
-  	-- Use removePathForcibly when available, to avoid crashing
-	-- if some other process is removing files in the directory at the
-	-- same time.
-#if MIN_VERSION_directory(1,2,7)
-	go = removePathForcibly
-#else
 	go = removeDirectoryRecursive
-#endif
diff --git a/doc/bugs/Tests_v8_locked__58___rsync_remote__58___FAIL.mdwn b/doc/bugs/Tests_v8_locked__58___rsync_remote__58___FAIL.mdwn
index 70ba96f0e1..d7137d7cce 100644
--- a/doc/bugs/Tests_v8_locked__58___rsync_remote__58___FAIL.mdwn
+++ b/doc/bugs/Tests_v8_locked__58___rsync_remote__58___FAIL.mdwn
@@ -39,3 +39,5 @@ cron-20220420/build-ubuntu.yaml-667-0346631d-failed/2_test-annex (crippled-tmp,
 cron-20220420/build-ubuntu.yaml-667-0346631d-failed/4_test-annex (nfs-home, ubuntu-latest).txt:1511:2022-04-20T03:34:57.9524586Z     rsync remote:                                         FAIL (1.57s)
 ...
 ```
+
+> [[fixed|done]] --[[Joey]]
diff --git a/doc/bugs/Tests_v8_locked__58___rsync_remote__58___FAIL/comment_2_0327663314d2a8b2f0cab7536fdaa6bd._comment b/doc/bugs/Tests_v8_locked__58___rsync_remote__58___FAIL/comment_2_0327663314d2a8b2f0cab7536fdaa6bd._comment
new file mode 100644
index 0000000000..c6aa04f30b
--- /dev/null
+++ b/doc/bugs/Tests_v8_locked__58___rsync_remote__58___FAIL/comment_2_0327663314d2a8b2f0cab7536fdaa6bd._comment
@@ -0,0 +1,32 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 2"""
+ date="2022-05-02T17:08:15Z"
+ content="""
+Here is the bug in action:
+
+	-r--r--r-- 1 joey joey 30 May  2 12:42 .git/annex/objects/Gj/8J/SHA256E-s30--3d65cafd9435fde3867a527d75ff8aea05a3632cb60574d45e0fc277f06c8a64/SHA256E-s30--3d65cafd9435fde3867a527d75ff8aea05a3632cb60574d45e0fc277f06c8a64
+	joey@darkstar:/tmp/x>git-annex copy --to foo
+	copy x (to foo...) 
+	ok
+	joey@darkstar:/tmp/x>ls -l .git/annex/objects/Gj/8J/SHA256E-s30--3d65cafd9435fde3867a527d75ff8aea05a3632cb60574d45e0fc277f06c8a64/SHA256E-s30--3d65cafd9435fde3867a527d75ff8aea05a3632cb60574d45e0fc277f06c8a64
+	-rwxr--r-- 1 joey joey 30 May  2 12:42 .git/annex/objects/Gj/8J/SHA256E-s30--3d65cafd9435fde3867a527d75ff8aea05a3632cb60574d45e0fc277f06c8a64/SHA256E-s30--3d65cafd9435fde3867a527d75ff8aea05a3632cb60574d45e0fc277f06c8a64*
+
+At first I thought this was rsync modifying the permissions of the source file.
+
+But no... [[!commit 17b20a24502aee3bfc5683146c3899a233295aea]]
+changed how temp directories get cleaned up. removePathForcibly
+is actually changing the permissions of the object file hard link
+in the rsynctmp directory when deleting that directory. Which also 
+changes the permissions of the object file.
+
+Filed a bug on removePathForcibly. <https://github.com/haskell/directory/issues/135>
+
+I think this makes removePathForcibly unsuitable for general purpose
+use in git-annex, because there are just too many ways for a hard link
+to enter the picture. (Eg annex.thin, or even a user making their own
+hard link that git-annex does not know about.) 
+
+So, I've reverted that commit, and put in a more
+targeted fix for the problem it was addressing.
+"""]]

comment
diff --git a/doc/bugs/Tests_v8_locked__58___rsync_remote__58___FAIL/comment_1_809ca8b986ccadbc3940cc795d1823e8._comment b/doc/bugs/Tests_v8_locked__58___rsync_remote__58___FAIL/comment_1_809ca8b986ccadbc3940cc795d1823e8._comment
new file mode 100644
index 0000000000..4ce0ff8c8a
--- /dev/null
+++ b/doc/bugs/Tests_v8_locked__58___rsync_remote__58___FAIL/comment_1_809ca8b986ccadbc3940cc795d1823e8._comment
@@ -0,0 +1,15 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2022-05-02T16:53:13Z"
+ content="""
+The failure message is:
+
+      ./Test/Framework.hs:328:
+      able to modify annexed file's foo content
+
+Apparently some change to rsync in 3.2.4 has messed
+up something involving permissions.
+
+See also another bug caused by the new rsync <http://bugs.debian.org/1010397>
+"""]]

Added a comment: Reproducible Example
diff --git a/doc/bugs/git_annex_repair_failed__58___missing_objects/comment_2_7571de4a3ab7030a877fa198fe4810b1._comment b/doc/bugs/git_annex_repair_failed__58___missing_objects/comment_2_7571de4a3ab7030a877fa198fe4810b1._comment
new file mode 100644
index 0000000000..c9d2685b28
--- /dev/null
+++ b/doc/bugs/git_annex_repair_failed__58___missing_objects/comment_2_7571de4a3ab7030a877fa198fe4810b1._comment
@@ -0,0 +1,48 @@
+[[!comment format=mdwn
+ username="Albert"
+ avatar="http://cdn.libravatar.org/avatar/79aaf48dabedd7cd96a9117685fbe2cc"
+ subject="Reproducible Example"
+ date="2022-04-29T15:57:34Z"
+ content="""
+Fast reproducible example:
+
+```bash
+❯ mkdir annextest && cd annextest
+❯ git init
+Initialized empty Git repository in ./annextest/.git/
+❯ git annex init
+init  ok
+(recording state in git...)
+❯ echo \"some data\" > testfile
+❯ git status
+On branch master
+
+No commits yet
+
+Untracked files:
+  (use \"git add <file>...\" to include in what will be committed)
+	testfile
+
+nothing added to commit but untracked files present (use \"git add\" to track)
+❯ git annex add .
+add testfile 
+ok                                
+(recording state in git...)
+❯ git annex sync --content
+commit 
+[master (root-commit) d5dfa14] git-annex in user@dev:~/annextest
+ 1 file changed, 1 insertion(+)
+ create mode 120000 testfile
+ok
+❯ git annex repair
+repair Running git fsck ...
+Fsck found no problems. Checking for broken branches.
+Found problems, attempting repair.
+Some git branches refer to missing objects:
+	refs/annex/last-index
+To force a recovery to a usable state, retry with the --force parameter.
+failed
+repair: 1 failed
+```
+
+"""]]

Added a comment: Minimal Example
diff --git a/doc/bugs/git_annex_repair_failed__58___missing_objects/comment_1_9f2166545159890b05ea061e2ab21a4f._comment b/doc/bugs/git_annex_repair_failed__58___missing_objects/comment_1_9f2166545159890b05ea061e2ab21a4f._comment
new file mode 100644
index 0000000000..29fbc25774
--- /dev/null
+++ b/doc/bugs/git_annex_repair_failed__58___missing_objects/comment_1_9f2166545159890b05ea061e2ab21a4f._comment
@@ -0,0 +1,166 @@
+[[!comment format=mdwn
+ username="Albert"
+ avatar="http://cdn.libravatar.org/avatar/79aaf48dabedd7cd96a9117685fbe2cc"
+ subject="Minimal Example"
+ date="2022-04-29T15:14:20Z"
+ content="""
+I have managed to further reduce the setup to generate this bug. No connection to another device necessary.
+
+Steps:
+
+* Create a fresh repository with `git annex webapp`
+* Add an empty file to the repository named `exec.zip`
+
+Now the repository is broken.
+
+```bash
+❯ ls
+exec.zip
+❯ git annex repair
+repair Running git fsck ...
+Fsck found no problems. Checking for broken branches.
+Found problems, attempting repair.
+Some git branches refer to missing objects:
+	refs/annex/last-index
+To force a recovery to a usable state, retry with the --force parameter.
+failed
+repair: 1 failed
+❯ git annex repair --force
+repair Running git fsck ...
+Fsck found no problems. Checking for broken branches.
+Found problems, attempting repair.
+Successfully recovered repository!
+You should run \"git fsck\" to make sure, but it looks like everything was recovered ok.
+ok
+❯ git fsck
+Checking object directories: 100% (256/256), done.
+❯ git annex fsck
+fsck exec.zip (checksum...) ok
+(recording state in git...)
+❯ git annex repair
+repair Running git fsck ...
+Fsck found no problems. Checking for broken branches.
+Found problems, attempting repair.
+Some git branches refer to missing objects:
+	refs/annex/last-index
+To force a recovery to a usable state, retry with the --force parameter.
+failed
+repair: 1 failed
+```
+
+The log shows nothing interesting, as far as errors go.
+
+```bash
+[2022-04-29 16:52:08.770880358] (Utility.Process) process [8244] chat: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"cat-file\",\"--batch\"]
+[2022-04-29 16:52:08.773934773] (Utility.Process) process [8245] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"ls-files\",\"--stage\",\"-z\",\"--\",\".\"]
+[2022-04-29 16:52:08.775028928] (Utility.Process) process [8245] done ExitSuccess
+[2022-04-29 16:52:08.775560498] (Utility.Process) process [8246] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"ls-files\",\"--stage\",\"-z\",\"--\",\".\"]
+[2022-04-29 16:52:08.77720598] (Utility.Process) process [8246] done ExitSuccess
+[2022-04-29 16:52:08.77980743] (Utility.Process) process [8247] chat: nice [\"ionice\",\"-c3\",\"/usr/bin/git-annex\",\"remotedaemon\",\"--foreground\"]
+[2022-04-29 16:52:08.831064871] (NetWatcher) Using running DBUS service org.freedesktop.NetworkManager to monitor network connection events.
+[2022-04-29 16:52:08.83204466] (MountWatcher) Using running DBUS service org.freedesktop.UDisks2 to monitor mount events.
+[2022-04-29 16:52:08.834446186] (Utility.Process) process [8248] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"ls-tree\",\"--full-tree\",\"-z\",\"--\",\"refs/heads/git-annex\",\"uuid.log\",\"remote.log\",\"trust.log\",\"group.log\",\"numcopies.log\",\"mincopies.log\",\"schedule.log\",\"preferred-content.log\",\"required-content.log\",\"group-preferred-content.log\"]
+[2022-04-29 16:52:08.835869051] (Utility.Process) process [8248] done ExitSuccess
+[2022-04-29 16:52:08.83662762] (Utility.Process) process [8249] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"show-ref\",\"git-annex\"]
+[2022-04-29 16:52:08.836789466] (TransferWatcher) watching for transfers
+[2022-04-29 16:52:08.837846991] (Utility.Process) process [8249] done ExitSuccess
+[2022-04-29 16:52:08.838365064] (Utility.Process) process [8250] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"show-ref\",\"--hash\",\"refs/heads/git-annex\"]
+[2022-04-29 16:52:08.839508977] (Utility.Process) process [8250] done ExitSuccess
+[2022-04-29 16:52:08.83982672] (Utility.Process) process [8251] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"log\",\"refs/heads/git-annex..8d3e39db152fe946720fc814daaa892ea9b5ae56\",\"--pretty=%H\",\"-n1\"]
+[2022-04-29 16:52:08.841105024] (Utility.Process) process [8251] done ExitSuccess
+[2022-04-29 16:52:08.841851852] (Utility.Process) process [8252] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"ls-tree\",\"--full-tree\",\"-z\",\"--\",\"refs/heads/git-annex\",\"uuid.log\",\"remote.log\",\"trust.log\",\"group.log\",\"numcopies.log\",\"mincopies.log\",\"schedule.log\",\"preferred-content.log\",\"required-content.log\",\"group-preferred-content.log\"]
+[2022-04-29 16:52:08.843105261] (Utility.Process) process [8252] done ExitSuccess
+[2022-04-29 16:52:08.843428605] (Utility.Process) process [8253] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"symbolic-ref\",\"-q\",\"HEAD\"]
+[2022-04-29 16:52:08.844400262] (Utility.Process) process [8253] done ExitSuccess
+[2022-04-29 16:52:08.844653432] (Utility.Process) process [8254] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"show-ref\",\"refs/heads/adjusted/master(unlocked)\"]
+[2022-04-29 16:52:08.845849783] (Utility.Process) process [8254] done ExitSuccess
+(scanning...) [2022-04-29 16:52:08.848533041] (Merger) watching .git/refs
+[2022-04-29 16:52:08.848937085] (Utility.Process) process [8255] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"ls-files\",\"-z\",\"--deleted\",\"--\",\".\"]
+[2022-04-29 16:52:08.850610811] (Utility.Process) process [8255] done ExitSuccess
+(started...) 
+[2022-04-29 16:52:08.85076117] (Watcher) watching .
+[2022-04-29 16:52:10.784464555] (TransferScanner) starting scan of [Remote { name =\"bittorrent\" },Remote { name =\"web\" }]
+[2022-04-29 16:52:10.786005379] (Utility.Process) process [8266] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"ls-files\",\"-z\",\"--cached\",\"--\"]
+[2022-04-29 16:52:10.78835403] (Utility.Process) process [8266] done ExitSuccess
+[2022-04-29 16:52:10.788482208] (TransferScanner) finished scan of [Remote { name =\"bittorrent\" },Remote { name =\"web\" }]
+[2022-04-29 16:52:38.780947807] (Utility.Process) process [8244] done ExitSuccess
+[2022-04-29 16:52:48.957820025] (Utility.Process) process [8373] chat: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"cat-file\",\"--batch-check=%(objectname) %(objecttype) %(objectsize)\"]
+[2022-04-29 16:52:48.961668832] (Utility.Process) process [8374] chat: git [\"--git-dir=.git\",\"--work-tree=.\",\"check-ignore\",\"-z\",\"--stdin\",\"--verbose\",\"--non-matching\"]
+[2022-04-29 16:52:48.96311124] (Watcher) add exec.zip
+[2022-04-29 16:52:48.963830706] (Watcher) add exec.zip
+[2022-04-29 16:52:48.974374383] (Utility.Process) process [8380] read: lsof [\"-F0can\",\"+d\",\".git/annex/watchtmp/\"]
+lsof: WARNING: can't stat() fuse.portal file system /run/user/1000/doc
+      Output information may be incomplete.
+[2022-04-29 16:52:49.063916257] (Utility.Process) process [8380] done ExitFailure 1
+[2022-04-29 16:52:49.0643913] (Utility.Process) process [8391] chat: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"check-attr\",\"-z\",\"--stdin\",\"annex.backend\",\"annex.largefiles\",\"annex.numcopies\",\"annex.mincopies\",\"--\"]
+add exec.zip [2022-04-29 16:52:49.067319092] (Utility.Process) process [8392] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"-c\",\"filter.annex.smudge=\",\"-c\",\"filter.annex.clean=\",\"-c\",\"filter.annex.process=\",\"write-tree\"]
+[2022-04-29 16:52:49.068674137] (Utility.Process) process [8392] done ExitSuccess
+[2022-04-29 16:52:49.069809159] (Utility.Process) process [8393] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"show-ref\",\"--hash\",\"refs/annex/last-index\"]
+[2022-04-29 16:52:49.071868139] (Utility.Process) process [8393] done ExitFailure 1
+[2022-04-29 16:52:49.086327935] (Utility.Process) process [8394] chat: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"cat-file\",\"--batch\"]
+[2022-04-29 16:52:49.089453108] (Utility.Process) process [8395] chat: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"hash-object\",\"-w\",\"--stdin-paths\",\"--no-filters\"]
+ok
+[2022-04-29 16:52:49.091145891] (Committer) committing 1 changes
+(recording state in git...)
+[2022-04-29 16:52:49.091604661] (Utility.Process) process [8396] feed: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"update-index\",\"-z\",\"--index-info\"]
+[2022-04-29 16:52:49.093194636] (Utility.Process) process [8396] done ExitSuccess
+[2022-04-29 16:52:49.093559897] (Utility.Process) process [8397] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"symbolic-ref\",\"-q\",\"HEAD\"]
+[2022-04-29 16:52:49.094699948] (Utility.Process) process [8397] done ExitSuccess
+[2022-04-29 16:52:49.095498501] (Utility.Process) process [8398] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"show-ref\",\"--hash\",\"refs/heads/adjusted/master(unlocked)\"]
+[2022-04-29 16:52:49.096545809] (Utility.Process) process [8398] done ExitSuccess
+[2022-04-29 16:52:49.096818906] (Utility.Process) process [8399] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"write-tree\"]
+[2022-04-29 16:52:49.098254637] (Utility.Process) process [8399] done ExitSuccess
+[2022-04-29 16:52:49.099010966] (Utility.Process) process [8400] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"rev-parse\",\"--verify\",\"--quiet\",\"fe77efb32c94158eff6697b39cb344f45584102a:\"]
+[2022-04-29 16:52:49.100061557] (Utility.Process) process [8400] done ExitSuccess
+[2022-04-29 16:52:49.100317245] (Utility.Process) process [8401] chat: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"commit-tree\",\"5234be1a3bf0adce43729baa1497a560b7cbebd7\",\"--no-gpg-sign\",\"-p\",\"fe77efb32c94158eff6697b39cb344f45584102a\"]
+[2022-04-29 16:52:49.101685356] (Utility.Process) process [8401] done ExitSuccess
+[2022-04-29 16:52:49.102371807] (Utility.Process) process [8402] call: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"update-ref\",\"refs/heads/adjusted/master(unlocked)\",\"4be985a06abd216c02f2fdd03ca40a85807bb96b\"]
+[2022-04-29 16:52:49.103729957] (Utility.Process) process [8402] done ExitSuccess
+[2022-04-29 16:52:49.10456259] (Utility.Process) process [8403] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"show-ref\",\"--hash\",\"refs/basis/adjusted/master(unlocked)\"]
+[2022-04-29 16:52:49.105752856] (Utility.Process) process [8403] done ExitSuccess
+[2022-04-29 16:52:49.106040828] (Utility.Process) process [8404] chat: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"cat-file\",\"--batch\"]
+[2022-04-29 16:52:49.107590591] (Utility.Process) process [8405] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"log\",\"refs/basis/adjusted/master(unlocked)..refs/heads/adjusted/master(unlocked)\",\"--pretty=%H\",\"--reverse\"]
+[2022-04-29 16:52:49.108889132] (Utility.Process) process [8405] done ExitSuccess
+[2022-04-29 16:52:49.110083161] (Utility.Process) process [8406] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"show\",\"-z\",\"--raw\",\"--no-renames\",\"-l0\",\"--no-abbrev\",\"--pretty=\",\"--raw\",\"4be985a06abd216c02f2fdd03ca40a85807bb96b\"]
+[2022-04-29 16:52:49.113120499] (Utility.Process) process [8407] chat: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"mktree\",\"--batch\",\"-z\"]
+[2022-04-29 16:52:49.113389197] (Utility.Process) process [8408] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"ls-tree\",\"--full-tree\",\"-z\",\"-r\",\"-t\",\"--\",\"c57819725af0809e4a82dd79efc9cb398e9b5f5e\"]
+[2022-04-29 16:52:49.115008909] (Utility.Process) process [8408] done ExitSuccess
+[2022-04-29 16:52:49.115220429] (Utility.Process) process [8407] done ExitSuccess
+[2022-04-29 16:52:49.115274136] (Utility.Process) process [8406] done ExitSuccess
+[2022-04-29 16:52:49.116504243] (Utility.Process) process [8409] chat: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"commit-tree\",\"1ea671eeef97631e93e128bf3ebe8a6d26ea5c26\",\"--no-gpg-sign\",\"-p\",\"c57819725af0809e4a82dd79efc9cb398e9b5f5e\"]
+[2022-04-29 16:52:49.118748333] (Utility.Process) process [8409] done ExitSuccess
+[2022-04-29 16:52:49.119085462] (Utility.Process) process [8410] call: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"update-ref\",\"refs/basis/adjusted/master(unlocked)\",\"c5dc2761bf626f373d382e2e137a030d54cb830f\"]
+[2022-04-29 16:52:49.120787007] (Utility.Process) process [8410] done ExitSuccess
+[2022-04-29 16:52:49.121084941] (Utility.Process) process [8411] call: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"update-ref\",\"refs/heads/master\",\"c5dc2761bf626f373d382e2e137a030d54cb830f\"]
+[2022-04-29 16:52:49.122396315] (Utility.Process) process [8411] done ExitSuccess
+[2022-04-29 16:52:49.123421452] (Utility.Process) process [8412] chat: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"commit-tree\",\"5234be1a3bf0adce43729baa1497a560b7cbebd7\",\"--no-gpg-sign\",\"-p\",\"c5dc2761bf626f373d382e2e137a030d54cb830f\"]
+[2022-04-29 16:52:49.125285001] (Utility.Process) process [8412] done ExitSuccess
+[2022-04-29 16:52:49.125594743] (Utility.Process) process [8413] call: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"update-ref\",\"-m\",\"rebasing adjusted branch on top of updated original branch\",\"refs/heads/adjusted/master(unlocked)\",\"5e2aa046891e88e6272556602fd783df737dd96b\"]
+[2022-04-29 16:52:49.127414941] (Utility.Process) process [8413] done ExitSuccess
+[2022-04-29 16:52:49.127755666] (Utility.Process) process [8414] call: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"branch\",\"-f\",\"synced/master\",\"refs/heads/master\"]
+[2022-04-29 16:52:49.129567194] (Utility.Process) process [8414] done ExitSuccess
+[2022-04-29 16:52:49.130874993] (Utility.Process) process [8415] feed: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"update-index\",\"-z\",\"--index-info\"]
+[2022-04-29 16:52:49.13224422] (Utility.Process) process [8415] done ExitSuccess
+[2022-04-29 16:52:49.132752938] (Utility.Process) process [8416] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"show-ref\",\"--hash\",\"refs/heads/git-annex\"]
+[2022-04-29 16:52:49.134298542] (Utility.Process) process [8416] done ExitSuccess
+(recording state in git...)
+[2022-04-29 16:52:49.134910162] (Utility.Process) process [8417] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"write-tree\"]
+[2022-04-29 16:52:49.136629434] (Utility.Process) process [8417] done ExitSuccess
+[2022-04-29 16:52:49.137062879] (Utility.Process) process [8418] chat: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"commit-tree\",\"fdebaeafd3e1ebbc439d1a74184ff97e53071472\",\"--no-gpg-sign\",\"-p\",\"refs/heads/git-annex\"]
+[2022-04-29 16:52:49.138527773] (Utility.Process) process [8418] done ExitSuccess
+[2022-04-29 16:52:49.13896562] (Utility.Process) process [8419] call: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"update-ref\",\"refs/heads/git-annex\",\"127321afb17f5281528fc71d54c9166ce177596c\"]
+[2022-04-29 16:52:49.140314152] (Utility.Process) process [8419] done ExitSuccess
+[2022-04-29 16:52:49.143236655] (Utility.Process) process [8420] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"log\",\"refs/heads/master..refs/heads/synced/master\",\"--pretty=%H\",\"-n1\"]
+[2022-04-29 16:52:49.144619131] (Utility.Process) process [8420] done ExitSuccess
+[2022-04-29 16:52:49.144959688] (Utility.Process) process [8421] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"show-ref\",\"git-annex\"]
+[2022-04-29 16:52:49.146275708] (Utility.Process) process [8421] done ExitSuccess
+[2022-04-29 16:52:49.146554643] (Utility.Process) process [8422] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"show-ref\",\"--hash\",\"refs/heads/git-annex\"]
+[2022-04-29 16:52:49.147803214] (Utility.Process) process [8422] done ExitSuccess
+[2022-04-29 16:52:49.148107921] (Utility.Process) process [8423] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"log\",\"refs/heads/git-annex..127321afb17f5281528fc71d54c9166ce177596c\",\"--pretty=%H\",\"-n1\"]
+[2022-04-29 16:52:49.149518193] (Utility.Process) process [8423] done ExitSuccess
+[2022-04-29 16:53:08.845070487] (Utility.Process) process [8451] read: git [\"--git-dir=.git\",\"--work-tree=.\",\"--literal-pathspecs\",\"ls-tree\",\"--full-tree\",\"-z\",\"--\",\"refs/heads/git-annex\",\"uuid.log\",\"remote.log\",\"trust.log\",\"group.log\",\"numcopies.log\",\"mincopies.log\",\"schedule.log\",\"preferred-content.log\",\"required-content.log\",\"group-preferred-content.log\"]
+[2022-04-29 16:53:08.848424429] (Utility.Process) process [8451] done ExitSuccess
+```
+
+Why causes a single file `git annex repair` to fail?
+"""]]

diff --git a/doc/todo/nitpick__58___show_one_column_per_UUID_in_git_annex_lis.mdwn b/doc/todo/nitpick__58___show_one_column_per_UUID_in_git_annex_lis.mdwn
new file mode 100644
index 0000000000..0a6d34eff1
--- /dev/null
+++ b/doc/todo/nitpick__58___show_one_column_per_UUID_in_git_annex_lis.mdwn
@@ -0,0 +1,13 @@
+Hi Joey
+
+Thanks for an amazing tool!
+
+I have a minor nitpick regarding git annex list output when using "alternative" git remotes for the same repository.
+
+My use case: I have a several devices which can be accessed either remotely via a ssh jump host, or locally via a 192.168.x.x address (git remotes like deviceN-local, deviceN-remote). This means I have two remotes for every unique repo. As git annex list outputs one column of Xs per remote, this means I have duplicates in there for every device, which causes the list to be very wide and hard to read with more than a couple of remotes (I have 8 unique=16 remotes in one repo).
+
+
+Would it be possible to get git annex list to (optionally) output one column per unique remote/UUID, e.g. by taking the name of the lowest cost remote with each UUID? This could be behind a flag, e.g. --unique-repos or similar.
+
+Best,
+KM

report about rsync test fail
diff --git a/doc/bugs/Tests_v8_locked__58___rsync_remote__58___FAIL.mdwn b/doc/bugs/Tests_v8_locked__58___rsync_remote__58___FAIL.mdwn
new file mode 100644
index 0000000000..70ba96f0e1
--- /dev/null
+++ b/doc/bugs/Tests_v8_locked__58___rsync_remote__58___FAIL.mdwn
@@ -0,0 +1,41 @@
+### Please describe the problem.
+
+```
+2022-04-28T03:40:48.4460611Z   Repo Tests v8 locked
+2022-04-28T03:40:48.4460898Z     Init Tests
+2022-04-28T03:40:48.4461239Z       init:                                               OK (0.08s)
+2022-04-28T03:40:48.4461605Z       add:                                                OK (0.31s)
+2022-04-28T03:40:48.4461985Z     preferred content:                                    OK (1.26s)
+2022-04-28T03:40:48.4462414Z     rsync remote:                                         FAIL (0.63s)
+```
+
+across setups:
+
+```
+(git)smaug:/mnt/datasets/datalad/ci/git-annex/builds/2022/04[master]cron-20220428
+$> git grep -n FAIL| grep test-annex                                                                
+build-macos.yaml-670-0346631d-failed/1_test-annex (normal, macos-10.15).txt:1580:2022-04-28T02:40:19.9278580Z     rsync remote:                                 FAIL (1.44s)
+build-macos.yaml-670-0346631d-failed/2_test-annex (crippled-tmp, macos-10.15).txt:1692:2022-04-28T02:44:48.5060190Z     rsync remote:                                 FAIL (1.41s)
+build-macos.yaml-670-0346631d-failed/3_test-annex (normal, macos-latest).txt:1581:2022-04-28T02:40:39.2774670Z     rsync remote:                                 FAIL (1.32s)
+build-macos.yaml-670-0346631d-failed/test-annex (crippled-tmp, macos-10.15)/8_Run tests.txt:1425:2022-04-28T02:44:48.5060140Z     rsync remote:                                 FAIL (1.41s)
+build-macos.yaml-670-0346631d-failed/test-annex (normal, macos-10.15)/8_Run tests.txt:1314:2022-04-28T02:40:19.9278580Z     rsync remote:                                 FAIL (1.44s)
+build-macos.yaml-670-0346631d-failed/test-annex (normal, macos-latest)/8_Run tests.txt:1314:2022-04-28T02:40:39.2774650Z     rsync remote:                                 FAIL (1.32s)
+build-ubuntu.yaml-675-0346631d-failed/1_test-annex (normal, ubuntu-latest).txt:1427:2022-04-28T03:40:48.4462418Z     rsync remote:                                         FAIL (0.63s)
+build-ubuntu.yaml-675-0346631d-failed/2_test-annex (crippled-tmp, ubuntu-latest).txt:1577:2022-04-28T03:41:44.8645593Z     rsync remote:                                         FAIL (0.64s)
+build-ubuntu.yaml-675-0346631d-failed/4_test-annex (nfs-home, ubuntu-latest).txt:1513:2022-04-28T03:45:52.6285760Z     rsync remote:                                         FAIL (1.56s)
+build-ubuntu.yaml-675-0346631d-failed/test-annex (crippled-tmp, ubuntu-latest)/7_Run tests.txt:1334:2022-04-28T03:41:44.8645589Z     rsync remote:                                         FAIL (0.64s)
+build-ubuntu.yaml-675-0346631d-failed/test-annex (nfs-home, ubuntu-latest)/7_Run tests.txt:1270:2022-04-28T03:45:52.6285755Z     rsync remote:                                         FAIL (1.56s)
+build-ubuntu.yaml-675-0346631d-failed/test-annex (normal, ubuntu-latest)/7_Run tests.txt:1185:2022-04-28T03:40:48.4462414Z     rsync remote:                                         FAIL (0.63s)
+
+```
+
+and for some time already (as pinged in an email) -- since 20220420
+
+
+```
+$> git grep -n rsync.*FAIL| grep test-annex | grep ubuntu-late
+cron-20220420/build-ubuntu.yaml-667-0346631d-failed/1_test-annex (normal, ubuntu-latest).txt:1427:2022-04-20T03:30:39.0198567Z     rsync remote:                                         FAIL (0.70s)
+cron-20220420/build-ubuntu.yaml-667-0346631d-failed/2_test-annex (crippled-tmp, ubuntu-latest).txt:1582:2022-04-20T03:31:17.9571887Z     rsync remote:                                         FAIL (0.62s)
+cron-20220420/build-ubuntu.yaml-667-0346631d-failed/4_test-annex (nfs-home, ubuntu-latest).txt:1511:2022-04-20T03:34:57.9524586Z     rsync remote:                                         FAIL (1.57s)
+...
+```

diff --git a/doc/bugs/git_annex_repair_failed__58___missing_objects.mdwn b/doc/bugs/git_annex_repair_failed__58___missing_objects.mdwn
new file mode 100644
index 0000000000..ac11a3a2be
--- /dev/null
+++ b/doc/bugs/git_annex_repair_failed__58___missing_objects.mdwn
@@ -0,0 +1,48 @@
+### Please describe the problem.
+
+Git annex finds problems with the repository after only adding and syncing a single empty file to the repository.
+
+### What steps will reproduce the problem?
+
+* Create one repository each on devices `A` and `B` via webapp
+* Pair with a passphrase via webapp
+* Wait until full sync
+* Make sure `git annex repair` finds no problems
+* Create an empty file `test.sh` on device `A`
+* Wait until both webapps show sync and transfer has completed
+
+Both repositories are now in a bad state.
+
+* `git annex repair` reports error code `repair: 1 failed`, `--force` seems to repair, but the next `git annex repair` always fails again with `repair: 1 failed`
+* This is true for both repositories
+
+
+### What version of git-annex are you using? On what operating system?
+
+`Linux 5.17.3-arch1-1 #1 SMP PREEMPT x86_64 GNU/Linux` device `A`
+
+`Linux 5.17.4-arch1-1 #1 SMP PREEMPT x86_64 GNU/Linux` device `B`
+
+Version provided from package manager:
+
+```
+git-annex version: 10.20220322-g959beeea9
+build flags: Assistant Webapp Pairing Inotify DBus DesktopNotify TorrentParser MagicMime Feeds Testsuite S3 WebDAV
+dependency versions: aws-0.22 bloomfilter-2.0.1.0 cryptonite-0.30 DAV-1.3.4 feed-1.3.2.1 ghc-9.0.2 http-client-0.7.11 persistent-sqlite-2.13.0.3 torrent-10000.1.1 uuid-1.3.15 yesod-1.6.2
+key/value backends: SHA256E SHA256 SHA512E SHA512 SHA224E SHA224 SHA384E SHA384 SHA3_256E SHA3_256 SHA3_512E SHA3_512 SHA3_224E SHA3_224 SHA3_384E SHA3_384 SKEIN256E SKEIN256 SKEIN512E SKEIN512 BLAKE2B256E BLAKE2B256 BLAKE2B512E BLAKE2B512 BLAKE2B160E BLAKE2B160 BLAKE2B224E BLAKE2B224 BLAKE2B384E BLAKE2B384 BLAKE2BP512E BLAKE2BP512 BLAKE2S256E BLAKE2S256 BLAKE2S160E BLAKE2S160 BLAKE2S224E BLAKE2S224 BLAKE2SP256E BLAKE2SP256 BLAKE2SP224E BLAKE2SP224 SHA1E SHA1 MD5E MD5 WORM URL X*
+remote types: git gcrypt p2p S3 bup directory rsync web bittorrent webdav adb tahoe glacier ddar git-lfs httpalso borg hook external
+operating system: linux x86_64
+supported repository versions: 8 9 10
+upgrade supported from repository versions: 0 1 2 3 4 5 6 7 8 9 10
+```
+
+
+### Please provide any additional information below.
+
+The bug is very easy to reproduce. This happens in a bigger repository of mine, where I `git annex repair` doesn't seem to do anything. I narrowed it down to this example and I hope it's the same cause.
+
+### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)
+
+Using it daily, with small-sized repositories.
+
+

Added a comment
diff --git a/doc/forum/Rebuild__47__populate_archive_bypassing___34__here__34___repo/comment_3_652d9957ead7dbf4feb3ba096137d22b._comment b/doc/forum/Rebuild__47__populate_archive_bypassing___34__here__34___repo/comment_3_652d9957ead7dbf4feb3ba096137d22b._comment
new file mode 100644
index 0000000000..b1563feaed
--- /dev/null
+++ b/doc/forum/Rebuild__47__populate_archive_bypassing___34__here__34___repo/comment_3_652d9957ead7dbf4feb3ba096137d22b._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="Lukey"
+ avatar="http://cdn.libravatar.org/avatar/c7c08e2efd29c692cc017c4a4ca3406b"
+ subject="comment 3"
+ date="2022-04-25T17:12:19Z"
+ content="""
+`git annex sync/copy/move` also works in bare repos. But you'll definitely need to add the archive drives as remotes of each other.
+"""]]

diff --git a/doc/bugs/No_viable_build_plan_for_ghc_9.2.2.mdwn b/doc/bugs/No_viable_build_plan_for_ghc_9.2.2.mdwn
new file mode 100644
index 0000000000..a0c5d5d076
--- /dev/null
+++ b/doc/bugs/No_viable_build_plan_for_ghc_9.2.2.mdwn
@@ -0,0 +1,84 @@
+### Please describe the problem.
+
+Some dependencies of `git-annex` don't have viable releases for ghc-9.2.2 (aws, bloomfilter).
+
+```
+cabal v2-build
+Resolving dependencies...
+cabal: Could not resolve dependencies:
+[__0] trying: bloomfilter-2.0.1.0 (user goal)
+[__1] trying: deepseq-1.4.6.1/installed-1.4.6.1 (dependency of bloomfilter)
+[__2] trying: bytestring-0.11.3.0/installed-0.11.3.0 (dependency of
+bloomfilter)
+[__3] trying: base-4.16.1.0/installed-4.16.1.0 (dependency of bloomfilter)
+[__4] trying: git-annex-10.20220322 (user goal)
+[__5] trying: uuid-1.3.15 (dependency of git-annex)
+[__6] trying: time-1.11.1.1/installed-1.11.1.1 (dependency of git-annex)
+[__7] trying: aws-0.22 (dependency of git-annex)
+[__8] next goal: aeson (dependency of git-annex)
+[__8] rejecting: aeson-2.0.3.0 (conflict: aws => aeson>=0.6 && <1.6)
+[__8] skipping: aeson-2.0.2.0, aeson-2.0.1.0, aeson-2.0.0.0 (has the same
+characteristics that caused the previous version to fail: excluded by
+constraint '>=0.6 && <1.6' from 'aws')
+...
+```
+
+### What steps will reproduce the problem?
+
+`cabal v2-build [--allow-newer]`
+
+### What version of git-annex are you using? On what operating system?
+
+I checked out HEAD as of `b25bfecb1`. Building it on OpenBSD with ghc-9.2.2.
+
+
+### Please provide any additional information below.
+
+Building with `--allow-newer` first points at `bloomfilter` which is a [known problem with a patch](https://github.com/bos/bloomfilter/pull/20). The next problem is with `aws` and this appears to [also be a known issue](https://github.com/aristidb/aws/issues/275) which also [has a patch](https://github.com/aristidb/aws/pull/277).
+
+### Have you had any luck using git-annex before? 
+
+Yes, very much so. It works great as an OpenBSD port when built with ghc-8.10.6.
+
+### Minor fix is required to git-annex
+
+Once I apply this little fix to `git-annex` and the two patches referenced above, the code type-checks.
+
+```patch
+diff --git a/Types/Export.hs b/Types/Export.hs
+index a5cb173e5..2d5419b91 100644
+--- a/Types/Export.hs
++++ b/Types/Export.hs
+@@ -21,7 +21,7 @@ import Git.FilePath
+ import Utility.Split
+ import Utility.FileSystemEncoding
+ 
+-import Data.ByteString.Short as S
++import qualified Data.ByteString.Short as S
+ import qualified System.FilePath.Posix as Posix
+ import GHC.Generics
+ import Control.DeepSeq
+```
+
+I also see all but one tests pass, but I'm pretty sure the same test used to fail with the older compiler:
+
+```
+Tests
+  Tasty
+    tasty self-check:                      OK
+      +++ OK, passed 1 test.
+  Repo Tests v8 locked
+    Init Tests
+      init:                                OK (0.77s)
+      add:                                 OK (2.71s)
+    rsync remote:                          FAIL (3.58s)
+      ./Test/Framework.hs:328:
+      able to modify annexed file's foo content
+      Use -p '/rsync remote/' to rerun this test only.
+    conflict resolution (adjusted branch): OK (16.26s)
+    conversion annexed to git:             OK (3.32s)
+    move (ssh remote):                     OK (6.11s)
+    export_import:                         OK (13.23s)
+
+1 out of 8 tests failed (46.00s)
+```

Added a comment: Duplicate Files
diff --git a/doc/forum/Assistant_invariants/comment_2_7d3990582435203e0f94a8a3a2f1ef5a._comment b/doc/forum/Assistant_invariants/comment_2_7d3990582435203e0f94a8a3a2f1ef5a._comment
new file mode 100644
index 0000000000..66791225ee
--- /dev/null
+++ b/doc/forum/Assistant_invariants/comment_2_7d3990582435203e0f94a8a3a2f1ef5a._comment
@@ -0,0 +1,13 @@
+[[!comment format=mdwn
+ username="Albert"
+ avatar="http://cdn.libravatar.org/avatar/79aaf48dabedd7cd96a9117685fbe2cc"
+ subject="Duplicate Files"
+ date="2022-04-24T14:13:46Z"
+ content="""
+Ok, so I think I understand now where all of my problems come from.
+
+On adding new content in `Desktop`, if some files are duplicates of already available content on `Laptop`, those will not be symlinks on `Laptop`.
+When I dropped files from `Laptop`, the duplicate files in other folders, which I did not intend to drop, got also removed. I re-added them, and observed that the files I dropped came back again. Obviously, because they are the same duplicate files.
+
+Very confusing behavior, at least until I understood how duplicate files are handled and the interaction with git annex drop/get + manual mode. Is there maybe some command that drops files from a folder, that **only exist in that folder**?
+"""]]

Added a comment: Fixed
diff --git a/doc/bugs/build_for_OpenBSD_is_outdated/comment_2_cb4f3d710b0c46b27bebed48bd2e9a21._comment b/doc/bugs/build_for_OpenBSD_is_outdated/comment_2_cb4f3d710b0c46b27bebed48bd2e9a21._comment
new file mode 100644
index 0000000000..6a510d77a2
--- /dev/null
+++ b/doc/bugs/build_for_OpenBSD_is_outdated/comment_2_cb4f3d710b0c46b27bebed48bd2e9a21._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="gnezdo"
+ avatar="http://cdn.libravatar.org/avatar/4176f99bc203b65ebe09a539a3b9c6bb"
+ subject="Fixed"
+ date="2022-04-23T21:42:42Z"
+ content="""
+This has been [fixed in -current on Apr 13](https://github.com/openbsd/ports/commit/a9f826b37e077cd0004f4ef4e7e0ddaa18ffdc4a).
+"""]]

Added a comment: Clarification Example
diff --git a/doc/forum/Assistant_invariants/comment_1_0426e10ffe45f42658d89756ea5ef5be._comment b/doc/forum/Assistant_invariants/comment_1_0426e10ffe45f42658d89756ea5ef5be._comment
new file mode 100644
index 0000000000..b73720e06d
--- /dev/null
+++ b/doc/forum/Assistant_invariants/comment_1_0426e10ffe45f42658d89756ea5ef5be._comment
@@ -0,0 +1,12 @@
+[[!comment format=mdwn
+ username="Albert"
+ avatar="http://cdn.libravatar.org/avatar/79aaf48dabedd7cd96a9117685fbe2cc"
+ subject="Clarification Example"
+ date="2022-04-23T12:10:36Z"
+ content="""
+Small example for
+
+> After adding files to Laptop, content would be synced to Desktop. The added content on Laptop would never be replaced with symlinks, because there is nothing to sync. The latest version is already there.
+
+I added a `script.sh` file and some resources to `Laptop`. After it synced to `Desktop`, I executed `script.sh`. The content of `script.sh` did not change, but right now the `script.sh` file became a symlink. I assume this symlink will not be replaced, because `Laptop` is in manual mode. Is some metadata change of the file causing syncing again? Access time metadata or some other internal file system metadata that I don't know about? 
+"""]]

Clarification about git assistant invariants
diff --git a/doc/forum/Assistant_invariants.mdwn b/doc/forum/Assistant_invariants.mdwn
new file mode 100644
index 0000000000..dda0d1adf1
--- /dev/null
+++ b/doc/forum/Assistant_invariants.mdwn
@@ -0,0 +1,20 @@
+I think I might have a wrong mental model about what the annex assistant is really doing and I want to clarify some things.
+
+I have a simple setup, everything was set up with the web app.
+
+* `Desktop`: repository mode, paired locally with Laptop, paired with web server remote `Server`
+* `Laptop`: manual mode, paired locally with Desktop, paired with web server remote `Server`
+* `Server`: transfer client
+
+The intention is that `Desktop` always holds all the files, `Laptop` has some files on-demand, and `Server` is only used for transfer.
+
+After the initial setup configuration, I restarted and used the `git annex assistant` on both `Desktop` and `Laptop` (instead of `git annex webapp`).
+Here are the things that I assumed would happen in this setup.
+
+* There won't be any traffic to the `Server` as long as `Desktop` and `Laptop` are reachable, i.e. in the same network.
+  * I first started pairing `Desktop` and `Laptop` locally. With both in sync, I added the `Server` remote to `Laptop` first, and then I already saw files transferred to `Server`, which I did not expect. 
+* After adding files to `Desktop`, eventually, all metadata would appear on `Laptop`, but never the content as long as I don't manually add it via `git annex add`
+* After adding files to `Laptop`, content would be synced to `Desktop`. The added content on `Laptop` would never be replaced with symlinks, because there is nothing to sync. The latest version is already there.
+* After dropping files from `Laptop`, the content would stay gone until I re-add it.
+
+All my assumptions are immediately violated, so I assume I got them wrong. Is my assistant setup correct for the things that I intend to do?

Added a comment
diff --git a/doc/forum/Rebuild__47__populate_archive_bypassing___34__here__34___repo/comment_2_aec1d16cb47b59dd4f2811b2956e5fdf._comment b/doc/forum/Rebuild__47__populate_archive_bypassing___34__here__34___repo/comment_2_aec1d16cb47b59dd4f2811b2956e5fdf._comment
new file mode 100644
index 0000000000..5d0f76da8a
--- /dev/null
+++ b/doc/forum/Rebuild__47__populate_archive_bypassing___34__here__34___repo/comment_2_aec1d16cb47b59dd4f2811b2956e5fdf._comment
@@ -0,0 +1,13 @@
+[[!comment format=mdwn
+ username="tanakaoji@cd5ba49c38f0a3b42b7e7bf56c4379d9024cd840"
+ nickname="tanakaoji"
+ avatar="http://cdn.libravatar.org/avatar/ccc61e285ed2498e328a131b13cfe712"
+ subject="comment 2"
+ date="2022-04-22T20:39:30Z"
+ content="""
+Ah, thank you for the link, I did skim the forum, but did not come across it.
+
+I don't replace drives daily, so it's not that big of a problem, just a nice to have.
+Currently I'm managing my data archive/backups manually, so it will already be a lot more efficient :-)
+
+"""]]

Added a comment: transitive transfers
diff --git a/doc/forum/Rebuild__47__populate_archive_bypassing___34__here__34___repo/comment_1_901272a6f738bfe8a0289c4900fd0c20._comment b/doc/forum/Rebuild__47__populate_archive_bypassing___34__here__34___repo/comment_1_901272a6f738bfe8a0289c4900fd0c20._comment
new file mode 100644
index 0000000000..03582311a5
--- /dev/null
+++ b/doc/forum/Rebuild__47__populate_archive_bypassing___34__here__34___repo/comment_1_901272a6f738bfe8a0289c4900fd0c20._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="Ilya_Shlyakhter"
+ avatar="http://cdn.libravatar.org/avatar/1647044369aa7747829c38b9dcc84df0"
+ subject="transitive transfers"
+ date="2022-04-22T17:01:13Z"
+ content="""
+Past discussions of this: [[todo/transitive_transfers]].
+"""]]

Added a comment: transitive transfers
diff --git a/doc/todo/transitive_transfers/comment_8_7ffe073c582fc9392213ce52ba6ba398._comment b/doc/todo/transitive_transfers/comment_8_7ffe073c582fc9392213ce52ba6ba398._comment
new file mode 100644
index 0000000000..2b1b6830d3
--- /dev/null
+++ b/doc/todo/transitive_transfers/comment_8_7ffe073c582fc9392213ce52ba6ba398._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="Ilya_Shlyakhter"
+ avatar="http://cdn.libravatar.org/avatar/1647044369aa7747829c38b9dcc84df0"
+ subject="transitive transfers"
+ date="2022-04-22T16:59:59Z"
+ content="""
+One more use case [here](https://git-annex.branchable.com/forum/Rebuild__47__populate_archive_bypassing___34__here__34___repo/).
+"""]]

diff --git a/doc/forum/Rebuild__47__populate_archive_bypassing___34__here__34___repo.mdwn b/doc/forum/Rebuild__47__populate_archive_bypassing___34__here__34___repo.mdwn
new file mode 100644
index 0000000000..990f939cbe
--- /dev/null
+++ b/doc/forum/Rebuild__47__populate_archive_bypassing___34__here__34___repo.mdwn
@@ -0,0 +1,46 @@
+Hello,
+
+I am currently testing out git-annex (git-annex version: 10.20220223-ge37f0f227)*. I like it a lot, but have one question,
+(I'll try to keep it succinct)
+
+Sample setup:
+
+- main repo (untrusted, manual group) --> Small drive, contains only currently needed files.
+- multiple remote archive repo's (trusted, archive group) --> (two for this test)
+- numcopies=2
+
+So far works as expected (two copies created in archives)
+
+Situation: I replace a couple of the oldest archive drives each year. (or one may fail)
+
+- drive to remove is marked dead + unwanted (as per the tips/help)
+- new drive is added (clone repo etc.)
+- fsck correctly identifies files as missing a copy, files locally available are copied to the new archive, other files are however not copied from another archive drive (which is connected and availabe).
+
+  doing:
+
+	- git annex copy --auto --from=data2
+	- git annex copy --auto --to=data4	// (this part is prolly not needed with assistant)
+
+  does work, but does an unnecessary copy to the main repo first, also note that in the real situation this drive won't be large enough to keep all the files, so would need a lot of to-and-fro'ing (and also needs cleanup afterwards).
+  
+I solved it** (for now) by converting the archives from raw to full repo's + adding the other archive to its remotes so a direct copy can be done (ie. git annex copy --auto / sync --content directly from the archive drive).
+- tedious but doable.
+
+
+Maybe I'm missing something? or I am doing it wrong? :-)
+If not: it would be nice if this worked irrespective of files being available locally, or to be able to do:
+	
+- git annex copy --auto --from=data2 --to=data4
+
+this way the separate archives do not need to be converted, and everything can be done from the central repo.
+(I prefer the archives being raw repo's, they shouldn't be messed with)
+
+Suggestions?
+
+Thanks
+KachoOji
+
+
+"* upgraded from v8 from debian repo
+ ** You could also clone the drive (dd), but that won't work if it failed.

Added a comment
diff --git a/doc/tips/largefiles/comment_15_d1f24df21ac577b89a8ae385532de6f2._comment b/doc/tips/largefiles/comment_15_d1f24df21ac577b89a8ae385532de6f2._comment
new file mode 100644
index 0000000000..f5c2110e06
--- /dev/null
+++ b/doc/tips/largefiles/comment_15_d1f24df21ac577b89a8ae385532de6f2._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="lh"
+ avatar="http://cdn.libravatar.org/avatar/d31bade008d7da493d9bfb37d68fd825"
+ subject="comment 15"
+ date="2022-04-21T01:18:21Z"
+ content="""
+@joey Thanks. I don't think 2 is even possible since I just installed git-annex. 1 seems likely. I'm guessing that could occur if I previously ran `git annex add` with a different `annex.largefiles` that caused some files to be added (though not committed) via git rather than git-annex? That's roughly the scenario I was in.
+"""]]

Added a comment: I had to come out of the rabbit hole
diff --git a/doc/forum/Making_git-annex_preserve_file_name__44___and_directory/comment_3_bbd3ea45cf560031856e9e8416b4d126._comment b/doc/forum/Making_git-annex_preserve_file_name__44___and_directory/comment_3_bbd3ea45cf560031856e9e8416b4d126._comment
new file mode 100644
index 0000000000..e73f0033e8
--- /dev/null
+++ b/doc/forum/Making_git-annex_preserve_file_name__44___and_directory/comment_3_bbd3ea45cf560031856e9e8416b4d126._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="datamanager"
+ avatar="http://cdn.libravatar.org/avatar/7d4ca7c5e571d4740ef072b83a746c12"
+ subject="I had to come out of the rabbit hole"
+ date="2022-04-20T15:28:13Z"
+ content="""
+After diving that rabbit hole, I got stuck: it looks like the project's application is not yet verified, and I can't use my own application because google doesn't like it's redirect URI. That's a bummer. 
+"""]]

diff --git a/doc/bugs/dropunused_returns_ok_but_doesn__39__t_delete_files.mdwn b/doc/bugs/dropunused_returns_ok_but_doesn__39__t_delete_files.mdwn
new file mode 100644
index 0000000000..204b6b7ad2
--- /dev/null
+++ b/doc/bugs/dropunused_returns_ok_but_doesn__39__t_delete_files.mdwn
@@ -0,0 +1,54 @@
+### Please describe the problem.
+I deleted a lot of files with "git rm" and now have the problem that I cannot delete the file-contents with annex dropunused.
+"git annex unused" shows correctly the files, then I delete them with "git annex dropunsed 1-1000" and the command
+returns Sucess/OK. When I go back to "git annex unused" all files are still there, altough they should have been deleted. 
+
+
+### What version of git-annex are you using? On what operating system?
+Debian 11 Bullseye; git-annex version: 10.20220322-1~ndall+1 
+
+### Please provide any additional information below.
+
+[[!format sh """
+# git annex unused
+unused . (checking for unused data...) (checking master...)
+  Some annexed data is no longer used by any files:
+    NUMBER  KEY
+    1       SHA256E-s38888--42db1349c507d96722d2a506921dbc1a90a0a278828db732c9f1971eb9cfb70c.pyc
+    2       SHA256E-s22918--093c4494596c7441d2cc6ee90efdd98463e7b830fa5030b2a081b05b559db3bc.pyc
+    3       SHA256E-s12896--8c2c167031e59888aea1af627705d4005b41a80b433bce4f1e751f9a6e5dc149.pyc
+    4       SHA256E-s63363--dbb7bdd1512300464f0116a79ab970f26f82bb6dd71236c8ab3d58bb591a473f.pyc
+    ....
+
+  (To see where this data was previously used, run: git annex whereused --historical --unused
+
+  To remove unwanted data: git-annex dropunused NUMBER
+
+ok
+
+#git annex dropunused 1-1600 --force
+dropunused 1 ok
+dropunused 2 ok
+dropunused 3 ok
+dropunused 4 ok
+...
+
+# git annex unused
+unused . (checking for unused data...) (checking master...)
+  Some annexed data is no longer used by any files:
+    NUMBER  KEY
+    1       SHA256E-s38888--42db1349c507d96722d2a506921dbc1a90a0a278828db732c9f1971eb9cfb70c.pyc
+    2       SHA256E-s22918--093c4494596c7441d2cc6ee90efdd98463e7b830fa5030b2a081b05b559db3bc.pyc
+    3       SHA256E-s12896--8c2c167031e59888aea1af627705d4005b41a80b433bce4f1e751f9a6e5dc149.pyc
+    4       SHA256E-s63363--dbb7bdd1512300464f0116a79ab970f26f82bb6dd71236c8ab3d58bb591a473f.pyc
+    ....
+
+  (To see where this data was previously used, run: git annex whereused --historical --unused
+
+  To remove unwanted data: git-annex dropunused NUMBER
+
+ok
+
+"""]]
+
+

Added a comment: re: See git-annex-export.
diff --git a/doc/forum/Making_git-annex_preserve_file_name__44___and_directory/comment_2_64863e149cc62a778b006ffe2bf82d06._comment b/doc/forum/Making_git-annex_preserve_file_name__44___and_directory/comment_2_64863e149cc62a778b006ffe2bf82d06._comment
new file mode 100644
index 0000000000..1e568576b2
--- /dev/null
+++ b/doc/forum/Making_git-annex_preserve_file_name__44___and_directory/comment_2_64863e149cc62a778b006ffe2bf82d06._comment
@@ -0,0 +1,10 @@
+[[!comment format=mdwn
+ username="datamanager"
+ avatar="http://cdn.libravatar.org/avatar/7d4ca7c5e571d4740ef072b83a746c12"
+ subject="re: See git-annex-export. "
+ date="2022-04-20T14:47:59Z"
+ content="""
+Thank you! 
+
+I'm assuming that since [rclone-special remotes aren't explicitly stated to support exporttree](https://git-annex.branchable.com/tips/using_Google_Drive/) that I should use [git-annex-remote-googledrive](https://github.com/Lykos153/git-annex-remote-googledrive) instead, correct? 
+"""]]

Added a comment: re: Making git-annex preserve file name, and directory
diff --git a/doc/forum/Making_git-annex_preserve_file_name__44___and_directory/comment_1_03a5369518a96c0550e68af520ed0060._comment b/doc/forum/Making_git-annex_preserve_file_name__44___and_directory/comment_1_03a5369518a96c0550e68af520ed0060._comment
new file mode 100644
index 0000000000..a60428d6e6
--- /dev/null
+++ b/doc/forum/Making_git-annex_preserve_file_name__44___and_directory/comment_1_03a5369518a96c0550e68af520ed0060._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="Ilya_Shlyakhter"
+ avatar="http://cdn.libravatar.org/avatar/1647044369aa7747829c38b9dcc84df0"
+ subject="re: Making git-annex preserve file name, and directory"
+ date="2022-04-20T04:03:46Z"
+ content="""
+See [[`git-annex-export`|git-annex-export]].
+"""]]

I want to bend git-annex to my will, but I don't know how
diff --git a/doc/forum/Making_git-annex_preserve_file_name__44___and_directory.mdwn b/doc/forum/Making_git-annex_preserve_file_name__44___and_directory.mdwn
new file mode 100644
index 0000000000..045a5b66bf
--- /dev/null
+++ b/doc/forum/Making_git-annex_preserve_file_name__44___and_directory.mdwn
@@ -0,0 +1,10 @@
+Hello,
+I have recordings of skype meetings with my teammates, all stored in google-drive. I can link to them just fine, but I thought it might be interesting to have git-annex manage them. 
+
+The problem is that I have to share these recordings with team mates. I therefore can't send them the hashed, and encrypted file. 
+
+For example, if I have a file at `attachments/video.mkv`, I want to be able to store it on google-drive at `annexed-attachments/video.mkv`. I thought that [backends](https://git-annex.branchable.com/backends/) might hold the answer, but I don't think so anymore.
+
+Even a point in the right direction would be appreciated. 
+
+

diff --git a/doc/bugs/git_error___34__update__95__ref_failed_for_ref__34__.mdwn b/doc/bugs/git_error___34__update__95__ref_failed_for_ref__34__.mdwn
new file mode 100644
index 0000000000..dc2ad5a3fe
--- /dev/null
+++ b/doc/bugs/git_error___34__update__95__ref_failed_for_ref__34__.mdwn
@@ -0,0 +1,37 @@
+Notice that the problem is due to the fact that the remote name has spaces. I have only tested it with adb special remote, but since it is a `git` error It will likely happen with any remote.
+
+Sorry I you don't consider this a bug or if it is documented somewhere and I haven't read it.
+
+### Please describe the problem.
+following [Android sync tip](https://git-annex.branchable.com/tips/android_sync_with_adb/), It fails with 
+
+```
+update refs/remotes/Redmi Note 8/master fatal: update_ref failed for ref'refs/remotes/Redmi Note 8/master': refuse to update ref with bad name 'refs/remotes/Redmi Note 8/master'
+
+git-annex: git [Param "update-ref",Param "refs/remotes/Redmi Note 8/master",Param "389056e22b21972da562d1f1c5ce7af2447523b7"] failed
+CallStack (from HasCallStack):
+  error, called at ./Git/Command.hs:42:17 in main:Git.Command
+failed
+```
+
+The error happens after executing `git annex sync --content "Redmi Note 8"` and pulling all files. 
+
+### What steps will reproduce the problem?
+Once you have conected to an adb special remote (probably any remote can trigger the error) run the following
+
+```bash
+#                        |- Notice the space
+git-annex initremote "Bad Remote" type=adb androiddirectory=/sdcard encryption=none exporttree=yes importtree=yes
+git config "remote.Bad Remote.annex-tracking-branch" master:android
+git annex sync --content "Bad Remote"
+```
+
+It will download the content and fail afterwards
+
+### What version of git-annex are you using? On what operating system?
+- git-annex version 8.20200226
+- OS: Ubuntu 20.04.3 LTS
+
+### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)
+I am not an experienced user but after using non-space-remotes-names I've been able to sync my phone with no major problems following the documentation in this page. So great work!
+

Added a comment
diff --git a/doc/forum/Issues_with_non-sync_workflow/comment_3_db02b002a977b57be0102d8ac7c2037f._comment b/doc/forum/Issues_with_non-sync_workflow/comment_3_db02b002a977b57be0102d8ac7c2037f._comment
new file mode 100644
index 0000000000..3b553bcaed
--- /dev/null
+++ b/doc/forum/Issues_with_non-sync_workflow/comment_3_db02b002a977b57be0102d8ac7c2037f._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="lh"
+ avatar="http://cdn.libravatar.org/avatar/d31bade008d7da493d9bfb37d68fd825"
+ subject="comment 3"
+ date="2022-04-19T18:40:57Z"
+ content="""
+If I had set annex.alwayscommit=false, would that make resetting the worktree potentially dangerous if I hadn't merged yet?
+"""]]

Added a comment
diff --git a/doc/forum/Issues_with_non-sync_workflow/comment_2_f27ec2462e5a8f6f96c6e2b7dd3f87ca._comment b/doc/forum/Issues_with_non-sync_workflow/comment_2_f27ec2462e5a8f6f96c6e2b7dd3f87ca._comment
new file mode 100644
index 0000000000..9b4b04a62e
--- /dev/null
+++ b/doc/forum/Issues_with_non-sync_workflow/comment_2_f27ec2462e5a8f6f96c6e2b7dd3f87ca._comment
@@ -0,0 +1,10 @@
+[[!comment format=mdwn
+ username="lh"
+ avatar="http://cdn.libravatar.org/avatar/d31bade008d7da493d9bfb37d68fd825"
+ subject="comment 2"
+ date="2022-04-19T18:15:53Z"
+ content="""
+But the worktree won't confuse git-annex in any way, right?
+
+If that's the case, I guess I'll just remember to hard reset the worktree every time I want to work with it. As long as git-annex doesn't ever leave things uncommitted on its branch, that should be fine.
+"""]]

added bug report for git-annex-import importing too much
diff --git a/doc/bugs/git-annex-import_imports_outside_of_directory.mdwn b/doc/bugs/git-annex-import_imports_outside_of_directory.mdwn
new file mode 100644
index 0000000000..d2d18493ae
--- /dev/null
+++ b/doc/bugs/git-annex-import_imports_outside_of_directory.mdwn
@@ -0,0 +1,27 @@
+### Please describe the problem.
+When using `git-annex-import` to import from a directory remote, if the directory tree has directory symlinks pointing to directories outside the directory tree of the directory remote, the targets of these symlinks get imported.  This can lead to importing much more than was intended; such symlinks should probably get imported as symlinks by default, with a command-line option to import their targets.   There might even be a security issue with unexpectedly importing and sharing content outside the explicitly specified directory tree.
+
+
+### What version of git-annex are you using? On what operating system?
+
+[[!format sh """
+# If you can, paste a complete transcript of the problem occurring here.
+# If the problem is with the git-annex assistant, paste in .git/annex/daemon.log
+$ git annex version
+git-annex version: 10.20220322-g7b64dea
+build flags: Assistant Webapp Pairing Inotify DBus DesktopNotify TorrentParser MagicMime Feeds Testsuite S3 WebDAV
+dependency versions: aws-0.22 bloomfilter-2.0.1.0 cryptonite-0.29 DAV-1.3.4 feed-1.3.2.0 ghc-8.10.7 http-client-0.7.9 persistent-sqlite-2.13.0.3 torrent-10000.1.1 uuid-1.3.15 yesod-1.6.1.2
+key/value backends: SHA256E SHA256 SHA512E SHA512 SHA224E SHA224 SHA384E SHA384 SHA3_256E SHA3_256 SHA3_512E SHA3_512 SHA3_224E SHA3_224 SHA3_384E SHA3_384 SKEIN256E SKEIN256 SKEIN512E SKEIN512 BLAKE2B256E BLAKE2B256 BLAKE2B512E BLAKE2B512 BLAKE2B160E BLAKE2B160 BLAKE2B224E BLAKE2B224 BLAKE2B384E BLAKE2B384 BLAKE2BP512E BLAKE2BP512 BLAKE2S256E BLAKE2S256 BLAKE2S160E BLAKE2S160 BLAKE2S224E BLAKE2S224 BLAKE2SP256E BLAKE2SP256 BLAKE2SP224E BLAKE2SP224 SHA1E SHA1 MD5E MD5 WORM URL X*
+remote types: git gcrypt p2p S3 bup directory rsync web bittorrent webdav adb tahoe glacier ddar git-lfs httpalso borg hook external
+operating system: linux x86_64
+supported repository versions: 8 9 10
+upgrade supported from repository versions: 0 1 2 3 4 5 6 7 8 9 10
+local repository version: 8
+$ uname -a
+Linux ctchpcpx163.merck.com 3.10.0-1160.6.1.el7.x86_64 #1 SMP Tue Nov 17 13:59:11 UTC 2020 x86_64 GNU/Linux
+# End of transcript or log.
+"""]]
+
+### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)
+
+That I use it enough to run into corner-case issues shows its continued usefulness :)

comment
diff --git a/doc/todo/add_maxextensionlength_to_git-annex-config/comment_9_5027255cc1b0dec41e315df8472fe60e._comment b/doc/todo/add_maxextensionlength_to_git-annex-config/comment_9_5027255cc1b0dec41e315df8472fe60e._comment
new file mode 100644
index 0000000000..541fbd34d7
--- /dev/null
+++ b/doc/todo/add_maxextensionlength_to_git-annex-config/comment_9_5027255cc1b0dec41e315df8472fe60e._comment
@@ -0,0 +1,29 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 9"""
+ date="2022-04-19T16:42:16Z"
+ content="""
+Well, maxextensionlength does at least have some connection to the
+filename, which does point at it being somewhat suitable for
+gitattributes. Eg, you might want to allow files with one long extension
+to include it all, but not others.
+
+The current implementation uses a single git check-attr process which
+reports on all 4 currently supported git-annex attributes for every query.
+So adding a new attribute also slows down queries for all the rest by some
+amount. To quantify that, I did a benchmark, modifiying git-annex to 
+request (but ignore) an additional attribute. git-annex add of 10000
+small files did not slow down by any measurable amount due to that change.
+(1:53.78 before the change and 1:52.00 afterwards actually, so the
+difference is lost in the noise)
+
+An attribute does impose more overhead than git-annex
+config though, since it has to be queried for every file rather than one
+time. I benchmarked the overhead of checking annex.maxextensionlength
+once per file added, over 10000 files. It slowed down by around 5%.
+
+git-annex add already queries the annex.largefiles attribute once per file.
+So, if it could also query annex.maxextensionlength at the same time, 
+it would be free to support the attribute. And then I'd certianly be in
+favor of it. This would need some nontrivial refactoring of the code.
+"""]]

Fix test failure on NFS when cleaning up gpg temp directory
Using removePathForcibly avoids concurrent removal problems.
The i386ancient build still uses an old version of ghc and directory that
do not include removePathForcibly though.
Sponsored-by: Dartmouth College's Datalad project
diff --git a/CHANGELOG b/CHANGELOG
index 159b32a835..5c1ae06361 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -8,6 +8,7 @@ git-annex (10.20220323) UNRELEASED; urgency=medium
     the user makes manually, and push them out to remotes promptly.
   * multicast: Support uftp 5.0 by switching from aes256-cbc to
     aes256-gcm.
+  * Fix test failure on NFS when cleaning up gpg temp directory.
 
  -- Joey Hess <id@joeyh.name>  Mon, 28 Mar 2022 14:46:10 -0400
 
diff --git a/Test/Framework.hs b/Test/Framework.hs
index 3927a55039..25be216408 100644
--- a/Test/Framework.hs
+++ b/Test/Framework.hs
@@ -264,7 +264,7 @@ isolateGitConfig a = Utility.Tmp.Dir.withTmpDir "testhome" $ \tmphome -> do
 
 removeDirectoryForCleanup :: FilePath -> IO ()
 #if MIN_VERSION_directory(1,2,7)
- removeDirectoryForCleanup = removePathForcibly
+removeDirectoryForCleanup = removePathForcibly
 #else
 removeDirectoryForCleanup = removeDirectoryRecursive
 #endif
diff --git a/Utility/Tmp/Dir.hs b/Utility/Tmp/Dir.hs
index c68ef86571..4deda1297e 100644
--- a/Utility/Tmp/Dir.hs
+++ b/Utility/Tmp/Dir.hs
@@ -1,6 +1,6 @@
 {- Temporary directories
  -
- - Copyright 2010-2013 Joey Hess <id@joeyh.name>
+ - Copyright 2010-2022 Joey Hess <id@joeyh.name>
  -
  - License: BSD-2-clause
  -}
@@ -63,8 +63,17 @@ removeTmpDir tmpdir = liftIO $ whenM (doesDirectoryExist tmpdir) $ do
 	-- after a process has just written to it and exited.
 	-- Because it's crap, presumably. So, ignore failure
 	-- to delete the temp directory.
-	_ <- tryIO $ removeDirectoryRecursive tmpdir
+	_ <- tryIO $ go tmpdir
 	return ()
 #else
-	removeDirectoryRecursive tmpdir
+	go tmpdir
+#endif
+  where
+  	-- Use removePathForcibly when available, to avoid crashing
+	-- if some other process is removing files in the directory at the
+	-- same time.
+#if MIN_VERSION_directory(1,2,7)
+	go = removePathForcibly
+#else
+	go = removeDirectoryRecursive
 #endif
diff --git a/doc/bugs/sporadic___40____63____41___fail_of_crypto_test__63__/comment_2_a254c4de61a74bf314eb33e301c199f0._comment b/doc/bugs/sporadic___40____63____41___fail_of_crypto_test__63__/comment_2_a254c4de61a74bf314eb33e301c199f0._comment
new file mode 100644
index 0000000000..547388a6e3
--- /dev/null
+++ b/doc/bugs/sporadic___40____63____41___fail_of_crypto_test__63__/comment_2_a254c4de61a74bf314eb33e301c199f0._comment
@@ -0,0 +1,26 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 2"""
+ date="2022-04-19T16:49:26Z"
+ content="""
+That is a *very* strange message! Because the list is all
+ExitSuccess, but before the code to check that message can run,
+it checks that the list is not `all (== ExitSuccess)`
+
+It should not be possible for a list without an ExitFailure in it to
+get past that check. And it certainly does not normally.
+
+And looking at the log, one of the tests *did* fail:
+
+	    crypto:                                   FAIL
+	      Exception: /tmp/gpgtmpSgOEyq/2/S.gpg-agent.ssh: removeDirectoryRecursive:removeContentsRecursive:removePathRecursive:removeContentsRecursive:removePathRecursive:getSymbolicLinkStatus: does not exist (No such file or directory)
+
+So there must have been an `ExitFailure 1` in the list, even though it
+somehow ends up displaying as containing all `ExitSuccess`. Almost as if
+the content of the list changed. But it cannot, barring a bug in the
+haskell runtime..
+
+As far as the cause of that failure, it should be fixable by using 
+removePathForcibly rather than removeDirectoryRecursive in removeTmpDir.
+I've made that change.
+"""]]

remove moreinfo
diff --git a/doc/todo/add_xxHash_backend.mdwn b/doc/todo/add_xxHash_backend.mdwn
index 8433047735..cf9143ea09 100644
--- a/doc/todo/add_xxHash_backend.mdwn
+++ b/doc/todo/add_xxHash_backend.mdwn
@@ -1,3 +1 @@
 From https://cyan4973.github.io/xxHash/ , xxHash seems much faster than md5 with comparable quality.  There's a Haskell implementation.
-
-[[!tag moreinfo]]

comment
diff --git a/doc/forum/Issues_with_non-sync_workflow/comment_1_99f89201446005f77d9278a7a889638d._comment b/doc/forum/Issues_with_non-sync_workflow/comment_1_99f89201446005f77d9278a7a889638d._comment
new file mode 100644
index 0000000000..9265f1caaa
--- /dev/null
+++ b/doc/forum/Issues_with_non-sync_workflow/comment_1_99f89201446005f77d9278a7a889638d._comment
@@ -0,0 +1,10 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2022-04-19T16:28:06Z"
+ content="""
+I don't think anything git-annex can do would make git worktree behave the
+way you want it to behave, except for a whole separate mode of operation
+that writes to the git-annex branch via writing to the worktree for it and
+committing.
+"""]]

comment
diff --git a/doc/backends/comment_34_3782e7561607c8ccf1741bd039c2c648._comment b/doc/backends/comment_34_3782e7561607c8ccf1741bd039c2c648._comment
new file mode 100644
index 0000000000..4a80db0d52
--- /dev/null
+++ b/doc/backends/comment_34_3782e7561607c8ccf1741bd039c2c648._comment
@@ -0,0 +1,7 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 34"""
+ date="2022-04-19T16:17:30Z"
+ content="""
+[[todo/add_xxHash_backend]] is tracking requirements for adding xxh3.
+"""]]

remove
diff --git a/doc/git-annex-lock/comment_1_b3afe023199622ab5a9105e0197d94b0._comment b/doc/git-annex-lock/comment_1_b3afe023199622ab5a9105e0197d94b0._comment
deleted file mode 100644
index 8c2a83e78b..0000000000
--- a/doc/git-annex-lock/comment_1_b3afe023199622ab5a9105e0197d94b0._comment
+++ /dev/null
@@ -1,8 +0,0 @@
-[[!comment format=mdwn
- username="Abdulbasi"
- avatar="http://cdn.libravatar.org/avatar/bf0699ce1dd11962016577cf72b64ce0"
- subject="Myself"
- date="2022-04-14T00:56:59Z"
- content="""
-To 
-"""]]

comment
diff --git a/doc/git-annex-add/comment_7_bb9d72f90f4d3c93d71de1a1bbc244b6._comment b/doc/git-annex-add/comment_7_bb9d72f90f4d3c93d71de1a1bbc244b6._comment
new file mode 100644
index 0000000000..166bd2e14d
--- /dev/null
+++ b/doc/git-annex-add/comment_7_bb9d72f90f4d3c93d71de1a1bbc244b6._comment
@@ -0,0 +1,14 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 7"""
+ date="2022-04-19T16:09:12Z"
+ content="""
+@lh I don't feel this man page's comment section is the place to discuss
+this in detail. But suffice to say that even if add behaved the way you
+suggest, any other git-annex command can commit the accumulated changes
+to the git-annex branch at any time. So the change would not guarantee
+the behavior you hope for.
+
+But, you can set annex.alwayscommit to false and run `git-annex merge`
+when you do want to commit changes to the git-annex branch.
+"""]]

comment
diff --git a/doc/tips/largefiles/comment_14_cd71d1c6e69a9fc7feadf9aa6a39c406._comment b/doc/tips/largefiles/comment_14_cd71d1c6e69a9fc7feadf9aa6a39c406._comment
new file mode 100644
index 0000000000..b40aff5619
--- /dev/null
+++ b/doc/tips/largefiles/comment_14_cd71d1c6e69a9fc7feadf9aa6a39c406._comment
@@ -0,0 +1,14 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 14"""
+ date="2022-04-19T16:03:49Z"
+ content="""
+@lh two possibilities are:
+
+1. When `git add` is run, git-annex does some additional checks to detect
+   things like a file that was checked into git before but has been
+   renamed, and will avoid annexing such a file even when annex.largefiles
+   says to.
+2. Perhaps `git add` is finding some other git-annex
+   version that behaves differently. Seems unlikely, but possible.
+"""]]