diff --git a/Annex/AdjustedBranch.hs b/Annex/AdjustedBranch.hs
--- a/Annex/AdjustedBranch.hs
+++ b/Annex/AdjustedBranch.hs
@@ -362,9 +362,9 @@
 commitForAdjustedBranch :: [CommandParam] -> Annex ()
 commitForAdjustedBranch ps = do
 	cmode <- annexCommitMode <$> Annex.getGitConfig
-	void $ inRepo $ Git.Branch.commitCommand cmode $
-		[ Param "--quiet"
-		, Param "--allow-empty"
+	let cquiet = Git.Branch.CommitQuiet True
+	void $ inRepo $ Git.Branch.commitCommand cmode cquiet $
+		[ Param "--allow-empty"
 		, Param "-m"
 		, Param "commit before entering adjusted branch"
 		] ++ ps
diff --git a/Annex/AutoMerge.hs b/Annex/AutoMerge.hs
--- a/Annex/AutoMerge.hs
+++ b/Annex/AutoMerge.hs
@@ -352,11 +352,13 @@
 		| otherwise = go fs
 	
 commitResolvedMerge :: Git.Branch.CommitMode -> Annex Bool
-commitResolvedMerge commitmode = inRepo $ Git.Branch.commitCommand commitmode
-	[ Param "--no-verify"
-	, Param "-m"
-	, Param "git-annex automatic merge conflict fix"
-	]
+commitResolvedMerge commitmode = do
+	commitquiet <- Git.Branch.CommitQuiet <$> commandProgressDisabled
+	inRepo $ Git.Branch.commitCommand commitmode commitquiet
+		[ Param "--no-verify"
+		, Param "-m"
+		, Param "git-annex automatic merge conflict fix"
+		]
 
 type InodeMap = M.Map (Either FilePath InodeCacheKey) FilePath
 
diff --git a/Annex/Content.hs b/Annex/Content.hs
--- a/Annex/Content.hs
+++ b/Annex/Content.hs
@@ -29,6 +29,7 @@
 	populatePointerFile,
 	linkToAnnex,
 	linkFromAnnex,
+	linkFromAnnex',
 	LinkAnnexResult(..),
 	unlinkAnnex,
 	checkedCopyFile,
@@ -50,6 +51,7 @@
 	pruneTmpWorkDirBefore,
 	isUnmodified,
 	isUnmodifiedCheap,
+	verifyKeyContentPostRetrieval,
 	verifyKeyContent,
 	VerifyConfig(..),
 	Verification(..),
@@ -64,6 +66,7 @@
 import Annex.Content.Presence
 import Annex.Content.LowLevel
 import Annex.Content.PointerFile
+import Annex.Verify
 import qualified Git
 import qualified Annex
 import qualified Annex.Queue
@@ -77,6 +80,7 @@
 import Annex.LockPool
 import Annex.UUID
 import Annex.InodeSentinal
+import Annex.ReplaceFile
 import Annex.AdjustedBranch (adjustedBranchRefresh)
 import Messages.Progress
 import Types.Remote (RetrievalSecurityPolicy(..))
@@ -227,7 +231,7 @@
 			_ -> MustVerify
 		else verification
 	if ok
-		then ifM (verifyKeyContent rsp v verification' key tmpfile)
+		then ifM (verifyKeyContentPostRetrieval rsp v verification' key tmpfile)
 			( pruneTmpWorkDirBefore tmpfile (moveAnnex key af)
 			, do
 				warning "verification of content failed"
@@ -250,7 +254,7 @@
 	-- RetrievalSecurityPolicy would cause verification to always fail.
 	checkallowed a = case rsp of
 		RetrievalAllKeysSecure -> a
-		RetrievalVerifiableKeysSecure -> ifM (Backend.isVerifiable key)
+		RetrievalVerifiableKeysSecure -> ifM (isVerifiable key)
 			( a
 			, ifM (annexAllowUnverifiedDownloads <$> Annex.getGitConfig)
 				( a
@@ -345,8 +349,11 @@
 			fs <- map (`fromTopFilePath` g)
 				<$> Database.Keys.getAssociatedFiles key
 			unless (null fs) $ do
+				destic <- withTSDelta $
+					liftIO . genInodeCache dest
 				ics <- mapM (populatePointerFile (Restage True) key dest) fs
-				Database.Keys.storeInodeCaches' key [dest] (catMaybes ics)
+				Database.Keys.addInodeCaches key
+					(catMaybes (destic:ics))
 		)
 	alreadyhave = liftIO $ R.removeLink src
 
@@ -367,6 +374,7 @@
 		return False
 
 data LinkAnnexResult = LinkAnnexOk | LinkAnnexFailed | LinkAnnexNoop
+	deriving (Eq)
 
 {- Populates the annex object file by hard linking or copying a source
  - file to it. -}
@@ -378,9 +386,23 @@
 	, return LinkAnnexFailed
 	)
 
-{- Makes a destination file be a link or copy from the annex object. -}
+{- Makes a destination file be a link or copy from the annex object.
+ -
+ - linkAnnex stats the file after copying it to add to the inode
+ - cache. But dest may be a file in the working tree, which could
+ - get modified immediately after being populated. To avoid such a
+ - race, call linkAnnex on a temporary file and move it into place
+ - afterwards. Note that a consequence of this is that, if the file
+ - already exists, it will be overwritten.
+ -}
 linkFromAnnex :: Key -> RawFilePath -> Maybe FileMode -> Annex LinkAnnexResult
-linkFromAnnex key dest destmode = do
+linkFromAnnex key dest destmode =
+	replaceFile' (const noop) (fromRawFilePath dest) (== LinkAnnexOk) $ \tmp ->
+		linkFromAnnex' key (toRawFilePath tmp) destmode
+
+{- This is only safe to use when dest is not a worktree file. -}
+linkFromAnnex' :: Key -> RawFilePath -> Maybe FileMode -> Annex LinkAnnexResult
+linkFromAnnex' key dest destmode = do
 	src <- calcRepo (gitAnnexLocation key)
 	srcic <- withTSDelta (liftIO . genInodeCache src)
 	linkAnnex From key src srcic dest destmode
@@ -471,19 +493,26 @@
  -}
 prepSendAnnex :: Key -> Annex (Maybe (FilePath, Annex Bool))
 prepSendAnnex key = withObjectLoc key $ \f -> do
+	let retval c = return $ Just (fromRawFilePath f, sameInodeCache f c)
 	cache <- Database.Keys.getInodeCaches key
-	cache' <- if null cache
+	if null cache
 		-- Since no inode cache is in the database, this
 		-- object is not currently unlocked. But that could
 		-- change while the transfer is in progress, so
 		-- generate an inode cache for the starting
 		-- content.
-		then maybeToList <$>
-			withTSDelta (liftIO . genInodeCache f)
-		else pure cache
-	return $ if null cache'
-		then Nothing
-		else Just (fromRawFilePath f, sameInodeCache f cache')
+		then maybe (return Nothing) (retval . (:[]))
+			=<< withTSDelta (liftIO . genInodeCache f)
+		-- Verify that the object is not modified. Usually this
+		-- only has to check the inode cache, but if the cache
+		-- is somehow stale, it will fall back to verifying its
+		-- content.
+		else withTSDelta (liftIO . genInodeCache f) >>= \case
+			Just fc -> ifM (isUnmodified' key f fc cache)
+				( retval (fc:cache)
+				, return Nothing
+				)
+			Nothing -> return Nothing
 
 prepSendAnnex' :: Key -> Annex (Maybe (FilePath, Annex (Maybe String)))
 prepSendAnnex' key = prepSendAnnex key >>= \case
diff --git a/Annex/Content/Presence.hs b/Annex/Content/Presence.hs
--- a/Annex/Content/Presence.hs
+++ b/Annex/Content/Presence.hs
@@ -1,6 +1,6 @@
 {- git-annex object content presence
  -
- - Copyright 2010-2020 Joey Hess <id@joeyh.name>
+ - Copyright 2010-2021 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -15,28 +15,18 @@
 	objectFileExists,
 	withObjectLoc,
 	isUnmodified,
+	isUnmodified',
 	isUnmodifiedCheap,
-	verifyKeyContent,
-	VerifyConfig(..),
-	Verification(..),
-	unVerified,
-	warnUnverifiableInsecure,
 	contentLockFile,
 ) where
 
+import Annex.Content.Presence.LowLevel
 import Annex.Common
 import qualified Annex
-import Annex.Verify
 import Annex.LockPool
-import Annex.WorkerPool
-import Types.Remote (unVerified, Verification(..), RetrievalSecurityPolicy(..))
-import qualified Types.Backend
-import qualified Backend
 import qualified Database.Keys
-import Types.Key
 import Annex.InodeSentinal
 import Utility.InodeCache
-import Types.WorkerPool
 import qualified Utility.RawFilePath as R
 
 #ifdef mingw32_HOST_OS
@@ -145,29 +135,22 @@
  - The cheaper way is to see if the InodeCache for the key matches the
  - file. -}
 isUnmodified :: Key -> RawFilePath -> Annex Bool
-isUnmodified key f = go =<< geti
-  where
-	go Nothing = return False
-	go (Just fc) = isUnmodifiedCheap' key fc <||> expensivecheck fc
-	expensivecheck fc = ifM (verifyKeyContent RetrievalAllKeysSecure AlwaysVerify UnVerified key f)
-		( do
-			-- The file could have been modified while it was
-			-- being verified. Detect that.
-			ifM (geti >>= maybe (return False) (compareInodeCaches fc))
-				( do
-					-- Update the InodeCache to avoid
-					-- performing this expensive check again.
-					Database.Keys.addInodeCaches key [fc]
-					return True
-				, return False
-				)
-		, return False
-		)
-	geti = withTSDelta (liftIO . genInodeCache f)
+isUnmodified key f = 
+	withTSDelta (liftIO . genInodeCache f) >>= \case
+		Just fc -> do
+			ic <- Database.Keys.getInodeCaches key
+			isUnmodified' key f fc ic
+		Nothing -> return False
 
+isUnmodified' :: Key -> RawFilePath -> InodeCache -> [InodeCache] -> Annex Bool
+isUnmodified' = isUnmodifiedLowLevel Database.Keys.addInodeCaches
+
 {- Cheap check if a file contains the unmodified content of the key,
  - only checking the InodeCache of the key.
  -
+ - When the InodeCache is stale, this may incorrectly report that a file is
+ - modified.
+ -
  - Note that, on systems not supporting high-resolution mtimes,
  - this may report a false positive when repeated edits are made to a file
  - within a small time window (eg 1 second).
@@ -177,57 +160,5 @@
 	=<< withTSDelta (liftIO . genInodeCache f)
 
 isUnmodifiedCheap' :: Key -> InodeCache -> Annex Bool
-isUnmodifiedCheap' key fc = 
-	anyM (compareInodeCaches fc) =<< Database.Keys.getInodeCaches key
-
-{- Verifies that a file is the expected content of a key.
- -
- - Configuration can prevent verification, for either a
- - particular remote or always, unless the RetrievalSecurityPolicy
- - requires verification.
- -
- - Most keys have a known size, and if so, the file size is checked.
- -
- - When the key's backend allows verifying the content (via checksum),
- - it is checked. 
- -
- - If the RetrievalSecurityPolicy requires verification and the key's
- - backend doesn't support it, the verification will fail.
- -}
-verifyKeyContent :: RetrievalSecurityPolicy -> VerifyConfig -> Verification -> Key -> RawFilePath -> Annex Bool
-verifyKeyContent rsp v verification k f = case (rsp, verification) of
-	(_, Verified) -> return True
-	(RetrievalVerifiableKeysSecure, _) -> ifM (Backend.isVerifiable k)
-		( verify
-		, ifM (annexAllowUnverifiedDownloads <$> Annex.getGitConfig)
-			( verify
-			, warnUnverifiableInsecure k >> return False
-			)
-		)
-	(_, UnVerified) -> ifM (shouldVerify v)
-		( verify
-		, return True
-		)
-	(_, MustVerify) -> verify
-  where
-	verify = enteringStage VerifyStage $ verifysize <&&> verifycontent
-	verifysize = case fromKey keySize k of
-		Nothing -> return True
-		Just size -> do
-			size' <- liftIO $ catchDefaultIO 0 $ getFileSize f
-			return (size' == size)
-	verifycontent = Backend.maybeLookupBackendVariety (fromKey keyVariety k) >>= \case
-		Nothing -> return True
-		Just b -> case Types.Backend.verifyKeyContent b of
-			Nothing -> return True
-			Just verifier -> verifier k f
-
-warnUnverifiableInsecure :: Key -> Annex ()
-warnUnverifiableInsecure k = warning $ unwords
-	[ "Getting " ++ kv ++ " keys with this remote is not secure;"
-	, "the content cannot be verified to be correct."
-	, "(Use annex.security.allow-unverified-downloads to bypass"
-	, "this safety check.)"
-	]
-  where
-	kv = decodeBS (formatKeyVariety (fromKey keyVariety k))
+isUnmodifiedCheap' key fc = isUnmodifiedCheapLowLevel fc
+	=<< Database.Keys.getInodeCaches key
diff --git a/Annex/Content/Presence/LowLevel.hs b/Annex/Content/Presence/LowLevel.hs
new file mode 100644
--- /dev/null
+++ b/Annex/Content/Presence/LowLevel.hs
@@ -0,0 +1,36 @@
+{- git-annex object content presence, low-level functions
+ -
+ - Copyright 2010-2021 Joey Hess <id@joeyh.name>
+ -
+ - Licensed under the GNU AGPL version 3 or higher.
+ -}
+
+module Annex.Content.Presence.LowLevel where
+
+import Annex.Common
+import Annex.Verify
+import Annex.InodeSentinal
+import Utility.InodeCache
+
+isUnmodifiedLowLevel :: (Key -> [InodeCache] -> Annex ()) -> Key -> RawFilePath -> InodeCache -> [InodeCache] -> Annex Bool
+isUnmodifiedLowLevel addinodecaches key f fc ic =
+	isUnmodifiedCheapLowLevel fc ic <||> expensivecheck
+  where
+	expensivecheck = ifM (verifyKeyContent key f)
+		( do
+			-- The file could have been modified while it was
+			-- being verified. Detect that.
+			ifM (geti >>= maybe (return False) (compareInodeCaches fc))
+				( do
+					-- Update the InodeCache to avoid
+					-- performing this expensive check again.
+					addinodecaches key [fc]
+					return True
+				, return False
+				)
+		, return False
+		)
+	geti = withTSDelta (liftIO . genInodeCache f)
+
+isUnmodifiedCheapLowLevel :: InodeCache -> [InodeCache] -> Annex Bool
+isUnmodifiedCheapLowLevel fc ic = anyM (compareInodeCaches fc) ic
diff --git a/Annex/CopyFile.hs b/Annex/CopyFile.hs
--- a/Annex/CopyFile.hs
+++ b/Annex/CopyFile.hs
@@ -16,7 +16,6 @@
 import Utility.FileMode
 import Utility.Touch
 import Types.Backend
-import Backend
 import Annex.Verify
 
 import Control.Concurrent
diff --git a/Annex/Init.hs b/Annex/Init.hs
--- a/Annex/Init.hs
+++ b/Annex/Init.hs
@@ -96,8 +96,8 @@
 		Right username -> [username, at, hostname, ":", reldir]
 		Left _ -> [hostname, ":", reldir]
 
-initialize :: Maybe String -> Maybe RepoVersion -> Annex ()
-initialize mdescription mversion = checkInitializeAllowed $ do
+initialize :: Bool -> Maybe String -> Maybe RepoVersion -> Annex ()
+initialize autoinit mdescription mversion = checkInitializeAllowed $ do
 	{- Has to come before any commits are made as the shared
 	 - clone heuristic expects no local objects. -}
 	sharedclone <- checkSharedClone
@@ -107,10 +107,10 @@
 	ensureCommit $ Annex.Branch.create
 
 	prepUUID
-	initialize' mversion
+	initialize' autoinit mversion
 	
 	initSharedClone sharedclone
-
+	
 	u <- getUUID
 	{- Avoid overwriting existing description with a default
 	 - description. -}
@@ -119,8 +119,8 @@
 
 -- Everything except for uuid setup, shared clone setup, and initial
 -- description.
-initialize' :: Maybe RepoVersion -> Annex ()
-initialize' mversion = checkInitializeAllowed $ do
+initialize' :: Bool -> Maybe RepoVersion -> Annex ()
+initialize' autoinit mversion = checkInitializeAllowed $ do
 	checkLockSupport
 	checkFifoSupport
 	checkCrippledFileSystem
@@ -135,9 +135,11 @@
 		then configureSmudgeFilter
 		else deconfigureSmudgeFilter
 	unlessM isBareRepo $ do
-		scanAnnexedFiles True
 		hookWrite postCheckoutHook
 		hookWrite postMergeHook
+		unless autoinit $
+			scanAnnexedFiles
+
 	AdjustedBranch.checkAdjustedClone >>= \case
 		AdjustedBranch.InAdjustedClone -> return ()
 		AdjustedBranch.NotInAdjustedClone ->
@@ -198,7 +200,7 @@
   where
 	needsinit = ifM autoInitializeAllowed
 		( do
-			initialize Nothing Nothing
+			initialize True Nothing Nothing
 			autoEnableSpecialRemotes
 		, giveup "First run: git-annex init"
 		)
@@ -232,7 +234,7 @@
   where
 	needsinit =
 		whenM (initializeAllowed <&&> autoInitializeAllowed) $ do
-			initialize Nothing Nothing
+			initialize True Nothing Nothing
 			autoEnableSpecialRemotes
 
 {- Checks if a repository is initialized. Does not check version for ugrade. -}
diff --git a/Annex/InodeSentinal.hs b/Annex/InodeSentinal.hs
--- a/Annex/InodeSentinal.hs
+++ b/Annex/InodeSentinal.hs
@@ -6,6 +6,7 @@
  -}
 
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 module Annex.InodeSentinal where
 
@@ -30,11 +31,23 @@
 {- Checks if one of the provided old InodeCache matches the current
  - version of a file. -}
 sameInodeCache :: RawFilePath -> [InodeCache] -> Annex Bool
-sameInodeCache _ [] = return False
+sameInodeCache file [] = do
+	fastDebug "Annex.InodeSentinal" $
+		fromRawFilePath file ++ " inode cache empty"
+	return False
 sameInodeCache file old = go =<< withTSDelta (liftIO . genInodeCache file)
   where
-	go Nothing = return False
-	go (Just curr) = elemInodeCaches curr old
+	go Nothing = do
+		fastDebug "Annex.InodeSentinal" $
+			fromRawFilePath file ++ " not present, cannot compare with inode cache"
+		return False
+	go (Just curr) = ifM (elemInodeCaches curr old)
+		( return True
+		, do
+			fastDebug "Annex.InodeSentinal" $
+				fromRawFilePath file ++ " (" ++ show curr ++ ") does not match inode cache (" ++ show old ++ ")"
+			return False
+		)
 
 elemInodeCaches :: InodeCache -> [InodeCache] -> Annex Bool
 elemInodeCaches _ [] = return False
diff --git a/Annex/Locations.hs b/Annex/Locations.hs
--- a/Annex/Locations.hs
+++ b/Annex/Locations.hs
@@ -1,6 +1,6 @@
 {- git-annex file locations
  -
- - Copyright 2010-2020 Joey Hess <id@joeyh.name>
+ - Copyright 2010-2021 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -22,7 +22,8 @@
 	gitAnnexContentLock,
 	gitAnnexInodeSentinal,
 	gitAnnexInodeSentinalCache,
-	annexLocations,
+	annexLocationsBare,
+	annexLocationsNonBare,
 	gitAnnexDir,
 	gitAnnexObjectDir,
 	gitAnnexTmpOtherDir,
@@ -134,14 +135,21 @@
 objectDir' :: RawFilePath
 objectDir' = P.addTrailingPathSeparator $ annexDir P.</> "objects"
 
-{- Annexed file's possible locations relative to the .git directory.
- - There are two different possibilities, using different hashes.
+{- Annexed file's possible locations relative to the .git directory
+ - in a non-bare repository.
  -
- - Also, some repositories have a Difference in hash directory depth.
- -}
-annexLocations :: GitConfig -> Key -> [RawFilePath]
-annexLocations config key = map (annexLocation config key) dirHashes
+ - Normally it is hashDirMixed. However, it's always possible that a
+ - bare repository was converted to non-bare, or that the cripped
+ - filesystem setting changed, so still need to check both. -}
+annexLocationsNonBare :: GitConfig -> Key -> [RawFilePath]
+annexLocationsNonBare config key = 
+	map (annexLocation config key) [hashDirMixed, hashDirLower]
 
+{- Annexed file's possible locations relative to a bare repository. -}
+annexLocationsBare :: GitConfig -> Key -> [RawFilePath]
+annexLocationsBare config key = 
+	map (annexLocation config key) [hashDirLower, hashDirMixed]
+
 annexLocation :: GitConfig -> Key -> (HashLevels -> Hasher) -> RawFilePath
 annexLocation config key hasher = objectDir' P.</> keyPath key (hasher $ objectHashLevels config)
 
@@ -171,23 +179,20 @@
 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
+	| Git.repoIsLocalBare r = checkall annexLocationsBare
+	{- If the repository is configured to only use lower, no need
+	 - to check both. -}
 	| hasDifference ObjectHashLower (annexDifferences config) = 
 		only hashDirLower
-	{- Repositories on crippled filesystems use hashDirLower
-	 - for new content, unless symlinks are supported too.
-	 - Then hashDirMixed is used. But, the content could be
-	 - in either location so check both. -}
+	{- Repositories on crippled filesystems use same layout as bare
+	 - repos for new content, unless symlinks are supported too. -}
 	| crippled = if symlinkssupported
-		then check $ map inrepo $ reverse $ annexLocations config key
-		else checkall
-	{- Regular repositories only use hashDirMixed, so
-	 - don't need to do any work to check if the file is
-	 - present. -}
-	| otherwise = only hashDirMixed
+		then checkall annexLocationsNonBare
+		else checkall annexLocationsBare
+	| otherwise = checkall annexLocationsNonBare
   where
 	only = return . inrepo . annexLocation config key
-	checkall = check $ map inrepo $ annexLocations config key
+	checkall f = check $ map inrepo $ f config key
 
 	inrepo d = gitdir P.</> d
 	check locs@(l:_) = fromMaybe l <$> firstM checker locs
@@ -621,8 +626,8 @@
 {- All possibile locations to store a key in a special remote
  - using different directory hashes.
  -
- - This is compatible with the annexLocations, for interoperability between
- - special remotes and git-annex repos.
+ - This is compatible with the annexLocationsNonBare and annexLocationsBare,
+ - for interoperability between special remotes and git-annex repos.
  -}
 keyPaths :: Key -> [RawFilePath]
 keyPaths key = map (\h -> keyPath key (h def)) dirHashes
diff --git a/Annex/ReplaceFile.hs b/Annex/ReplaceFile.hs
--- a/Annex/ReplaceFile.hs
+++ b/Annex/ReplaceFile.hs
@@ -1,6 +1,6 @@
 {- git-annex file replacing
  -
- - Copyright 2013-2020 Joey Hess <id@joeyh.name>
+ - Copyright 2013-2021 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -12,6 +12,7 @@
 	replaceGitDirFile,
 	replaceWorkTreeFile,
 	replaceFile,
+	replaceFile',
 ) where
 
 import Annex.Common
@@ -54,7 +55,10 @@
  - fails, and can create any parent directory structure needed.
  -}
 replaceFile :: (RawFilePath -> Annex ()) -> FilePath -> (FilePath -> Annex a) -> Annex a
-replaceFile createdirectory file action = withOtherTmp $ \othertmpdir -> do
+replaceFile createdirectory file action = replaceFile' createdirectory file (const True) action
+
+replaceFile' :: (RawFilePath -> Annex ()) -> FilePath -> (a -> Bool) -> (FilePath -> Annex a) -> Annex a
+replaceFile' createdirectory file checkres action = withOtherTmp $ \othertmpdir -> do
 	let othertmpdir' = fromRawFilePath othertmpdir
 #ifndef mingw32_HOST_OS
 	-- Use part of the filename as the template for the temp
@@ -70,7 +74,8 @@
 	withTmpDirIn othertmpdir' basetmp $ \tmpdir -> do
 		let tmpfile = tmpdir </> basetmp
 		r <- action tmpfile
-		replaceFileFrom tmpfile file createdirectory
+		when (checkres r) $
+			replaceFileFrom tmpfile file createdirectory
 		return r
 
 replaceFileFrom :: FilePath -> FilePath -> (RawFilePath -> Annex ()) -> Annex ()
diff --git a/Annex/Verify.hs b/Annex/Verify.hs
--- a/Annex/Verify.hs
+++ b/Annex/Verify.hs
@@ -5,11 +5,29 @@
  - Licensed under the GNU AGPL version 3 or higher.
  -}
 
-module Annex.Verify where
+module Annex.Verify (
+	VerifyConfig(..),
+	shouldVerify,
+	verifyKeyContentPostRetrieval,
+	verifyKeyContent,
+	Verification(..),
+	unVerified,
+	warnUnverifiableInsecure,
+	isVerifiable,
+	startVerifyKeyContentIncrementally,
+	IncrementalVerifier(..),
+) where
 
 import Annex.Common
 import qualified Annex
 import qualified Types.Remote
+import qualified Types.Backend
+import Types.Backend (IncrementalVerifier(..))
+import qualified Backend
+import Types.Remote (unVerified, Verification(..), RetrievalSecurityPolicy(..))
+import Annex.WorkerPool
+import Types.WorkerPool
+import Types.Key
 
 data VerifyConfig = AlwaysVerify | NoVerify | RemoteVerify Remote | DefaultVerify
 
@@ -23,3 +41,74 @@
 	-- Export remotes are not key/value stores, so always verify
 	-- content from them even when verification is disabled.
 	<||> Types.Remote.isExportSupported r
+
+{- Verifies that a file is the expected content of a key.
+ -
+ - Configuration can prevent verification, for either a
+ - particular remote or always, unless the RetrievalSecurityPolicy
+ - requires verification.
+ -
+ - Most keys have a known size, and if so, the file size is checked.
+ -
+ - When the key's backend allows verifying the content (via checksum),
+ - it is checked. 
+ -
+ - If the RetrievalSecurityPolicy requires verification and the key's
+ - backend doesn't support it, the verification will fail.
+ -}
+verifyKeyContentPostRetrieval :: RetrievalSecurityPolicy -> VerifyConfig -> Verification -> Key -> RawFilePath -> Annex Bool
+verifyKeyContentPostRetrieval rsp v verification k f = case (rsp, verification) of
+	(_, Verified) -> return True
+	(RetrievalVerifiableKeysSecure, _) -> ifM (isVerifiable k)
+		( verify
+		, ifM (annexAllowUnverifiedDownloads <$> Annex.getGitConfig)
+			( verify
+			, warnUnverifiableInsecure k >> return False
+			)
+		)
+	(_, UnVerified) -> ifM (shouldVerify v)
+		( verify
+		, return True
+		)
+	(_, MustVerify) -> verify
+  where
+	verify = enteringStage VerifyStage $ verifyKeyContent k f
+
+verifyKeyContent :: Key -> RawFilePath -> Annex Bool
+verifyKeyContent k f = verifysize <&&> verifycontent
+  where
+	verifysize = case fromKey keySize k of
+		Nothing -> return True
+		Just size -> do
+			size' <- liftIO $ catchDefaultIO 0 $ getFileSize f
+			return (size' == size)
+	verifycontent = Backend.maybeLookupBackendVariety (fromKey keyVariety k) >>= \case
+		Nothing -> return True
+		Just b -> case Types.Backend.verifyKeyContent b of
+			Nothing -> return True
+			Just verifier -> verifier k f
+
+warnUnverifiableInsecure :: Key -> Annex ()
+warnUnverifiableInsecure k = warning $ unwords
+	[ "Getting " ++ kv ++ " keys with this remote is not secure;"
+	, "the content cannot be verified to be correct."
+	, "(Use annex.security.allow-unverified-downloads to bypass"
+	, "this safety check.)"
+	]
+  where
+	kv = decodeBS (formatKeyVariety (fromKey keyVariety k))
+
+isVerifiable :: Key -> Annex Bool
+isVerifiable k = maybe False (isJust . Types.Backend.verifyKeyContent) 
+	<$> Backend.maybeLookupBackendVariety (fromKey keyVariety k)
+
+startVerifyKeyContentIncrementally :: VerifyConfig -> Key -> Annex (Maybe IncrementalVerifier)
+startVerifyKeyContentIncrementally verifyconfig k = 
+	ifM (shouldVerify verifyconfig)
+		( Backend.maybeLookupBackendVariety (fromKey keyVariety k) >>= \case
+			Just b -> case Types.Backend.verifyKeyContentIncrementally b of
+				Just v -> Just <$> v k
+				Nothing -> return Nothing
+			Nothing -> return Nothing
+		, return Nothing
+		)
diff --git a/Annex/WorkTree.hs b/Annex/WorkTree.hs
--- a/Annex/WorkTree.hs
+++ b/Annex/WorkTree.hs
@@ -8,25 +8,11 @@
 module Annex.WorkTree where
 
 import Annex.Common
-import qualified Annex
 import Annex.Link
 import Annex.CatFile
-import Annex.Content
-import Annex.ReplaceFile
 import Annex.CurrentBranch
-import Annex.InodeSentinal
-import Utility.InodeCache
-import Git.FilePath
-import Git.CatFile
-import qualified Git.Ref
-import qualified Git.LsTree
-import qualified Git.Types
 import qualified Database.Keys
-import Config
-import qualified Utility.RawFilePath as R
 
-import qualified Data.ByteString.Lazy as L
-
 {- Looks up the key corresponding to an annexed file in the work tree,
  - by examining what the file links to.
  -
@@ -69,74 +55,13 @@
 
 {- Find all annexed files and update the keys database for them.
  - 
- - This is expensive, and so normally the associated files are updated
- - incrementally when changes are noticed. So, this only needs to be done
- - when initializing/upgrading a repository.
+ - Normally the keys database is updated incrementally when it's being
+ - opened, and changes are noticed. Calling this explicitly allows
+ - running the update at an earlier point.
  -
- - Also, the content for an unlocked file may already be present as
- - an annex object. If so, populate the pointer file with it.
- - But if worktree file does not have a pointer file's content, it is left
- - as-is.
+ - All that needs to be done is to open the database,
+ - that will result in Database.Keys.reconcileStaged
+ - running, and doing the work.
  -}
-scanAnnexedFiles :: Bool -> Annex ()
-scanAnnexedFiles initscan = do
-	-- This gets the keys database populated with all annexed files,
-	-- by running Database.Keys.reconcileStaged.
-	Database.Keys.runWriter (const noop)
-	-- The above tries to populate pointer files, but one thing it
-	-- is not able to handle is populating a pointer file when the
-	-- annex object file already exists, but its inode is not yet
-	-- cached and annex.thin is set. So, the rest of this makes
-	-- another pass over the tree to do that.
-	whenM
-		( pure initscan
-		<&&> annexThin <$> Annex.getGitConfig
-		<&&> inRepo Git.Ref.headExists
-		<&&> not <$> isBareRepo
-		) $ do
-			g <- Annex.gitRepo
-			(l, cleanup) <- inRepo $ Git.LsTree.lsTree
-				Git.LsTree.LsTreeRecursive
-				(Git.LsTree.LsTreeLong True)
-				Git.Ref.headRef
-			catObjectStreamLsTree l want g go
-			liftIO $ void cleanup
-  where
-	-- Want to process symlinks, and regular files.
-	want i = case Git.Types.toTreeItemType (Git.LsTree.mode i) of
-		Just Git.Types.TreeSymlink -> Just (i, False)
-		Just Git.Types.TreeFile -> checkfilesize i
-		Just Git.Types.TreeExecutable -> checkfilesize i
-		_ -> Nothing
-	
-	-- Avoid processing files that are too large to be pointer files.
-	checkfilesize i = case Git.LsTree.size i of
-		Just n | n < maxPointerSz -> Just (i, True)
-		_ -> Nothing
-	
-	go getnext = liftIO getnext >>= \case
-		Just ((i, isregfile), Just c) -> do
-			maybe noop (add i isregfile)
-				(parseLinkTargetOrPointer (L.toStrict c))
-			go getnext
-		_ -> return ()
-	
-	add i isregfile k = do
-		let tf = Git.LsTree.file i
-		whenM (pure isregfile <&&> inAnnex k) $ do
-			f <- fromRepo $ fromTopFilePath tf
-			liftIO (isPointerFile f) >>= \case
-				Just k' | k' == k -> do
-					destmode <- liftIO $ catchMaybeIO $
-						fileMode <$> R.getFileStatus f
-					ic <- replaceWorkTreeFile (fromRawFilePath f) $ \tmp -> do
-						let tmp' = toRawFilePath tmp
-						linkFromAnnex k tmp' destmode >>= \case
-							LinkAnnexOk -> 
-								withTSDelta (liftIO . genInodeCache tmp')
-							LinkAnnexNoop -> return Nothing
-							LinkAnnexFailed -> liftIO $ do
-								writePointerFile tmp' k destmode
-								return Nothing
-					maybe noop (restagePointerFile (Restage True) f) ic
-				_ -> noop
+scanAnnexedFiles :: Annex ()
+scanAnnexedFiles = Database.Keys.runWriter (const noop)
diff --git a/Assistant/MakeRepo.hs b/Assistant/MakeRepo.hs
--- a/Assistant/MakeRepo.hs
+++ b/Assistant/MakeRepo.hs
@@ -60,8 +60,8 @@
 	unlessM (Git.Config.isBare <$> gitRepo) $ do
 		cmode <- annexCommitMode <$> Annex.getGitConfig
 		void $ inRepo $ Git.Branch.commitCommand cmode
-			[ Param "--quiet"
-			, Param "--allow-empty"
+			(Git.Branch.CommitQuiet True)
+			[ Param "--allow-empty"
 			, Param "-m"
 			, Param "created repository"
 			]
@@ -85,7 +85,7 @@
 
 initRepo' :: Maybe String -> Maybe StandardGroup -> Annex ()
 initRepo' desc mgroup = unlessM isInitialized $ do
-	initialize desc Nothing
+	initialize False desc Nothing
 	u <- getUUID
 	maybe noop (defaultStandardGroup u) mgroup
 	{- Ensure branch gets committed right away so it is
diff --git a/Assistant/Sync.hs b/Assistant/Sync.hs
--- a/Assistant/Sync.hs
+++ b/Assistant/Sync.hs
@@ -21,6 +21,7 @@
 import qualified Git.Command
 import qualified Remote
 import qualified Types.Remote as Remote
+import qualified Annex
 import qualified Annex.Branch
 import Remote.List.Util
 import Annex.UUID
@@ -125,25 +126,26 @@
 
 pushToRemotes' :: UTCTime -> [Remote] -> Assistant [Remote]
 pushToRemotes' now remotes = do
-	(g, branch, u) <- liftAnnex $ do
+	(g, branch, u, ms) <- liftAnnex $ do
 		Annex.Branch.commit =<< Annex.Branch.commitMessage
-		(,,)
+		(,,,)
 			<$> gitRepo
 			<*> getCurrentBranch
 			<*> getUUID
-	ret <- go True branch g u remotes
+			<*> Annex.getState Annex.output
+	ret <- go ms True branch g u remotes
 	return ret
   where
-	go _ (Nothing, _) _ _ _ = return [] -- no branch, so nothing to do
-	go _ _ _ _ [] = return [] -- no remotes, so nothing to do
-	go shouldretry currbranch@(Just branch, _) g u rs =  do
+	go _ _ (Nothing, _) _ _ _ = return [] -- no branch, so nothing to do
+	go _ _ _ _ _ [] = return [] -- no remotes, so nothing to do
+	go ms shouldretry currbranch@(Just branch, _) g u rs =  do
 		debug ["pushing to", show rs]
-		(succeeded, failed) <- parallelPush g rs (push branch)
+		(succeeded, failed) <- parallelPush g rs (push ms branch)
 		updatemap succeeded []
 		if null failed
 			then return []
 			else if shouldretry
-				then retry currbranch g u failed
+				then retry ms currbranch g u failed
 				else fallback branch g u failed
 
 	updatemap succeeded failed = do
@@ -153,10 +155,10 @@
 				M.difference m (makemap succeeded)
 	makemap l = M.fromList $ zip l (repeat now)
 
-	retry currbranch g u rs = do
+	retry ms currbranch g u rs = do
 		debug ["trying manual pull to resolve failed pushes"]
 		void $ manualPull currbranch rs
-		go False currbranch g u rs
+		go ms False currbranch g u rs
 
 	fallback branch g u rs = do
 		debug ["fallback pushing to", show rs]
@@ -164,7 +166,7 @@
 		updatemap succeeded failed
 		return failed
 		
-	push branch remote = Command.Sync.pushBranch remote (Just branch)
+	push ms branch remote = Command.Sync.pushBranch remote (Just branch) ms
 
 parallelPush :: Git.Repo -> [Remote] -> (Remote -> Git.Repo -> IO Bool)-> Assistant ([Remote], [Remote])
 parallelPush g rs a = do
@@ -211,6 +213,8 @@
 manualPull :: Command.Sync.CurrBranch -> [Remote] -> Assistant ([Remote], Bool)
 manualPull currentbranch remotes = do
 	g <- liftAnnex gitRepo
+	-- Allow merging unrelated histories.
+	mc <- liftAnnex $ Command.Sync.mergeConfig True
 	failed <- forM remotes $ \r -> if wantpull $ Remote.gitconfig r
 		then do
 			g' <- liftAnnex $ do
@@ -225,7 +229,7 @@
 		<$> liftAnnex Annex.Branch.forceUpdate
 	forM_ remotes $ \r ->
 		liftAnnex $ Command.Sync.mergeRemote r
-			currentbranch Command.Sync.mergeConfig def
+			currentbranch mc def
 	when haddiverged $
 		updateExportTreeFromLogAll
 	return (catMaybes failed, haddiverged)
diff --git a/Assistant/Threads/Merger.hs b/Assistant/Threads/Merger.hs
--- a/Assistant/Threads/Merger.hs
+++ b/Assistant/Threads/Merger.hs
@@ -90,8 +90,11 @@
 					]
 				void $ liftAnnex $ do
 					cmode <- annexCommitMode <$> Annex.getGitConfig
+					-- Allow merging unrelated histories.
+					mc <- Command.Sync.mergeConfig True
 					Command.Sync.merge
-						currbranch Command.Sync.mergeConfig
+						currbranch
+						mc
 						def
 						cmode
 						changedbranch
diff --git a/Backend.hs b/Backend.hs
--- a/Backend.hs
+++ b/Backend.hs
@@ -16,14 +16,11 @@
 	maybeLookupBackendVariety,
 	isStableKey,
 	isCryptographicallySecure,
-	isVerifiable,
-	startVerifyKeyContentIncrementally,
 ) where
 
 import Annex.Common
 import qualified Annex
 import Annex.CheckAttr
-import Annex.Verify
 import Types.Key
 import Types.KeySource
 import qualified Types.Backend as B
@@ -125,18 +122,3 @@
 isCryptographicallySecure :: Key -> Annex Bool
 isCryptographicallySecure k = maybe False (`B.isCryptographicallySecure` k)
 	<$> maybeLookupBackendVariety (fromKey keyVariety k)
-
-isVerifiable :: Key -> Annex Bool
-isVerifiable k = maybe False (isJust . B.verifyKeyContent) 
-	<$> maybeLookupBackendVariety (fromKey keyVariety k)
-
-startVerifyKeyContentIncrementally :: VerifyConfig -> Key -> Annex (Maybe B.IncrementalVerifier)
-startVerifyKeyContentIncrementally verifyconfig k = 
-	ifM (shouldVerify verifyconfig)
-		( maybeLookupBackendVariety (fromKey keyVariety k) >>= \case
-			Just b -> case B.verifyKeyContentIncrementally b of
-				Just v -> Just <$> v k
-				Nothing -> return Nothing
-			Nothing -> return Nothing
-		, return Nothing
-		)
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,34 @@
+git-annex (8.20210803) upstream; urgency=medium
+
+  * whereused: New command, finds what files use a key, or where a key
+    was used historically.
+  * Fix a bug that prevented getting content from a repository that
+    started out as a bare repository, or had annex.crippledfilesystem
+    set, and was converted to a non-bare repository.
+  * Fix retrieval of content from borg repos accessed over ssh.
+  * sync: When --quiet is used, run git commit, push, and pull without
+    their ususual output.
+  * merge: When --quiet is used, run git merge without its usual output.
+  * sync, merge, post-receive: Avoid merging unrelated histories,
+    which used to be allowed only to support direct mode repositories.
+    (However, sync does still merge unrelated histories when importing
+    trees from special remotes, and the assistant still merges unrelated
+    histories.)
+  * sync, merge: Added --allow-unrelated-histories option, which
+    is the same as the git merge option.
+  * Fix bug that caused some transfers to incorrectly fail with
+    "content changed while it was being sent", when the content was not
+    changed.
+  * Fix bug that could prevent pointer files from being populated,
+    in a repository that was upgraded from v7.
+  * fsck: Detect and correct stale or missing inode caches.
+  * Fix a rounding bug in display of data sizes.
+  * git-annex get when run as the first git-annex command in a new repo
+    did not populate unlocked files.
+    (Reversion in version 8.20210621)
+
+ -- Joey Hess <id@joeyh.name>  Tue, 03 Aug 2021 12:20:09 -0400
+
 git-annex (8.20210714) upstream; urgency=medium
 
   * assistant: Avoid unncessary git repository repair in a situation where
diff --git a/CmdLine/GitAnnex.hs b/CmdLine/GitAnnex.hs
--- a/CmdLine/GitAnnex.hs
+++ b/CmdLine/GitAnnex.hs
@@ -72,6 +72,7 @@
 import qualified Command.Find
 import qualified Command.FindRef
 import qualified Command.Whereis
+import qualified Command.WhereUsed
 import qualified Command.List
 import qualified Command.Log
 import qualified Command.Merge
@@ -207,6 +208,7 @@
 	, Command.Find.cmd
 	, Command.FindRef.cmd
 	, Command.Whereis.cmd
+	, Command.WhereUsed.cmd
 	, Command.List.cmd
 	, Command.Log.cmd
 	, Command.Merge.cmd
diff --git a/Command/ConfigList.hs b/Command/ConfigList.hs
--- a/Command/ConfigList.hs
+++ b/Command/ConfigList.hs
@@ -47,7 +47,7 @@
 		else ifM (Annex.Branch.hasSibling <||> (isJust <$> Fields.getField Fields.autoInit))
 			( do
 				liftIO checkNotReadOnly
-				initialize Nothing Nothing
+				initialize True Nothing Nothing
 				getUUID
 			, return NoUUID
 			)
diff --git a/Command/Fix.hs b/Command/Fix.hs
--- a/Command/Fix.hs
+++ b/Command/Fix.hs
@@ -77,15 +77,15 @@
 		unlessM (checkedCopyFile key obj tmp' mode) $
 			error "unable to break hard link"
 		thawContent tmp'
+		Database.Keys.storeInodeCaches key [tmp']
 		modifyContent obj $ freezeContent obj
-	Database.Keys.storeInodeCaches key [file]
 	next $ return True
 
 makeHardLink :: RawFilePath -> Key -> CommandPerform
 makeHardLink file key = do
 	replaceWorkTreeFile (fromRawFilePath file) $ \tmp -> do
 		mode <- liftIO $ catchMaybeIO $ fileMode <$> R.getFileStatus file
-		linkFromAnnex key (toRawFilePath tmp) mode >>= \case
+		linkFromAnnex' key (toRawFilePath tmp) mode >>= \case
 			LinkAnnexFailed -> error "unable to make hard link"
 			_ -> noop
 	next $ return True
diff --git a/Command/Fsck.hs b/Command/Fsck.hs
--- a/Command/Fsck.hs
+++ b/Command/Fsck.hs
@@ -1,6 +1,6 @@
 {- git-annex command
  -
- - Copyright 2010-2020 Joey Hess <id@joeyh.name>
+ - Copyright 2010-2021 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -16,6 +16,7 @@
 import qualified Types.Backend
 import qualified Backend
 import Annex.Content
+import Annex.Content.Presence.LowLevel
 import Annex.Perms
 import Annex.Link
 import Logs.Location
@@ -31,6 +32,8 @@
 import Utility.CopyFile
 import Git.FilePath
 import Utility.PID
+import Utility.InodeCache
+import Annex.InodeSentinal
 import qualified Database.Keys
 import qualified Database.Fsck as FsckDb
 import Types.CleanupActions
@@ -357,13 +360,13 @@
 				let tmp' = toRawFilePath tmp
 				mode <- liftIO $ catchMaybeIO $ fileMode <$> R.getFileStatus file
 				ifM (annexThin <$> Annex.getGitConfig)
-					( void $ linkFromAnnex key tmp' mode
+					( void $ linkFromAnnex' key tmp' mode
 					, do
 						obj <- calcRepo (gitAnnexLocation key)
 						void $ checkedCopyFile key obj tmp' mode
 						thawContent tmp'
 					)
-			Database.Keys.storeInodeCaches key [file]
+				Database.Keys.storeInodeCaches key [tmp']
 		_ -> return ()
 	return True
 
@@ -437,17 +440,23 @@
 
 {- Runs the backend specific check on a key's content object.
  -
- - When a file is unlocked, it may be a hard link to the object,
- - thus when the user modifies the file, the object will be modified and
+ - When a annex.this is set, an unlocked file may be a hard link to the object.
+ - Thus when the user modifies the file, the object will be modified and
  - not pass the check, and we don't want to find an error in this case.
- - So, skip the check if the key is unlocked and modified.
  -}
 checkBackend :: Backend -> Key -> KeyStatus -> AssociatedFile -> Annex Bool
 checkBackend backend key keystatus afile = do
 	content <- calcRepo (gitAnnexLocation key)
 	ifM (pure (isKeyUnlockedThin keystatus) <&&> (not <$> isUnmodified key content))
 		( nocheck
-		, checkBackendOr badContent backend key content ai
+		, do
+			mic <- withTSDelta (liftIO . genInodeCache content)
+			ifM (checkBackendOr badContent backend key content ai)
+				( do
+					checkInodeCache key content mic ai
+					return True
+				, return False
+				)
 		)
   where
 	nocheck = return True
@@ -460,29 +469,44 @@
 
 checkBackendOr :: (Key -> Annex String) -> Backend -> Key -> RawFilePath -> ActionItem -> Annex Bool
 checkBackendOr bad backend key file ai =
-	checkBackendOr' bad backend key file ai (return True)
-
--- The postcheck action is run after the content is verified,
--- in order to detect situations where the file is changed while being
--- verified.
-checkBackendOr' :: (Key -> Annex String) -> Backend -> Key -> RawFilePath -> ActionItem -> Annex Bool -> Annex Bool
-checkBackendOr' bad backend key file ai postcheck =
 	case Types.Backend.verifyKeyContent backend of
-		Nothing -> return True
 		Just verifier -> do
 			ok <- verifier key file
-			ifM postcheck
-				( do
-					unless ok $ do
-						msg <- bad key
+			unless ok $ do
+				msg <- bad key
+				warning $ concat
+					[ decodeBS' (actionItemDesc ai)
+					, ": Bad file content; "
+					, msg
+					]
+			return ok
+		Nothing -> return True
+
+{- Check, if there are InodeCaches recorded for a key, that one of them
+ - matches the object file. There are situations where the InodeCache
+ - of the object file does not get recorded, including a v8 upgrade.
+ - There may also be situations where the wrong InodeCache is recorded,
+ - if inodes are not stable.
+ -
+ - This must be called after the content of the object file has been
+ - verified to be correct. The InodeCache is generated again to detect if
+ - the object file was changed while the content was being verified.
+ -}
+checkInodeCache :: Key -> RawFilePath -> Maybe InodeCache -> ActionItem -> Annex ()
+checkInodeCache key content mic ai = case mic of
+	Nothing -> noop
+	Just ic -> do
+		ics <- Database.Keys.getInodeCaches key
+		unless (null ics) $
+			unlessM (isUnmodifiedCheapLowLevel ic ics) $ do
+				withTSDelta (liftIO . genInodeCache content) >>= \case
+					Nothing -> noop
+					Just ic' -> whenM (compareInodeCaches ic ic') $ do
 						warning $ concat
 							[ decodeBS' (actionItemDesc ai)
-							, ": Bad file content; "
-							, msg
+							, ": Stale or missing inode cache; updating."
 							]
-					return ok
-				, return True
-				)
+						Database.Keys.addInodeCaches key [ic]
 
 checkKeyNumCopies :: Key -> AssociatedFile -> NumCopies -> Annex Bool
 checkKeyNumCopies key afile numcopies = do
diff --git a/Command/Init.hs b/Command/Init.hs
--- a/Command/Init.hs
+++ b/Command/Init.hs
@@ -70,7 +70,7 @@
 			Just v | v /= wantversion ->
 				giveup $ "This repository is already a initialized with version " ++ show (fromRepoVersion v) ++ ", not changing to requested version."
 			_ -> noop
-	initialize
+	initialize False
 		(if null (initDesc os) then Nothing else Just (initDesc os))
 		(initVersion os)
 	Annex.SpecialRemote.autoEnable
diff --git a/Command/Lock.hs b/Command/Lock.hs
--- a/Command/Lock.hs
+++ b/Command/Lock.hs
@@ -91,8 +91,10 @@
 		liftIO $ removeWhenExistsWith R.removeLink obj
 		case mfile of
 			Just unmodified ->
-				unlessM (checkedCopyFile key unmodified obj Nothing)
-					lostcontent
+				ifM (checkedCopyFile key unmodified obj Nothing)
+					( Database.Keys.storeInodeCaches key [obj]
+					, lostcontent
+					)
 			Nothing -> lostcontent
 
 	lostcontent = logStatus key InfoMissing
diff --git a/Command/Merge.hs b/Command/Merge.hs
--- a/Command/Merge.hs
+++ b/Command/Merge.hs
@@ -1,6 +1,6 @@
 {- git-annex command
  -
- - Copyright 2011-2019 Joey Hess <id@joeyh.name>
+ - Copyright 2011-2021 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -12,23 +12,35 @@
 import qualified Git
 import qualified Git.Branch
 import Annex.CurrentBranch
-import Command.Sync (prepMerge, mergeLocal, mergeConfig, merge, SyncOptions(..))
+import Command.Sync (prepMerge, mergeLocal, mergeConfig, merge, notOnlyAnnexOption, parseUnrelatedHistoriesOption)
 import Git.Types
 
 cmd :: Command
 cmd = command "merge" SectionMaintenance
 	"merge changes from remotes"
-	(paramOptional paramRef) (withParams seek)
+	(paramOptional paramRef) (seek <$$> optParser)
 
-seek :: CmdParams -> CommandSeek
-seek [] = do
-	prepMerge
-	commandAction mergeAnnexBranch
-	commandAction mergeSyncedBranch
-seek bs = do
-	prepMerge
-	forM_ bs (commandAction . mergeBranch . Git.Ref . encodeBS')
+data MergeOptions = MergeOptions
+	{ mergeBranches :: [String]
+	, allowUnrelatedHistories :: Bool
+	}
 
+optParser :: CmdParamsDesc -> Parser MergeOptions
+optParser desc = MergeOptions
+	<$> cmdParams desc
+	<*> parseUnrelatedHistoriesOption
+
+seek :: MergeOptions -> CommandSeek
+seek o
+	| mergeBranches o == [] = do
+		prepMerge
+		commandAction mergeAnnexBranch
+		commandAction (mergeSyncedBranch o)
+	| otherwise = do
+		prepMerge
+		forM_ (mergeBranches o) $
+			commandAction . mergeBranch o . Git.Ref . encodeBS'
+
 mergeAnnexBranch :: CommandStart
 mergeAnnexBranch = starting "merge" ai si $ do
 	_ <- Annex.Branch.update
@@ -39,14 +51,17 @@
 	ai = ActionItemOther (Just (fromRef Annex.Branch.name))
 	si = SeekInput []
 
-mergeSyncedBranch :: CommandStart
-mergeSyncedBranch = mergeLocal mergeConfig def =<< getCurrentBranch
+mergeSyncedBranch :: MergeOptions -> CommandStart
+mergeSyncedBranch o = do
+	mc <- mergeConfig (allowUnrelatedHistories o)
+	mergeLocal mc def =<< getCurrentBranch
 
-mergeBranch :: Git.Ref -> CommandStart
-mergeBranch r = starting "merge" ai si $ do
+mergeBranch :: MergeOptions -> Git.Ref -> CommandStart
+mergeBranch o r = starting "merge" ai si $ do
 	currbranch <- getCurrentBranch
-	let o = def { notOnlyAnnexOption = True }
-	next $ merge currbranch mergeConfig o Git.Branch.ManualCommit r
+	mc <- mergeConfig (allowUnrelatedHistories o)
+	let so = def { notOnlyAnnexOption = True }
+	next $ merge currbranch mc so Git.Branch.ManualCommit r
   where
 	ai = ActionItemOther (Just (Git.fromRef r))
 	si = SeekInput []
diff --git a/Command/PostReceive.hs b/Command/PostReceive.hs
--- a/Command/PostReceive.hs
+++ b/Command/PostReceive.hs
@@ -52,4 +52,5 @@
 updateInsteadEmulation = do
 	prepMerge
 	let o = def { notOnlyAnnexOption = True }
-	mergeLocal mergeConfig o =<< getCurrentBranch
+	mc <- mergeConfig False
+	mergeLocal mc o =<< getCurrentBranch
diff --git a/Command/Reinit.hs b/Command/Reinit.hs
--- a/Command/Reinit.hs
+++ b/Command/Reinit.hs
@@ -35,6 +35,6 @@
 		then return $ toUUID s
 		else Remote.nameToUUID s
 	storeUUID u
-	initialize' Nothing
+	initialize' False Nothing
 	Annex.SpecialRemote.autoEnable
 	next $ return True
diff --git a/Command/Reinject.hs b/Command/Reinject.hs
--- a/Command/Reinject.hs
+++ b/Command/Reinject.hs
@@ -50,7 +50,7 @@
   where
 	src' = toRawFilePath src
 	go key = starting "reinject" ai si $
-		ifM (verifyKeyContent RetrievalAllKeysSecure DefaultVerify UnVerified key src')
+		ifM (verifyKeyContent key src')
 			( perform src' key
 			, giveup $ src ++ " does not have expected content of " ++ dest
 			)
diff --git a/Command/Smudge.hs b/Command/Smudge.hs
--- a/Command/Smudge.hs
+++ b/Command/Smudge.hs
@@ -267,7 +267,7 @@
 	-- point to refresh the keys database for changes to annexed files.
 	-- Doing it explicitly here avoids a later pause in the middle of
 	-- some other action.
-	scanAnnexedFiles False
+	scanAnnexedFiles
 	updateSmudged (Restage True)
 	stop
 
diff --git a/Command/Sync.hs b/Command/Sync.hs
--- a/Command/Sync.hs
+++ b/Command/Sync.hs
@@ -1,7 +1,7 @@
 {- git-annex command
  -
  - Copyright 2011 Joachim Breitner <mail@joachim-breitner.de>
- - Copyright 2011-2020 Joey Hess <id@joeyh.name>
+ - Copyright 2011-2021 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -24,6 +24,7 @@
 	syncBranch,
 	updateBranches,
 	seekExportContent,
+	parseUnrelatedHistoriesOption,
 	SyncOptions(..),
 ) where
 
@@ -102,6 +103,7 @@
 	, cleanupOption :: Bool
 	, keyOptions :: Maybe KeyOptions
 	, resolveMergeOverride :: Bool
+	, allowUnrelatedHistories :: Bool
 	}
 
 instance Default SyncOptions where
@@ -120,6 +122,7 @@
 		, cleanupOption = False
 		, keyOptions = Nothing
 		, resolveMergeOverride = False
+		, allowUnrelatedHistories = False
 		}
 
 optParser :: CmdParamsDesc -> Parser SyncOptions
@@ -177,7 +180,14 @@
 	<*> invertableSwitch "resolvemerge" True
 		( help "do not automatically resolve merge conflicts"
 		)
+	<*> parseUnrelatedHistoriesOption
 
+parseUnrelatedHistoriesOption :: Parser Bool
+parseUnrelatedHistoriesOption = 
+	invertableSwitch "allow-unrelated-histories" False
+		( help "allow merging unrelated histories"
+		)
+
 -- Since prepMerge changes the working directory, FilePath options
 -- have to be adjusted.
 instance DeferredParseClass SyncOptions where
@@ -196,6 +206,7 @@
 		<*> pure (cleanupOption v)
 		<*> pure (keyOptions v)
 		<*> pure (resolveMergeOverride v)
+		<*> pure (allowUnrelatedHistories v)
 
 seek :: SyncOptions -> CommandSeek
 seek o = do
@@ -218,21 +229,23 @@
 			commandAction (withbranch cleanupLocal)
 			mapM_ (commandAction . withbranch . cleanupRemote) gitremotes
 		else do
+			mc <- mergeConfig (allowUnrelatedHistories o)
+
 			-- Syncing involves many actions, any of which
 			-- can independently fail, without preventing
 			-- the others from running. 
 			-- These actions cannot be run concurrently.
 			mapM_ includeCommandAction $ concat
 				[ [ commit o ]
-				, [ withbranch (mergeLocal mergeConfig o) ]
-				, map (withbranch . pullRemote o mergeConfig) gitremotes
+				, [ withbranch (mergeLocal mc o) ]
+				, map (withbranch . pullRemote o mc) gitremotes
 				,  [ mergeAnnex ]
 				]
 			
 			content <- shouldSyncContent o
 
 			forM_ (filter isImport contentremotes) $
-				withbranch . importRemote content o mergeConfig
+				withbranch . importRemote content o
 			forM_ (filter isThirdPartyPopulated contentremotes) $
 				pullThirdPartyPopulated o
 			
@@ -259,7 +272,7 @@
 				-- avoid our push overwriting those changes.
 				when (syncedcontent || exportedcontent) $ do
 					mapM_ includeCommandAction $ concat
-						[ map (withbranch . pullRemote o mergeConfig) gitremotes
+						[ map (withbranch . pullRemote o mc) gitremotes
 						, [ commitAnnex, mergeAnnex ]
 						]
 	
@@ -273,17 +286,18 @@
 prepMerge :: Annex ()
 prepMerge = Annex.changeDirectory . fromRawFilePath =<< fromRepo Git.repoPath
 
-mergeConfig :: [Git.Merge.MergeConfig]	
-mergeConfig = 
-	[ Git.Merge.MergeNonInteractive
-	-- In several situations, unrelated histories should be merged
-	-- together. This includes pairing in the assistant, merging
-	-- from a remote into a newly created direct mode repo,
-	-- and an initial merge from an import from a special remote.
-	-- (Once direct mode is removed, this could be changed, so only
-	-- the assistant and import from special remotes use it.)
-	, Git.Merge.MergeUnrelatedHistories
-	]
+mergeConfig :: Bool -> Annex [Git.Merge.MergeConfig]
+mergeConfig mergeunrelated = do
+	quiet <- commandProgressDisabled
+	return $ catMaybes
+		[ Just Git.Merge.MergeNonInteractive
+		, if mergeunrelated 
+			then Just Git.Merge.MergeUnrelatedHistories
+			else Nothing
+		, if quiet
+			then Just Git.Merge.MergeQuiet
+			else Nothing
+		]
 
 merge :: CurrBranch -> [Git.Merge.MergeConfig] -> SyncOptions -> Git.Branch.CommitMode -> Git.Branch -> Annex Bool
 merge currbranch mergeconfig o commitmode tomerge = do
@@ -331,7 +345,9 @@
 	Annex.Branch.commit =<< Annex.Branch.commitMessage
 	next $ do
 		showOutput
-		void $ inRepo $ Git.Branch.commitCommand Git.Branch.ManualCommit
+		let cmode = Git.Branch.ManualCommit
+		cquiet <- Git.Branch.CommitQuiet <$> commandProgressDisabled
+		void $ inRepo $ Git.Branch.commitCommand cmode cquiet
 			[ Param "-a"
 			, Param "-m"
 			, Param commitmessage
@@ -462,16 +478,21 @@
   where
 	fetch bs = do
 		repo <- Remote.getRepo remote
+		ms <- Annex.getState Annex.output
 		inRepoWithSshOptionsTo repo (Remote.gitconfig remote) $
-			Git.Command.runBool $
-				[Param "fetch", Param $ Remote.name remote]
-					++ map Param bs
+			Git.Command.runBool $ catMaybes
+				[ Just $ Param "fetch"
+				, if commandProgressDisabled' ms
+					then Just $ Param "--quiet"
+					else Nothing
+				, Just $ Param $ Remote.name remote
+				] ++ map Param bs
 	wantpull = remoteAnnexPull (Remote.gitconfig remote)
 	ai = ActionItemOther (Just (Remote.name remote))
 	si = SeekInput []
 
-importRemote :: Bool -> SyncOptions -> [Git.Merge.MergeConfig] -> Remote -> CurrBranch -> CommandSeek
-importRemote importcontent o mergeconfig remote currbranch
+importRemote :: Bool -> SyncOptions -> Remote -> CurrBranch -> CommandSeek
+importRemote importcontent o remote currbranch
 	| not (pullOption o) || not wantpull = noop
 	| otherwise = case remoteAnnexTrackingBranch (Remote.gitconfig remote) of
 		Nothing -> noop
@@ -484,7 +505,13 @@
 			if canImportKeys remote importcontent
 				then do
 					Command.Import.seekRemote remote branch subdir importcontent (CheckGitIgnore True)
-					void $ mergeRemote remote currbranch mergeconfig o
+					-- Importing generates a branch
+					-- that is not initially connected
+					-- to the current branch, so allow
+					-- merging unrelated histories when
+					-- mergeing it.
+					mc <- mergeConfig True
+					void $ mergeRemote remote currbranch mc o
 				else warning $ "Cannot import from " ++ Remote.name remote ++ " when not syncing content."
   where
 	wantpull = remoteAnnexPull (Remote.gitconfig remote)
@@ -548,8 +575,9 @@
 		starting "push" ai si $ next $ do
 			repo <- Remote.getRepo remote
 			showOutput
+			ms <- Annex.getState Annex.output
 			ok <- inRepoWithSshOptionsTo repo gc $
-				pushBranch remote mainbranch
+				pushBranch remote mainbranch ms
 			if ok
 				then postpushupdate repo
 				else do
@@ -621,8 +649,8 @@
  - The only difference caused by using a forced push in that case is that
  - the last repository to push wins the race, rather than the first to push.
  -}
-pushBranch :: Remote -> Maybe Git.Branch -> Git.Repo -> IO Bool
-pushBranch remote mbranch g = directpush `after` annexpush `after` syncpush
+pushBranch :: Remote -> Maybe Git.Branch -> MessageState -> Git.Repo -> IO Bool
+pushBranch remote mbranch ms g = directpush `after` annexpush `after` syncpush
   where
 	syncpush = flip Git.Command.runBool g $ pushparams $ catMaybes
 		[ (refspec . fromAdjustedBranch) <$> mbranch
@@ -648,9 +676,12 @@
 			(transcript, ok) <- processTranscript' p Nothing
 			when (not ok && not ("denyCurrentBranch" `isInfixOf` transcript)) $
 				hPutStr stderr transcript
-	pushparams branches =
-		[ Param "push"
-		, Param $ Remote.name remote
+	pushparams branches = catMaybes
+		[ Just $ Param "push"
+		, if commandProgressDisabled' ms
+			then Just $ Param "--quiet"
+			else Nothing
+		, Just $ Param $ Remote.name remote
 		] ++ map Param branches
 	refspec b = concat 
 		[ Git.fromRef $ Git.Ref.base b
diff --git a/Command/TestRemote.hs b/Command/TestRemote.hs
--- a/Command/TestRemote.hs
+++ b/Command/TestRemote.hs
@@ -354,7 +354,7 @@
 		liftIO $ hClose h
 		tryNonAsync (Remote.retrieveExport ea k testexportlocation tmp nullMeterUpdate) >>= \case
 			Left _ -> return False
-			Right () -> verifyKeyContent RetrievalAllKeysSecure AlwaysVerify UnVerified k (toRawFilePath tmp)
+			Right () -> verifyKeyContentPostRetrieval RetrievalAllKeysSecure AlwaysVerify UnVerified k (toRawFilePath tmp)
 	checkpresentexport ea k = Remote.checkPresentExport ea k testexportlocation
 	removeexport ea k = Remote.removeExport ea k testexportlocation
 	removeexportdirectory ea = case Remote.removeExportDirectory ea of
diff --git a/Command/Unlock.hs b/Command/Unlock.hs
--- a/Command/Unlock.hs
+++ b/Command/Unlock.hs
@@ -50,7 +50,7 @@
 	replaceWorkTreeFile (fromRawFilePath dest) $ \tmp ->
 		ifM (inAnnex key)
 			( do
-				r <- linkFromAnnex key (toRawFilePath tmp) destmode
+				r <- linkFromAnnex' key (toRawFilePath tmp) destmode
 				case r of
 					LinkAnnexOk -> return ()
 					LinkAnnexNoop -> return ()
diff --git a/Command/Unused.hs b/Command/Unused.hs
--- a/Command/Unused.hs
+++ b/Command/Unused.hs
@@ -139,7 +139,7 @@
 unusedMsg' u mheader mtrailer = unlines $
 	mheader ++
 	table u ++
-	["(To see where data was previously used, try: git log --stat --no-textconv -S'KEY')"] ++
+	["(To see where this data was previously used, run: git annex whereused --historical --unused"] ++
 	mtrailer
 
 remoteUnusedMsg :: Maybe Remote -> RemoteName -> [(Int, Key)] -> String
diff --git a/Command/Upgrade.hs b/Command/Upgrade.hs
--- a/Command/Upgrade.hs
+++ b/Command/Upgrade.hs
@@ -45,6 +45,6 @@
 start _ =
 	starting "upgrade" (ActionItemOther Nothing) (SeekInput []) $ do
 		whenM (isNothing <$> getVersion) $ do
-			initialize Nothing Nothing
+			initialize False Nothing Nothing
 		r <- upgrade False latestVersion
 		next $ return r
diff --git a/Command/WhereUsed.hs b/Command/WhereUsed.hs
new file mode 100644
--- /dev/null
+++ b/Command/WhereUsed.hs
@@ -0,0 +1,163 @@
+{- git-annex command
+ -
+ - Copyright 2021 Joey Hess <id@joeyh.name>
+ -
+ - Licensed under the GNU AGPL version 3 or higher.
+ -}
+
+module Command.WhereUsed where
+
+import Command
+import Git
+import Git.Sha
+import Git.FilePath
+import qualified Git.Ref
+import qualified Git.Command
+import qualified Git.DiffTree as DiffTree
+import qualified Annex
+import qualified Annex.Branch
+import Annex.CatFile
+import Database.Keys
+
+import Data.Char
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Lazy as L
+
+cmd :: Command
+cmd = noCommit $ withGlobalOptions [annexedMatchingOptions] $
+	command "whereused" SectionQuery
+		"lists repositories that have file content"
+		paramNothing (seek <$$> optParser)
+
+data WhereUsedOptions = WhereUsedOptions
+	{ keyOptions :: KeyOptions
+	, historicalOption :: Bool
+	}
+
+optParser :: CmdParamsDesc -> Parser WhereUsedOptions
+optParser _desc = WhereUsedOptions
+	<$> (parseUnusedKeysOption <|> parseSpecificKeyOption)
+	<*> switch
+		( long "historical"
+		<> help "find historical uses"
+		)
+
+seek :: WhereUsedOptions -> CommandSeek
+seek o = withKeyOptions (Just (keyOptions o)) False dummyfileseeker
+	(commandAction . start o) dummyfilecommandseek (WorkTreeItems [])
+  where
+	dummyfileseeker = AnnexedFileSeeker
+		{ startAction = \_ _ _ -> return Nothing
+		, checkContentPresent = Nothing
+		, usesLocationLog = False
+		}
+	dummyfilecommandseek = const noop
+
+start :: WhereUsedOptions -> (SeekInput, Key, ActionItem) -> CommandStart
+start o (_, key, _) = startingCustomOutput key $ do
+	fs <- filterM stillassociated 
+		=<< mapM (fromRepo . fromTopFilePath)
+		=<< getAssociatedFiles key
+	liftIO $ forM_ fs $ display key . fromRawFilePath
+
+	when (historicalOption o && null fs) $
+		findHistorical key
+
+	next $ return True
+  where
+	-- Some associated files that are in the keys database may no
+	-- longer correspond to files in the repository.
+	stillassociated f = catKeyFile f >>= \case
+		Just k | k == key -> return True
+		_ -> return False
+
+display :: Key -> String -> IO ()
+display key loc = putStrLn (serializeKey key ++ " " ++ loc)
+
+findHistorical :: Key -> Annex ()
+findHistorical key = do
+	-- Find most recent change to the key, in all branches and
+	-- tags, except the git-annex branch.
+	found <- searchLog key 
+		-- Search all local branches, except git-annex branch.
+		[ Param ("--exclude=*/" ++ fromRef (Annex.Branch.name))
+		, Param "--glob=*"
+		-- Also search remote branches
+		, Param ("--exclude=" ++ fromRef (Annex.Branch.name))
+		, Param "--remotes=*"
+		-- And search tags.
+		, Param "--tags=*"
+		-- Output the commit hash
+		, Param "--pretty=%H"
+		] $ \h fs repo -> do
+			commitsha <- getSha "log" (pure h)
+			commitdesc <- S.takeWhile (/= fromIntegral (ord '\n'))
+				<$> Git.Command.pipeReadStrict
+					[ Param "describe"
+					, Param "--contains"
+					, Param "--all"
+					, Param (fromRef commitsha)
+					] repo
+			if S.null commitdesc
+				then return False
+				else process fs $
+					displayreffile (Ref commitdesc)
+		
+	unless found $
+		void $ searchLog key
+			[ Param "--walk-reflogs"
+			-- Output the reflog selector
+			, Param "--pretty=%gd"
+			] $ \h fs _ -> process fs $
+				displayreffile (Ref h)
+  where
+	process fs a = or <$> forM fs a
+
+	displayreffile r f = do
+		let fref = Git.Ref.branchFileRef r f
+		display key (fromRef fref)
+		return True
+
+searchLog :: Key -> [CommandParam] -> (S.ByteString -> [RawFilePath] -> Repo -> IO Bool) -> Annex Bool
+searchLog key ps a = Annex.inRepo $ \repo -> do
+	(output, cleanup) <- Git.Command.pipeNullSplit ps' repo
+	found <- case output of
+		(h:rest) -> do
+			let diff = DiffTree.parseDiffRaw rest
+			let fs = map (flip fromTopFilePath repo . DiffTree.file) diff
+			rfs <- mapM relPathCwdToFile fs
+			a (L.toStrict h) rfs repo
+		_ -> return False
+	void cleanup
+	return found
+  where
+	ps' = 
+		[ Param "log"
+		, Param "-z"
+		-- Don't convert pointer files.
+		, Param "--no-textconv"
+		-- Don't abbreviate hashes.
+		, Param "--no-abbrev"
+		-- Only find the most recent commit, for speed.
+		, Param "-n1"
+		-- Be sure to treat -G as a regexp.
+		, Param "--basic-regexp"
+		-- Find commits that contain the key. The object has to
+		-- end with the key to avoid confusion with longer keys,
+		-- so a regexp is used. Since annex pointer files
+		-- may contain a newline followed by perhaps something
+		-- else, that is also matched.
+		, Param ("-G" ++ escapeRegexp (fromRawFilePath (keyFile key)) ++ "($|\n)")
+		-- Skip commits where the file was deleted,
+		-- only find those where it was added or modified.
+		, Param "--diff-filter=ACMRTUX"
+		-- Output the raw diff.
+		, Param "--raw"
+		] ++ ps
+
+escapeRegexp :: String -> String
+escapeRegexp = concatMap esc
+  where
+	esc c
+		| isAscii c && isAlphaNum c = [c]
+		| otherwise = ['[', c, ']']
diff --git a/Database/Keys.hs b/Database/Keys.hs
--- a/Database/Keys.hs
+++ b/Database/Keys.hs
@@ -18,7 +18,6 @@
 	getAssociatedKey,
 	removeAssociatedFile,
 	storeInodeCaches,
-	storeInodeCaches',
 	addInodeCaches,
 	getInodeCaches,
 	removeInodeCaches,
@@ -37,6 +36,7 @@
 import qualified Annex
 import Annex.LockFile
 import Annex.Content.PointerFile
+import Annex.Content.Presence.LowLevel
 import Annex.Link
 import Utility.InodeCache
 import Annex.InodeSentinal
@@ -175,18 +175,22 @@
 
 {- Stats the files, and stores their InodeCaches. -}
 storeInodeCaches :: Key -> [RawFilePath] -> Annex ()
-storeInodeCaches k fs = storeInodeCaches' k fs []
-
-storeInodeCaches' :: Key -> [RawFilePath] -> [InodeCache] -> Annex ()
-storeInodeCaches' k fs ics = withTSDelta $ \d ->
-	addInodeCaches k . (++ ics) . catMaybes
+storeInodeCaches k fs = withTSDelta $ \d ->
+	addInodeCaches k . catMaybes
 		=<< liftIO (mapM (\f -> genInodeCache f d) fs)
 
 addInodeCaches :: Key -> [InodeCache] -> Annex ()
 addInodeCaches k is = runWriterIO $ SQL.addInodeCaches k is
 
 {- A key may have multiple InodeCaches; one for the annex object, and one
- - for each pointer file that is a copy of it. -}
+ - for each pointer file that is a copy of it.
+ -
+ - When there are no pointer files, the annex object typically does not
+ - have its InodeCache recorded either, so the list will be empty.
+ -
+ - Note that, in repos upgraded from v7, there may be InodeCaches recorded
+ - for pointer files, but none recorded for the annex object.
+ -}
 getInodeCaches :: Key -> Annex [InodeCache]
 getInodeCaches = runReaderIO . SQL.getInodeCaches
 
@@ -379,12 +383,16 @@
 	reconcilepointerfile file key = do
 		ics <- liftIO $ SQL.getInodeCaches key (SQL.ReadHandle qh)
 		obj <- calcRepo (gitAnnexLocation key)
-		objic <- withTSDelta (liftIO . genInodeCache obj)
-		-- Like inAnnex, check the annex object's inode cache
+		mobjic <- withTSDelta (liftIO . genInodeCache obj)
+		let addinodecaches k v = liftIO $
+			SQL.addInodeCaches k v (SQL.WriteHandle qh)
+		-- Like inAnnex, check the annex object is unmodified
 		-- when annex.thin is set.
 		keypopulated <- ifM (annexThin <$> Annex.getGitConfig)
-			( maybe (pure False) (`elemInodeCaches` ics) objic
-			, pure (isJust objic)
+			( case mobjic of
+				Just objic -> isUnmodifiedLowLevel addinodecaches key obj objic ics
+				Nothing -> pure False
+			, pure (isJust mobjic)
 			)
 		p <- fromRepo $ fromTopFilePath file
 		filepopulated <- sameInodeCache p ics
@@ -392,10 +400,8 @@
 			(True, False) ->
 				populatePointerFile (Restage True) key obj p >>= \case
 					Nothing -> return ()
-					Just ic -> liftIO $
-						SQL.addInodeCaches key
-							(catMaybes [Just ic, objic])
-							(SQL.WriteHandle qh)
+					Just ic -> addinodecaches key
+						(catMaybes [Just ic, mobjic])
 			(False, True) -> depopulatePointerFile key p
 			_ -> return ()
 	
diff --git a/Database/Keys/Handle.hs b/Database/Keys/Handle.hs
--- a/Database/Keys/Handle.hs
+++ b/Database/Keys/Handle.hs
@@ -8,7 +8,6 @@
 module Database.Keys.Handle (
 	DbHandle,
 	newDbHandle,
-	unavailableDbHandle,
 	DbState(..),
 	withDbState,
 	flushDbQueue,
@@ -33,9 +32,6 @@
 
 newDbHandle :: IO DbHandle
 newDbHandle = DbHandle <$> newMVar DbClosed
-
-unavailableDbHandle :: IO DbHandle
-unavailableDbHandle = DbHandle <$> newMVar DbUnavailable
 
 -- Runs an action on the state of the handle, which can change its state.
 -- The MVar is empty while the action runs, which blocks other users
diff --git a/Git/Branch.hs b/Git/Branch.hs
--- a/Git/Branch.hs
+++ b/Git/Branch.hs
@@ -121,6 +121,13 @@
 			(False, True) -> findbest c rs -- worse
 			(False, False) -> findbest c rs -- same
 
+{- Should the commit avoid the usual summary output? -}
+newtype CommitQuiet = CommitQuiet Bool
+
+applyCommitQuiet :: CommitQuiet -> [CommandParam] -> [CommandParam]
+applyCommitQuiet (CommitQuiet True) ps = Param "--quiet" : ps
+applyCommitQuiet (CommitQuiet False) ps = ps
+
 {- The user may have set commit.gpgsign, intending all their manual
  - commits to be signed. But signing automatic/background commits could
  - easily lead to unwanted gpg prompts or failures.
@@ -148,12 +155,14 @@
 	ps' = applyCommitMode commitmode ps
 
 {- Commit via the usual git command. -}
-commitCommand :: CommitMode -> [CommandParam] -> Repo -> IO Bool
+commitCommand :: CommitMode -> CommitQuiet -> [CommandParam] -> Repo -> IO Bool
 commitCommand = commitCommand' runBool
 
-commitCommand' :: ([CommandParam] -> Repo -> IO a) -> CommitMode -> [CommandParam] -> Repo -> IO a
-commitCommand' runner commitmode ps = runner $
-	Param "commit" : applyCommitMode commitmode ps
+commitCommand' :: ([CommandParam] -> Repo -> IO a) -> CommitMode -> CommitQuiet -> [CommandParam] -> Repo -> IO a
+commitCommand' runner commitmode commitquiet ps =
+	runner $ Param "commit" : ps'
+  where
+	ps' = applyCommitMode commitmode (applyCommitQuiet commitquiet ps)
 
 {- Commits the index into the specified branch (or other ref), 
  - with the specified parent refs, and returns the committed sha.
@@ -162,7 +171,7 @@
  - one parent, and it has the same tree that would be committed.
  -
  - Unlike git-commit, does not run any hooks, or examine the work tree
- - in any way.
+ - in any way, or output a summary.
  -}
 commit :: CommitMode -> Bool -> String -> Branch -> [Ref] -> Repo -> IO (Maybe Sha)
 commit commitmode allowempty message branch parentrefs repo = do
diff --git a/Git/DiffTree.hs b/Git/DiffTree.hs
--- a/Git/DiffTree.hs
+++ b/Git/DiffTree.hs
@@ -15,6 +15,7 @@
 	diffFiles,
 	diffLog,
 	commitDiff,
+	parseDiffRaw,
 ) where
 
 import qualified Data.ByteString as B
@@ -116,9 +117,13 @@
 	go (s:[]) = error $ "diff-tree parse error near \"" ++ decodeBL' s ++ "\""
 
 -- :<srcmode> SP <dstmode> SP <srcsha> SP <dstsha> SP <status>
+--
+-- May be prefixed with a newline, which git log --pretty=format
+-- adds to the first line of the diff, even with -z.
 parserDiffRaw :: RawFilePath -> A.Parser DiffTreeItem
 parserDiffRaw f = DiffTreeItem
-	<$ A8.char ':'
+	<$ A.option '\n' (A8.char '\n')
+	<* A8.char ':'
 	<*> octal
 	<* A8.char ' '
 	<*> octal
diff --git a/Git/Merge.hs b/Git/Merge.hs
--- a/Git/Merge.hs
+++ b/Git/Merge.hs
@@ -1,6 +1,6 @@
 {- git merging
  -
- - Copyright 2012-2016 Joey Hess <id@joeyh.name>
+ - Copyright 2012-2021 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -22,9 +22,12 @@
 
 data MergeConfig
 	= MergeNonInteractive
-	-- ^ avoids interactive merge
+	-- ^ avoids interactive merge with commit message edit
 	| MergeUnrelatedHistories
 	-- ^ avoids git's prevention of merging unrelated histories
+	| MergeQuiet
+	-- ^ avoids usual output when merging, but errors will still be
+	-- displayed
 	deriving (Eq)
 
 merge :: Ref -> [MergeConfig] -> CommitMode -> Repo -> IO Bool
@@ -36,11 +39,14 @@
 		go [Param $ fromRef branch]
 	| otherwise = go [Param "--no-edit", Param $ fromRef branch]
   where
-	go ps = merge'' (sp ++ [Param "merge"] ++ ps ++ extraparams) mergeconfig r
+	go ps = merge'' (sp ++ [Param "merge"] ++ qp ++ ps ++ extraparams) mergeconfig r
 	sp
 		| commitmode == AutomaticCommit =
 			[Param "-c", Param "commit.gpgsign=false"]
 		| otherwise = []
+	qp
+		| MergeQuiet `notElem` mergeconfig = []
+		| otherwise = [Param "--quiet"]
 
 merge'' :: [CommandParam] -> [MergeConfig] -> Repo -> IO Bool
 merge'' ps mergeconfig r
diff --git a/Messages.hs b/Messages.hs
--- a/Messages.hs
+++ b/Messages.hs
@@ -43,9 +43,11 @@
 	setupConsole,
 	enableDebugOutput,
 	commandProgressDisabled,
+	commandProgressDisabled',
 	jsonOutputEnabled,
 	outputMessage,
 	withMessageState,
+	MessageState,
 	prompt,
 	mkPrompter,
 ) where
@@ -276,12 +278,14 @@
 {- Should commands that normally output progress messages have that
  - output disabled? -}
 commandProgressDisabled :: Annex Bool
-commandProgressDisabled = withMessageState $ \s -> return $
-	case outputType s of
-		NormalOutput -> concurrentOutputEnabled s
-		QuietOutput -> True
-		JSONOutput _ -> True
-		SerializedOutput _ _ -> True
+commandProgressDisabled = withMessageState $ return . commandProgressDisabled'
+
+commandProgressDisabled' :: MessageState -> Bool
+commandProgressDisabled' s = case outputType s of
+	NormalOutput -> concurrentOutputEnabled s
+	QuietOutput -> True
+	JSONOutput _ -> True
+	SerializedOutput _ _ -> True
 
 jsonOutputEnabled :: Annex Bool
 jsonOutputEnabled = withMessageState $ \s -> return $
diff --git a/P2P/Annex.hs b/P2P/Annex.hs
--- a/P2P/Annex.hs
+++ b/P2P/Annex.hs
@@ -23,8 +23,7 @@
 import Logs.Location
 import Types.NumCopies
 import Utility.Metered
-import Types.Backend (IncrementalVerifier(..))
-import Backend
+import Annex.Verify
 
 import Control.Monad.Free
 import Control.Concurrent.STM
diff --git a/Remote/Borg.hs b/Remote/Borg.hs
--- a/Remote/Borg.hs
+++ b/Remote/Borg.hs
@@ -39,7 +39,7 @@
 import qualified Data.ByteString.Lazy as L
 import qualified System.FilePath.ByteString as P
 
-type BorgRepo = String
+newtype BorgRepo = BorgRepo { locBorgRepo :: String }
 
 type BorgArchiveName = S.ByteString
 
@@ -109,9 +109,7 @@
 		, config = c
 		, getRepo = return r
 		, gitconfig = gc
-		, localpath = if borgLocal borgrepo && not (null borgrepo)
-			then Just borgrepo
-			else Nothing
+		, localpath = borgRepoLocalPath borgrepo
 		, remotetype = remote
 		, availability = if borgLocal borgrepo then LocallyAvailable else GloballyAvailable
 		, readonly = False
@@ -122,13 +120,16 @@
 		, untrustworthy = maybe True not $
 			getRemoteConfigValue appendonlyField c
 		, mkUnavailable = return Nothing
-		, getInfo = return [("repo", borgrepo)]
+		, getInfo = return [("repo", locBorgRepo borgrepo)]
 		, claimUrl = Nothing
 		, checkUrl = Nothing
 		, remoteStateHandle = rs
 		}
   where
-	borgrepo = fromMaybe (giveup "missing borgrepo") $ remoteAnnexBorgRepo gc
+	borgrepo = maybe
+		(giveup "missing borgrepo")
+		BorgRepo
+		(remoteAnnexBorgRepo gc)
 
 borgSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
 borgSetup _ mu _ c _gc = do
@@ -145,15 +146,26 @@
 	return (c, u)
 
 borgLocal :: BorgRepo -> Bool
-borgLocal = notElem ':'
+borgLocal (BorgRepo r) = notElem ':' r
 
 borgArchive :: BorgRepo -> BorgArchiveName -> String
-borgArchive r n = r ++ "::" ++ decodeBS' n
+borgArchive (BorgRepo r) n = r ++ "::" ++ decodeBS' n
 
+absBorgRepo :: BorgRepo -> IO BorgRepo
+absBorgRepo r@(BorgRepo p)
+	| borgLocal r = BorgRepo . fromRawFilePath
+		<$> absPath (toRawFilePath p)
+	| otherwise = return r
+
+borgRepoLocalPath :: BorgRepo -> Maybe FilePath
+borgRepoLocalPath r@(BorgRepo p)
+	| borgLocal r && not (null p) = Just p
+	| otherwise = Nothing
+
 listImportableContentsM :: UUID -> BorgRepo -> ParsedRemoteConfig -> Annex (Maybe (ImportableContents (ContentIdentifier, ByteSize)))
 listImportableContentsM u borgrepo c = prompt $ do
 	imported <- getImported u
-	ls <- withborglist borgrepo Nothing formatarchivelist $ \as ->
+	ls <- withborglist (locBorgRepo borgrepo) Nothing formatarchivelist $ \as ->
 		forM (filter (not . S.null) as) $ \archivename ->
 			case M.lookup archivename imported of
 				Just getfast -> return $ Left (archivename, getfast)
@@ -329,14 +341,14 @@
 			[ Param "list"
 			, Param "--format"
 			, Param "1"
-			, Param borgrepo
+			, Param (locBorgRepo borgrepo)
 			]
 		(Nothing, Nothing, Nothing, pid) <- withNullHandle $ \nullh ->
 			createProcess $ p
 				{ std_out = UseHandle nullh }
 		ifM (checkSuccessProcess pid)
 			( return False -- repo exists, content not in it
-			, giveup $ "Unable to access borg repository " ++ borgrepo
+			, giveup $ "Unable to access borg repository " ++ locBorgRepo borgrepo
 			)
 
 retrieveExportWithContentIdentifierM :: BorgRepo -> ImportLocation -> ContentIdentifier -> FilePath -> Annex Key -> MeterUpdate -> Annex Key
@@ -345,7 +357,7 @@
 	prompt $ withOtherTmp $ \othertmp -> liftIO $ do
 		-- borgrepo could be relative, and borg has to be run
 		-- in the temp directory to get it to write there
-		absborgrepo <- fromRawFilePath <$> absPath (toRawFilePath borgrepo)
+		absborgrepo <- absBorgRepo borgrepo
 		let p = proc "borg" $ toCommand
 			[ Param "extract"
 			, Param (borgArchive absborgrepo archivename)
diff --git a/Remote/Git.hs b/Remote/Git.hs
--- a/Remote/Git.hs
+++ b/Remote/Git.hs
@@ -269,7 +269,7 @@
 		set_ignore "uses a protocol not supported by git-annex" False
 		return r
 	| otherwise = storeUpdatedRemote $
-		liftIO readlocalannexconfig
+		readlocalannexconfig
 			`catchNonAsync` const failedreadlocalconfig
   where
 	haveconfig = not . M.null . Git.config
@@ -355,8 +355,8 @@
 				warning $ "remote " ++ Git.repoDescribe r ++
 					":"  ++ show e
 			Annex.getState Annex.repo
-		s <- Annex.new r
-		Annex.eval s $ check `finally` stopCoProcesses
+		s <- newLocal r
+		liftIO $ Annex.eval s $ check `finally` stopCoProcesses
 		
 	failedreadlocalconfig = do
 		unless hasuuid $ case Git.remoteName r of
@@ -429,8 +429,8 @@
 	-- If the remote is known to not be bare, try the hash locations
 	-- used for non-bare repos first, as an optimisation.
 	locs
-		| remoteAnnexBare remoteconfig == Just False = reverse (annexLocations gc key)
-		| otherwise = annexLocations gc key
+		| remoteAnnexBare remoteconfig == Just False = annexLocationsNonBare gc key
+		| otherwise = annexLocationsBare gc key
 #ifndef mingw32_HOST_OS
 	locs' = map fromRawFilePath locs
 #else
@@ -773,10 +773,20 @@
 	lra <- mkLocalRemoteAnnex repo
 	onLocal' lra a
 
+newLocal :: Git.Repo -> Annex (Annex.AnnexState, Annex.AnnexRead)
+newLocal repo = do
+	(st, rd) <- liftIO $ Annex.new repo
+	debugenabled <- Annex.getRead Annex.debugenabled
+	debugselector <- Annex.getRead Annex.debugselector
+	return (st,  rd
+		{ Annex.debugenabled = debugenabled
+		, Annex.debugselector = debugselector
+		})
+
 onLocal' :: LocalRemoteAnnex -> Annex a -> Annex a
 onLocal' (LocalRemoteAnnex repo mv) a = liftIO (takeMVar mv) >>= \case
 	Nothing -> do
-		v <- liftIO $ Annex.new repo
+		v <- newLocal repo
 		go (v, ensureInitialized >> a)
 	Just v -> go (v, a)
   where
diff --git a/Remote/Helper/ExportImport.hs b/Remote/Helper/ExportImport.hs
--- a/Remote/Helper/ExportImport.hs
+++ b/Remote/Helper/ExportImport.hs
@@ -13,7 +13,7 @@
 import Types.Remote
 import Types.Key
 import Types.ProposedAccepted
-import Backend
+import Annex.Verify
 import Remote.Helper.Encryptable (encryptionIsEnabled)
 import qualified Database.Export as Export
 import qualified Database.ContentIdentifier as ContentIdentifier
diff --git a/Remote/Helper/P2P.hs b/Remote/Helper/P2P.hs
--- a/Remote/Helper/P2P.hs
+++ b/Remote/Helper/P2P.hs
@@ -17,7 +17,7 @@
 import Messages.Progress
 import Utility.Metered
 import Types.NumCopies
-import Backend
+import Annex.Verify
 
 import Control.Concurrent
 
diff --git a/Test.hs b/Test.hs
--- a/Test.hs
+++ b/Test.hs
@@ -1983,7 +1983,7 @@
 	testimport = do
 		origbranch <- annexeval origBranch
 		git_annex "import" [origbranch++":"++subdir, "--from", "foo"] "import of subdir"
-		git_annex "merge" ["foo/master"] "git annex merge foo/master"
+		git_annex "merge" ["foo/master", "--allow-unrelated-histories"] "git annex merge foo/master"
 
 		-- Make sure that import did not import the file to the top
 		-- of the repo.
diff --git a/Test/Framework.hs b/Test/Framework.hs
--- a/Test/Framework.hs
+++ b/Test/Framework.hs
@@ -488,6 +488,7 @@
 		, ("GIT_ANNEX_USE_GIT_SSH", "1")
 		, ("TESTMODE", show testmode)
 		]
+
 runFakeSsh :: [String] -> IO ()
 runFakeSsh ("-n":ps) = runFakeSsh ps
 runFakeSsh (_host:cmd:[]) =
diff --git a/Upgrade/V5.hs b/Upgrade/V5.hs
--- a/Upgrade/V5.hs
+++ b/Upgrade/V5.hs
@@ -47,7 +47,7 @@
 		, do
 			checkGitVersionForIndirectUpgrade
 		)
-	scanAnnexedFiles True
+	scanAnnexedFiles
 	configureSmudgeFilter
 	-- Inode sentinal file was only used in direct mode and when
 	-- locking down files as they were added. In v6, it's used more
diff --git a/Utility/HumanNumber.hs b/Utility/HumanNumber.hs
--- a/Utility/HumanNumber.hs
+++ b/Utility/HumanNumber.hs
@@ -1,6 +1,6 @@
 {- numbers for humans
  -
- - Copyright 2012-2013 Joey Hess <id@joeyh.name>
+ - Copyright 2012-2021 Joey Hess <id@joeyh.name>
  -
  - License: BSD-2-clause
  -}
@@ -11,11 +11,15 @@
  - of decimal digits. -}
 showImprecise :: RealFrac a => Int -> a -> String
 showImprecise precision n
-	| precision == 0 || remainder == 0 = show (round n :: Integer)
-	| otherwise = show int ++ "." ++ striptrailing0s (pad0s $ show remainder)
+	| precision == 0 || remainder' == 0 = show (round n :: Integer)
+	| otherwise = show int' ++ "." ++ striptrailing0s (pad0s $ show remainder')
   where
 	int :: Integer
 	(int, frac) = properFraction n
 	remainder = round (frac * 10 ^ precision) :: Integer
+	(int', remainder')
+		-- carry the 1
+		| remainder == 10 ^ precision = (int + 1, 0)
+		| otherwise = (int, remainder)
 	pad0s s = replicate (precision - length s) '0' ++ s
 	striptrailing0s = reverse . dropWhile (== '0') . reverse
diff --git a/Utility/Process/Transcript.hs b/Utility/Process/Transcript.hs
--- a/Utility/Process/Transcript.hs
+++ b/Utility/Process/Transcript.hs
@@ -86,7 +86,7 @@
   where
 	asyncreader pid h = async $ reader pid h []
 	reader pid h c = hGetLineUntilExitOrEOF pid h >>= \case
-		Nothing -> return (concat (reverse c))
+		Nothing -> return (unlines (reverse c))
 		Just l -> reader pid h (l:c)
 	writeinput (Just s) p = do
 		let inh = stdinHandle p
diff --git a/doc/git-annex-filter-branch.mdwn b/doc/git-annex-filter-branch.mdwn
new file mode 100644
--- /dev/null
+++ b/doc/git-annex-filter-branch.mdwn
@@ -0,0 +1,151 @@
+# NAME
+
+git-annex filter-branch - filter information from the git-annex branch
+
+# SYNOPSIS
+
+git annex filter-branch [...]
+
+# DESCRIPTION
+
+This copies selected information from the git-annex branch into a git
+commit object, and outputs its hash. The git commit can be transported
+to another git repository, and given a branch name such as "foo/git-annex",
+and git-annex there will automatically merge that into its git-annex
+branch. This allows publishing some information from your git-annex branch,
+without publishing the whole thing.
+
+Other ways to avoid publishing information from a git-annex branch,
+or remove information from it include [[git-annex-forget]](1), the 
+`annex.private` git config, and the `--private` option to
+[[git-annex-initremote](1). Those are much easier to use, but this
+provides full control for those who need it.
+
+With no options, no information at all will be included from the git-annex
+branch. Use options to specify what to include. All options can be specified
+multiple times.
+
+When the repository contains information about a private
+repository (due to `annex.private` being set, or `git-annex initremote
+--private` being used), that private information will be included when
+allowed by the options, even though it is not recorded on the git-annex
+branch.
+
+When a repository was created with `git annex initremote --sameas=foo`,
+its information will be included when the information for foo is,
+and excluded when foo is excluded.
+
+When a special remote is configured with importtree=yes or exporttree=yes,
+normally the git tree corresponding to the repository is included in
+the git-annex branch, to make sure it does not get garbage collected
+by `git gc`. Those trees are *not* included when filtering the git-annex
+branch. Usually this will not cause any problems, but if such a tree does
+get garbage collected, it will prevent accessing files on the special
+remote, until the next time a tree is imported or exported to it.
+
+# OPTIONS
+
+* `path`
+
+  Include information about all keys of annexed files in the path.
+
+* file matching options
+
+  The [[git-annex-matching-options]](1)
+  can be used to specify which files in a path to include.
+
+* `--branch=ref`
+
+  Include information about keys referred of annexed files in the branch
+  or treeish.
+
+* `--key=key`
+
+  Include information about a specific key.
+
+* `--all`
+
+  Include information about all keys.
+
+* `--include-key-information-for=repo`
+
+  When including information about a key, include information specific to
+  this repository. The repository can be specified with a uuid or the name
+  of a remote. This option can be used repeatedly to include several
+  repositories.
+
+* `--include-all-key-information`
+
+  Include key information for all repositories, except any excluded with
+  the `--exclude-key-information-for` option.
+
+* `--exclude-key-information-for=repo`
+
+  When including information about a key, exclude information specific to
+  this repository. The repository can be specified with a uuid or the name
+  of a remote. This option can be used repeatedly to exclude
+  several repositories.
+
+* `--include-repo-config-for=repo`
+
+  Include configuration specific to this repository. 
+  The repository can be specified with a uuid or the name of a remote.
+
+  This includes the configuration of special remotes, which may include
+  embedded credentials, or encryption parameters. It also includes trust
+  settings, preferred content, etc. It does not include information
+  about any git-annex keys. This option can be used repeatedly to include
+  several repositories.
+
+* `--include-all-repo-config`
+
+  Include the configuration of all repositories, except for any excluded
+  with the `--exclude-repo-config-for` option.
+
+* `--exclude-repo-config-for=repo`
+
+  Exclude configuration specific to this repository. 
+  The repository can be specified with a uuid or the name of a remote.
+  This option can be used repeatedly to exclude several repositories.
+
+* `--include-global-config`
+
+  Include global configuration, that is not specific to any repository.
+
+  This includes configs stored by [[git-annex-numcopies]](1),
+  [[git-annex-config]](1), etc.
+
+# EXAMPLES
+
+You have a big git-annex repository and are splitting the directory "foo"
+out, to make a smaller repository. You want the smaller repo's git-annex
+branch to contain all the information about remotes and other configuration,
+but only information about keys in that directory.
+
+	git-annex filter-branch foo --include-all-key-information \
+		--include-all-repo-config --include-global-config
+
+That only includes information about the keys that are currently
+in the directory "foo", not keys used by old versions of files.
+To also include information about the version of the subdir in
+tag "1.0", add the option `--branch=1.0:foo`
+
+Your repository has a special remote "bar", and you want to share information
+about which annexed files are stored in it, but without sharing anything
+about the configuration of the remote.
+
+	git-annex filter-branch --all --include-all-key-information \
+		--include-all-repo-config --exclude-repo-config-for=bar \
+		--include-global-config
+
+# SEE ALSO
+
+[[git-annex]](1)
+
+[[git-annex-forget]](1)
+
+# AUTHOR
+
+Joey Hess <id@joeyh.name>
+
+Warning: Automatically converted into a man page by mdwn2man. Edit with care.
diff --git a/doc/git-annex-import.mdwn b/doc/git-annex-import.mdwn
--- a/doc/git-annex-import.mdwn
+++ b/doc/git-annex-import.mdwn
@@ -42,19 +42,17 @@
 For example:
 
 	git annex import master --from myremote
-	git annex merge myremote/master
+	git annex merge --allow-unrelated-histories myremote/master
 
-You could just as well use `git merge myremote/master` as the second step,
-but using `git-annex merge` avoids a couple of gotchas. When using adjusted
-branches, it adjusts the branch before merging from it. And it avoids
-the merge failing on the first merge from an import due to unrelated
-histories.
+You could just as well use `git merge --allow-unrelated-histories myremote/master`
+as the second step, but using `git-annex merge` avoids a couple of gotchas.
+When using adjusted branches, it adjusts the branch before merging from it.
 
-If you do use `git merge`, you can pass `--allow-unrelated-histories` the
-first time you `git merge` from an import. Think of this as the remote
-being a separate git repository with its own files. If you first
-`git annex export` files to a remote, and then `git annex import` from it,
-you won't need that option.
+The --allow-unrelated-histories option is needed for at least the first
+merge of an imported remote tracking branch, since the branch's history is
+not connected. Think of this as the remote being a separate git repository
+with its own files. If you first `git annex export` files to a remote, and
+then `git annex import` from it, you won't need that option.
 
 You can import into a subdirectory, using the "branch:subdir" syntax. For
 example, if "camera" is a special remote that accesses a camera, and you
diff --git a/doc/git-annex-merge.mdwn b/doc/git-annex-merge.mdwn
--- a/doc/git-annex-merge.mdwn
+++ b/doc/git-annex-merge.mdwn
@@ -22,7 +22,12 @@
 
 # OPTIONS
 
-* The [[git-annex-common-options]](1) can be used.
+* `--allow-unrelated-histories`, `--no-allow-unrelated-histories`
+
+  Passed on to `git merge`, to control whether or not to merge
+  histories that do not share a common ancestor.
+
+* Also, the [[git-annex-common-options]](1) can be used.
 
 # SEE ALSO
 
diff --git a/doc/git-annex-sync.mdwn b/doc/git-annex-sync.mdwn
--- a/doc/git-annex-sync.mdwn
+++ b/doc/git-annex-sync.mdwn
@@ -150,6 +150,11 @@
   less efficient. When --content is synced, the files are processed
   in parallel as well.
 
+* `--allow-unrelated-histories`, `--no-allow-unrelated-histories`
+
+  Passed on to `git merge`, to control whether or not to merge
+  histories that do not share a common ancestor.
+
 * `--resolvemerge`, `--no-resolvemerge`
 
   By default, merge conflicts are automatically handled by sync. When two
diff --git a/doc/git-annex-unused.mdwn b/doc/git-annex-unused.mdwn
--- a/doc/git-annex-unused.mdwn
+++ b/doc/git-annex-unused.mdwn
@@ -83,6 +83,8 @@
 
 [[git-annex-addunused]](1)
 
+[[git-annex-whereused]](1)
+
 # AUTHOR
 
 Joey Hess <id@joeyh.name>
diff --git a/doc/git-annex-whereused.mdwn b/doc/git-annex-whereused.mdwn
new file mode 100644
--- /dev/null
+++ b/doc/git-annex-whereused.mdwn
@@ -0,0 +1,53 @@
+# NAME
+
+git-annex whereused - find what files use or used a key
+
+# SYNOPSIS
+
+git annex whereused `--key=keyname|--unused`
+
+# DESCRIPTION
+
+Finds what files use or used a key.
+
+For each file in the working tree that uses a key, this outputs one line,
+starting with the key, then a space, and then the name of the file.
+When multiple files use the same key, they will all be listed. When
+nothing is found that uses the key, there will be no output.
+
+The default is to find only files in the current working tree that use a
+key. The `--historical` option makes it also find past versions of files.
+
+# OPTIONS
+
+* `--key=keyname`
+
+  Operate on this key.
+
+* `--unused`
+
+  Operate on keys found by last run of git-annex unused.
+
+  Usually these keys won't be used by any files in the current working
+  tree, or any tags or branches. Combining this option with `--historical`
+  will find past uses of the keys.
+
+* `--historical`
+
+  When no files in the current working tree use a key, this causes more
+  work to be done, looking at past versions of the current branch, other
+  branches, tags, and the reflog, to find somewhere that the key was used.
+  It stops after finding one use of the key, and outputs a git rev that
+  refers to where it was used, eg "HEAD@{40}:somefile"
+
+# SEE ALSO
+
+[[git-annex]](1)
+
+[[git-annex-unused]](1)
+
+# AUTHOR
+
+Joey Hess <id@joeyh.name>
+
+Warning: Automatically converted into a man page by mdwn2man. Edit with care.
diff --git a/doc/git-annex.mdwn b/doc/git-annex.mdwn
--- a/doc/git-annex.mdwn
+++ b/doc/git-annex.mdwn
@@ -447,6 +447,10 @@
   
   See [[git-annex-list]](1) for details.
 
+* `whereused`
+
+  Finds what files use or used a key.
+
 * `log [path ...]`
 
   Displays the location log for the specified file or files,
diff --git a/git-annex.cabal b/git-annex.cabal
--- a/git-annex.cabal
+++ b/git-annex.cabal
@@ -1,5 +1,5 @@
 Name: git-annex
-Version: 8.20210714
+Version: 8.20210803
 Cabal-Version: >= 1.10
 License: AGPL-3
 Maintainer: Joey Hess <id@joeyh.name>
@@ -64,6 +64,7 @@
   doc/git-annex-examinekey.mdwn
   doc/git-annex-expire.mdwn
   doc/git-annex-export.mdwn
+  doc/git-annex-filter-branch.mdwn
   doc/git-annex-find.mdwn
   doc/git-annex-findref.mdwn
   doc/git-annex-fix.mdwn
@@ -143,6 +144,7 @@
   doc/git-annex-watch.mdwn
   doc/git-annex-webapp.mdwn
   doc/git-annex-whereis.mdwn
+  doc/git-annex-whereused.mdwn
   doc/git-remote-tor-annex.mdwn
   doc/logo.svg
   doc/logo_16x16.png
@@ -628,6 +630,7 @@
     Annex.Concurrent.Utility
     Annex.Content
     Annex.Content.Presence
+    Annex.Content.Presence.LowLevel
     Annex.Content.LowLevel
     Annex.Content.PointerFile
     Annex.CopyFile
@@ -829,6 +832,7 @@
     Command.View
     Command.Wanted
     Command.Whereis
+    Command.WhereUsed
     Common
     Config
     Config.Cost
