aboutsummaryrefslogtreecommitdiff
path: root/Assistant/Threads/Merger.hs
blob: 5d24d1862bd068d41fca57e61420d981ecdfed5e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
{- git-annex assistant git merge thread
 -
 - Copyright 2012 Joey Hess <joey@kitenet.net>
 -
 - Licensed under the GNU GPL version 3 or higher.
 -}

module Assistant.Threads.Merger where

import Common.Annex
import Assistant.ThreadedMonad
import Utility.DirWatcher
import Utility.Types.DirWatcher
import qualified Annex.Branch
import qualified Git
import qualified Git.Command
import qualified Git.Merge
import qualified Git.Branch
import qualified Command.Sync
import qualified Remote

{- This thread watches for changes to .git/refs/heads/synced/*,
 - which indicate incoming pushes. It merges those pushes into the
 - currently checked out branch. -}
mergeThread :: ThreadState -> IO ()
mergeThread st = do
	g <- runThreadState st $ fromRepo id
	let dir = Git.localGitDir g </> "refs" </> "heads" </> "synced"
	createDirectoryIfMissing True dir
	let hook a = Just $ runHandler g a
	-- XXX: For reasons currently unknown, using the ThreadState
	-- inside the watch hooks leads to a MVar deadlock.
	-- Luckily, we don't currently need to do that.
	let hooks = mkWatchHooks
		{ addHook = hook onAdd
		, errHook = hook onErr
		}
	void $ watchDir dir (const False) hooks id

type Handler = Git.Repo -> FilePath -> Maybe FileStatus -> IO ()

{- Runs an action handler.
 -
 - Exceptions are ignored, otherwise a whole thread could be crashed.
 -}
runHandler :: Git.Repo -> Handler -> FilePath -> Maybe FileStatus -> IO ()
runHandler g handler file filestatus = void $ do
        either print (const noop) =<< tryIO go
        where
                go = handler g file filestatus

{- Called when there's an error with inotify. -}
onErr :: Handler
onErr _ msg _ = error msg

{- Called when a new branch ref is written.
 -
 - This relies on git's atomic method of updating branch ref files,
 - which is to first write the new file to .lock, and then rename it
 - over the old file. So, ignore .lock files, and the rename ensures
 - the watcher sees a new file being added on each update.
 -
 - At startup, synthetic add events fire, causing this to run, but that's
 - ok; it ensures that any changes pushed since the last time the assistant
 - ran are merged in.
 -}
onAdd :: Handler
onAdd g file _
	| ".lock" `isSuffixOf` file = noop
	| otherwise = do
		let changedbranch = Git.Ref $
			"refs" </> "heads" </> takeFileName file
		current <- Git.Branch.current g
		when (Just changedbranch == current) $
			void $ mergeBranch changedbranch g

mergeBranch :: Git.Ref -> Git.Repo -> IO Bool
mergeBranch = Git.Merge.mergeNonInteractive . Command.Sync.syncBranch

{- Manually pull from remotes and merge their branches. Called by the pusher
 - when a push fails, which can happen due to a remote not having pushed
 - changes to us. That could be because it doesn't have us as a remote, or
 - because the assistant is not running there, or other reasons. -}
manualPull :: Git.Ref -> [Remote] -> Annex ()
manualPull currentbranch remotes = do
	forM_ remotes $ \r ->
		inRepo $ Git.Command.runBool "fetch" [Param $ Remote.name r]
	Annex.Branch.forceUpdate
	forM_ remotes $ \r ->
		Command.Sync.mergeRemote r currentbranch