2 {-# LANGUAGE LambdaCase #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 {-# LANGUAGE RankNTypes #-}
5 {-# LANGUAGE RecordWildCards #-}
6 {-# LANGUAGE ScopedTypeVariables #-}
8 module Distribution
.Client
.VCS
19 -- * Validating 'SourceRepo's and configuring VCS drivers
20 , validatePDSourceRepo
23 , SourceRepoProblem
(..)
27 -- * Running the VCS driver
31 -- * The individual VCS drivers
41 import Distribution
.Client
.Compat
.Prelude
44 import Distribution
.Client
.RebuildMonad
47 , monitorDirectoryExistence
50 import Distribution
.Client
.Types
.SourceRepo
(SourceRepoMaybe
, SourceRepositoryPackage
(..), srpToProxy
)
51 import qualified Distribution
.PackageDescription
as PD
52 import Distribution
.Simple
.Program
53 ( ConfiguredProgram
(programVersion
)
54 , Program
(programFindVersion
)
55 , ProgramInvocation
(..)
58 , getProgramInvocationOutput
61 , runProgramInvocation
64 import Distribution
.Simple
.Program
.Db
65 ( prependProgramSearchPath
67 import Distribution
.System
71 import Distribution
.Types
.SourceRepo
75 import Distribution
.Verbosity
as Verbosity
78 import Distribution
.Version
82 #if !MIN_VERSION_base
(4,18,0)
83 import Control
.Applicative
87 import Control
.Exception
91 import Control
.Monad
.Trans
94 import qualified Data
.Char as Char
95 import qualified Data
.List
as List
96 import qualified Data
.Map
as Map
97 import System
.Directory
99 , removeDirectoryRecursive
102 import System
.FilePath
106 import System
.IO.Error
107 ( isDoesNotExistError
110 import qualified System
.Process
as Process
112 -- | A driver for a version control system, e.g. git, darcs etc.
113 data VCS program
= VCS
114 { vcsRepoType
:: RepoType
115 -- ^ The type of repository this driver is for.
116 , vcsProgram
:: program
117 -- ^ The vcs program itself.
118 -- This is used at type 'Program' and 'ConfiguredProgram'.
123 -> SourceRepositoryPackage f
124 -> FilePath -- Source URI
125 -> FilePath -- Destination directory
126 -> [ProgramInvocation
]
127 -- ^ The program invocation(s) to get\/clone a repository into a fresh
133 -> [(SourceRepositoryPackage f
, FilePath)]
134 -> IO [MonitorFilePath
]
135 -- ^ The program invocation(s) to synchronise a whole set of /related/
136 -- repositories with corresponding local directories. Also returns the
137 -- files that the command depends on, for change monitoring.
140 -- ------------------------------------------------------------
142 -- * Selecting repos and drivers
144 -- ------------------------------------------------------------
146 data SourceRepoProblem
147 = SourceRepoRepoTypeUnspecified
148 | SourceRepoRepoTypeUnsupported
(SourceRepositoryPackage Proxy
) RepoType
149 | SourceRepoLocationUnspecified
152 -- | Validates that the 'SourceRepo' specifies a location URI and a repository
153 -- type that is supported by a VCS driver.
155 -- | It also returns the 'VCS' driver we should use to work with it.
157 :: SourceRepositoryPackage f
158 -> Either SourceRepoProblem
(SourceRepositoryPackage f
, String, RepoType
, VCS Program
)
159 validateSourceRepo
= \repo
-> do
160 let rtype
= srpType repo
161 vcs
<- Map
.lookup rtype knownVCSs ?
! SourceRepoRepoTypeUnsupported
(srpToProxy repo
) rtype
162 let uri
= srpLocation repo
163 return (repo
, uri
, rtype
, vcs
)
165 a ?
! e
= maybe (Left e
) Right a
169 -> Either SourceRepoProblem
(SourceRepoMaybe
, String, RepoType
, VCS Program
)
170 validatePDSourceRepo repo
= do
171 rtype
<- PD
.repoType repo ?
! SourceRepoRepoTypeUnspecified
172 uri
<- PD
.repoLocation repo ?
! SourceRepoLocationUnspecified
174 SourceRepositoryPackage
177 , srpTag
= PD
.repoTag repo
178 , srpBranch
= PD
.repoBranch repo
179 , srpSubdir
= PD
.repoSubdir repo
180 , srpCommand
= mempty
183 a ?
! e
= maybe (Left e
) Right a
185 -- | As 'validateSourceRepo' but for a bunch of 'SourceRepo's, and return
186 -- things in a convenient form to pass to 'configureVCSs', or to report
189 :: [SourceRepositoryPackage f
]
191 [(SourceRepositoryPackage f
, SourceRepoProblem
)]
192 [(SourceRepositoryPackage f
, String, RepoType
, VCS Program
)]
193 validateSourceRepos rs
=
194 case partitionEithers
(map validateSourceRepo
' rs
) of
195 (problems
@(_
: _
), _
) -> Left problems
196 ([], vcss
) -> Right vcss
199 :: SourceRepositoryPackage f
201 (SourceRepositoryPackage f
, SourceRepoProblem
)
202 (SourceRepositoryPackage f
, String, RepoType
, VCS Program
)
203 validateSourceRepo
' r
=
207 (validateSourceRepo r
)
212 -- ^ Extra prog paths
214 -> IO (VCS ConfiguredProgram
)
215 configureVCS verbosity progPaths vcs
@VCS
{vcsProgram
= prog
} = do
216 progPath
<- prependProgramSearchPath verbosity progPaths
[] emptyProgramDb
217 asVcsConfigured
<$> requireProgram verbosity prog progPath
219 asVcsConfigured
(prog
', _
) = vcs
{vcsProgram
= prog
'}
224 -- ^ Extra prog paths
225 -> Map RepoType
(VCS Program
)
226 -> IO (Map RepoType
(VCS ConfiguredProgram
))
227 configureVCSs verbosity progPaths
= traverse
(configureVCS verbosity progPaths
)
229 -- ------------------------------------------------------------
231 -- * Running the driver
233 -- ------------------------------------------------------------
235 -- | Clone a single source repo into a fresh directory, using a configured VCS.
237 -- This is for making a new copy, not synchronising an existing copy. It will
238 -- fail if the destination directory already exists.
240 -- Make sure to validate the 'SourceRepo' using 'validateSourceRepo' first.
243 -> VCS ConfiguredProgram
244 -> SourceRepositoryPackage f
250 repo
@SourceRepositoryPackage
{srpLocation
= srcuri
}
252 traverse_
(runProgramInvocation verbosity
) invocations
263 -- | Synchronise a set of 'SourceRepo's referring to the same repository with
264 -- corresponding local directories. The local directories may or may not
267 -- The 'SourceRepo' values used in a single invocation of 'syncSourceRepos',
268 -- or used across a series of invocations with any local directory must refer
269 -- to the /same/ repository. That means it must be the same location but they
270 -- can differ in the branch, or tag or subdir.
272 -- The reason to allow multiple related 'SourceRepo's is to allow for the
273 -- network or storage to be shared between different checkouts of the repo.
274 -- For example if a single repo contains multiple packages in different subdirs
275 -- and in some project it may make sense to use a different state of the repo
276 -- for one subdir compared to another.
279 -> VCS ConfiguredProgram
280 -> [(SourceRepositoryPackage f
, FilePath)]
282 syncSourceRepos verbosity vcs repos
= do
283 files
<- liftIO
$ vcsSyncRepos vcs verbosity
(vcsProgram vcs
) repos
286 -- ------------------------------------------------------------
288 -- * The various VCS drivers
290 -- ------------------------------------------------------------
292 -- | The set of all supported VCS drivers, organised by 'RepoType'.
293 knownVCSs
:: Map RepoType
(VCS Program
)
294 knownVCSs
= Map
.fromList
[(vcsRepoType vcs
, vcs
) | vcs
<- vcss
]
296 vcss
= [vcsBzr
, vcsDarcs
, vcsGit
, vcsHg
, vcsSvn
]
298 -- | VCS driver for Bazaar.
299 vcsBzr
:: VCS Program
302 { vcsRepoType
= KnownRepoType Bazaar
303 , vcsProgram
= bzrProgram
311 -> SourceRepositoryPackage f
314 -> [ProgramInvocation
]
315 vcsCloneRepo verbosity prog repo srcuri destdir
=
318 ([branchCmd
, srcuri
, destdir
] ++ tagArgs
++ verboseArg
)
321 -- The @get@ command was deprecated in version 2.4 in favour of
322 -- the alias @branch@
324 | programVersion prog
>= Just
(mkVersion
[2, 4]) =
329 tagArgs
= case srpTag repo
of
331 Just tag
-> ["-r", "tag:" ++ tag
]
332 verboseArg
:: [String]
333 verboseArg
= ["--quiet" | verbosity
< Verbosity
.normal
]
338 -> [(SourceRepositoryPackage f
, FilePath)]
339 -> IO [MonitorFilePath
]
340 vcsSyncRepos _v _p _rs
= fail "sync repo not yet supported for bzr"
342 bzrProgram
:: Program
344 (simpleProgram
"bzr")
345 { programFindVersion
= findProgramVersion
"--version" $ \str
->
347 -- "Bazaar (bzr) 2.6.0\n ... lots of extra stuff"
348 (_
: _
: ver
: _
) -> ver
352 -- | VCS driver for Darcs.
353 vcsDarcs
:: VCS Program
356 { vcsRepoType
= KnownRepoType Darcs
357 , vcsProgram
= darcsProgram
365 -> SourceRepositoryPackage f
368 -> [ProgramInvocation
]
369 vcsCloneRepo verbosity prog repo srcuri destdir
=
370 [programInvocation prog cloneArgs
]
372 cloneArgs
:: [String]
373 cloneArgs
= [cloneCmd
, srcuri
, destdir
] ++ tagArgs
++ verboseArg
374 -- At some point the @clone@ command was introduced as an alias for
375 -- @get@, and @clone@ seems to be the recommended one now.
378 | programVersion prog
>= Just
(mkVersion
[2, 8]) =
382 tagArgs
= case srpTag repo
of
384 Just tag
-> ["-t", tag
]
385 verboseArg
:: [String]
386 verboseArg
= ["--quiet" | verbosity
< Verbosity
.normal
]
391 -> [(SourceRepositoryPackage f
, FilePath)]
392 -> IO [MonitorFilePath
]
393 vcsSyncRepos _ _
[] = return []
394 vcsSyncRepos verbosity prog
((primaryRepo
, primaryLocalDir
) : secondaryRepos
) =
396 vcsSyncRepo verbosity prog primaryRepo primaryLocalDir Nothing
397 for_ secondaryRepos
$ \(repo
, localDir
) ->
398 vcsSyncRepo verbosity prog repo localDir
$ Just primaryLocalDir
401 dirs
= primaryLocalDir
: (snd <$> secondaryRepos
)
402 monitors
:: [MonitorFilePath
]
403 monitors
= monitorDirectoryExistence
<$> dirs
405 vcsSyncRepo verbosity prog SourceRepositoryPackage
{..} localDir _peer
=
406 try (lines <$> darcsWithOutput localDir
["log", "--last", "1"]) >>= \case
407 Right
(_
: _
: _
: x
: _
)
408 | Just tag
<- (List
.stripPrefix
"tagged " . List
.dropWhile Char.isSpace) x
409 , Just tag
' <- srpTag
412 Left e |
not (isDoesNotExistError e
) -> throw e
414 removeDirectoryRecursive localDir `
catch` liftA2
unless isDoesNotExistError throw
415 darcs
(takeDirectory localDir
) cloneArgs
417 darcs
:: FilePath -> [String] -> IO ()
418 darcs
= darcs
' runProgramInvocation
420 darcsWithOutput
:: FilePath -> [String] -> IO String
421 darcsWithOutput
= darcs
' getProgramInvocationOutput
423 darcs
' :: (Verbosity
-> ProgramInvocation
-> t
) -> FilePath -> [String] -> t
427 (programInvocation prog args
)
428 { progInvokeCwd
= Just cwd
431 cloneArgs
:: [String]
432 cloneArgs
= ["clone"] ++ tagArgs
++ [srpLocation
, localDir
] ++ verboseArg
434 tagArgs
= case srpTag
of
436 Just tag
-> ["-t" ++ tag
]
437 verboseArg
:: [String]
438 verboseArg
= ["--quiet" | verbosity
< Verbosity
.normal
]
440 darcsProgram
:: Program
442 (simpleProgram
"darcs")
443 { programFindVersion
= findProgramVersion
"--version" $ \str
->
450 -- | VCS driver for Git.
451 vcsGit
:: VCS Program
454 { vcsRepoType
= KnownRepoType Git
455 , vcsProgram
= gitProgram
463 -> SourceRepositoryPackage f
466 -> [ProgramInvocation
]
467 vcsCloneRepo verbosity prog repo srcuri destdir
=
468 [programInvocation prog cloneArgs
]
469 -- And if there's a tag, we have to do that in a second step:
470 ++ [git
(resetArgs tag
) | tag
<- maybeToList (srpTag repo
)]
471 ++ [ git
(["submodule", "sync", "--recursive"] ++ verboseArg
)
472 , git
(["submodule", "update", "--init", "--force", "--recursive"] ++ verboseArg
)
475 git args
= (programInvocation prog args
){progInvokeCwd
= Just destdir
}
477 ["clone", srcuri
, destdir
]
480 branchArgs
= case srpBranch repo
of
481 Just b
-> ["--branch", b
]
483 resetArgs tag
= "reset" : verboseArg
++ ["--hard", tag
, "--"]
484 verboseArg
= ["--quiet" | verbosity
< Verbosity
.normal
]
486 -- Note: No --depth=1 for vcsCloneRepo since that is used for `cabal get -s`,
487 -- whereas `vcsSyncRepo` is used for source-repository-package where we do want shallow clones.
492 -> [(SourceRepositoryPackage f
, FilePath)]
493 -> IO [MonitorFilePath
]
494 vcsSyncRepos _ _
[] = return []
498 ((primaryRepo
, primaryLocalDir
) : secondaryRepos
) = do
499 vcsSyncRepo verbosity gitProg primaryRepo primaryLocalDir Nothing
501 [ vcsSyncRepo verbosity gitProg repo localDir
(Just primaryLocalDir
)
502 |
(repo
, localDir
) <- secondaryRepos
505 [ monitorDirectoryExistence dir
506 | dir
<- (primaryLocalDir
: map snd secondaryRepos
)
509 vcsSyncRepo verbosity gitProg SourceRepositoryPackage
{..} localDir peer
= do
510 exists
<- doesDirectoryExist localDir
512 then git localDir
["fetch"]
513 else git
(takeDirectory localDir
) cloneArgs
514 -- Before trying to checkout other commits, all submodules must be
515 -- de-initialised and the .git/modules directory must be deleted. This
516 -- is needed because sometimes `git submodule sync` does not actually
517 -- update the submodule source URL. Detailed description here:
518 -- https://git.coop/-/snippets/85
519 git localDir
["submodule", "deinit", "--force", "--all"]
520 let gitModulesDir
= localDir
</> ".git" </> "modules"
521 gitModulesExists
<- doesDirectoryExist gitModulesDir
522 when gitModulesExists
$
523 if buildOS
== Windows
525 -- Windows can't delete some git files #10182
527 Process
.createProcess_
"attrib" $
529 "attrib -s -h -r " <> gitModulesDir
<> "\\*.* /s /d"
532 (removePathForcibly gitModulesDir
)
533 (\e
-> if isPermissionError e
then removePathForcibly gitModulesDir
else throw e
)
534 else removeDirectoryRecursive gitModulesDir
535 when (resetTarget
/= "HEAD") $ do
536 git localDir fetchArgs
-- first fetch the tag if needed
537 git localDir setTagArgs
538 git localDir resetArgs
-- only then reset to the commit
539 git localDir
$ ["submodule", "sync", "--recursive"] ++ verboseArg
540 git localDir
$ ["submodule", "update", "--force", "--init", "--recursive"] ++ verboseArg
541 git localDir
$ ["submodule", "foreach", "--recursive"] ++ verboseArg
++ ["git clean -ffxdq"]
542 git localDir
$ ["clean", "-ffxdq"]
544 git
:: FilePath -> [String] -> IO ()
546 runProgramInvocation verbosity
$
547 (programInvocation gitProg args
)
548 { progInvokeCwd
= Just cwd
552 ["clone", "--depth=1", "--no-checkout", loc
, localDir
]
555 Just peerLocalDir
-> ["--reference", peerLocalDir
]
559 -- To checkout/reset to a particular commit, we must first fetch it
560 -- (since the base clone is shallow).
561 fetchArgs
= "fetch" : verboseArg
++ ["origin", resetTarget
]
562 -- And then create the Tag from the FETCH_HEAD (which we should have just fetched)
563 setTagArgs
= ["tag", "-f", resetTarget
, "FETCH_HEAD"]
564 -- Then resetting to that tag will work (if we don't create the tag
565 -- locally from FETCH_HEAD, it won't exist).
566 resetArgs
= "reset" : verboseArg
++ ["--hard", resetTarget
, "--"]
567 resetTarget
= fromMaybe "HEAD" (srpBranch `mplus` srpTag
)
568 verboseArg
= ["--quiet" | verbosity
< Verbosity
.normal
]
570 gitProgram
:: Program
572 (simpleProgram
"git")
573 { programFindVersion
= findProgramVersion
"--version" $ \str
->
575 -- "git version 2.5.5"
576 (_
: _
: ver
: _
) |
all isTypical ver
-> ver
577 -- or annoyingly "git version 2.17.1.windows.2" yes, really
580 . takeWhile (all isNum
)
586 isNum c
= c
>= '0' && c
<= '9'
587 isTypical c
= isNum c || c
== '.'
588 split cs
= case break (== '.') cs
of
589 (chunk
, []) -> chunk
: []
590 (chunk
, _
: rest
) -> chunk
: split rest
592 -- | VCS driver for Mercurial.
596 { vcsRepoType
= KnownRepoType Mercurial
597 , vcsProgram
= hgProgram
605 -> SourceRepositoryPackage f
608 -> [ProgramInvocation
]
609 vcsCloneRepo verbosity prog repo srcuri destdir
=
610 [programInvocation prog cloneArgs
]
613 ["clone", srcuri
, destdir
]
617 branchArgs
= case srpBranch repo
of
618 Just b
-> ["--branch", b
]
620 tagArgs
= case srpTag repo
of
621 Just t
-> ["--rev", t
]
623 verboseArg
= ["--quiet" | verbosity
< Verbosity
.normal
]
628 -> [(SourceRepositoryPackage f
, FilePath)]
629 -> IO [MonitorFilePath
]
630 vcsSyncRepos _ _
[] = return []
634 ((primaryRepo
, primaryLocalDir
) : secondaryRepos
) = do
635 vcsSyncRepo verbosity hgProg primaryRepo primaryLocalDir
637 [ vcsSyncRepo verbosity hgProg repo localDir
638 |
(repo
, localDir
) <- secondaryRepos
641 [ monitorDirectoryExistence dir
642 | dir
<- (primaryLocalDir
: map snd secondaryRepos
)
644 vcsSyncRepo verbosity hgProg repo localDir
= do
645 exists
<- doesDirectoryExist localDir
647 then hg localDir
["pull"]
648 else hg
(takeDirectory localDir
) cloneArgs
649 hg localDir checkoutArgs
651 hg
:: FilePath -> [String] -> IO ()
653 runProgramInvocation verbosity
$
654 (programInvocation hgProg args
)
655 { progInvokeCwd
= Just cwd
658 ["clone", "--noupdate", (srpLocation repo
), localDir
]
660 verboseArg
= ["--quiet" | verbosity
< Verbosity
.normal
]
662 ["checkout", "--clean"]
664 tagArgs
= case srpTag repo
of
665 Just t
-> ["--rev", t
]
671 { programFindVersion
= findProgramVersion
"--version" $ \str
->
673 -- Mercurial Distributed SCM (version 3.5.2)\n ... long message
674 (_
: _
: _
: _
: ver
: _
) -> takeWhile (\c
-> Char.isDigit c || c
== '.') ver
678 -- | VCS driver for Subversion.
679 vcsSvn
:: VCS Program
682 { vcsRepoType
= KnownRepoType SVN
683 , vcsProgram
= svnProgram
691 -> SourceRepositoryPackage f
694 -> [ProgramInvocation
]
695 vcsCloneRepo verbosity prog _repo srcuri destdir
=
696 [programInvocation prog checkoutArgs
]
698 checkoutArgs
= ["checkout", srcuri
, destdir
] ++ verboseArg
699 verboseArg
= ["--quiet" | verbosity
< Verbosity
.normal
]
700 -- TODO: branch or tag?
705 -> [(SourceRepositoryPackage f
, FilePath)]
706 -> IO [MonitorFilePath
]
707 vcsSyncRepos _v _p _rs
= fail "sync repo not yet supported for svn"
709 svnProgram
:: Program
711 (simpleProgram
"svn")
712 { programFindVersion
= findProgramVersion
"--version" $ \str
->
714 -- svn, version 1.9.4 (r1740329)\n ... long message
715 (_
: _
: ver
: _
) -> ver
719 -- | VCS driver for Pijul.
720 -- Documentation for Pijul can be found at <https://pijul.org/manual/introduction.html>
724 -- As far as I understand pijul, there are branches and "tags" in pijul,
725 -- but there aren't a "commit hash" identifying an arbitrary state.
727 -- One can create `a pijul tag`, which will make a patch hash,
728 -- which depends on everything currently in the repository.
729 -- I guess if you try to apply that patch, you'll be forced to apply
730 -- all the dependencies too. In other words, there are no named tags.
732 -- It's not clear to me whether there is an option to
733 -- "apply this patch *and* all of its dependencies".
734 -- And relatedly, whether how to make sure that there are no other
737 -- With branches it's easier, as you can `pull` and `checkout` them,
738 -- and they seem to be similar enough. Yet, pijul documentations says
740 -- > Note that the purpose of branches in Pijul is quite different from Git,
741 -- since Git's "feature branches" can usually be implemented by just
744 -- I guess it means that indeed instead of creating a branch and making PR
745 -- in "GitHub" workflow, you'd just create a patch and offer it.
746 -- You can do that with `git` too. Push (a branch with) commit to remote
747 -- and ask other to cherry-pick that commit. Yet, in git identity of commit
748 -- changes when it applied to other trees, where patches in pijul have
749 -- will continue to have the same hash.
751 -- Unfortunately pijul doesn't talk about conflict resolution.
752 -- It seems that you get something like:
757 -- Unresolved conflicts:
758 -- (fix conflicts and record the resolution with "pijul record ...")
764 -- >> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>
766 -- ================================
768 -- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
771 -- And then the `pijul dependencies` would draw you a graph like
774 -- -----> foo on branch B ----->
775 -- resolve conflict Initial patch
776 -- -----> foo on branch A ----->
778 -- Which is seems reasonable.
780 -- So currently, pijul support is very experimental, and most likely
781 -- won't work, even the basics are in place. Tests are also written
782 -- but disabled, as the branching model differs from `git` one,
783 -- for which tests are written.
784 vcsPijul
:: VCS Program
787 { vcsRepoType
= KnownRepoType Pijul
788 , vcsProgram
= pijulProgram
795 -- \^ it seems that pijul does not have verbose flag
797 -> SourceRepositoryPackage f
800 -> [ProgramInvocation
]
801 vcsCloneRepo _verbosity prog repo srcuri destdir
=
802 [programInvocation prog cloneArgs
]
803 -- And if there's a tag, we have to do that in a second step:
804 ++ [ (programInvocation prog
(checkoutArgs tag
))
805 { progInvokeCwd
= Just destdir
807 | tag
<- maybeToList (srpTag repo
)
810 cloneArgs
:: [String]
812 ["clone", srcuri
, destdir
]
814 branchArgs
:: [String]
815 branchArgs
= case srpBranch repo
of
816 Just b
-> ["--from-branch", b
]
818 checkoutArgs tag
= "checkout" : [tag
] -- TODO: this probably doesn't work either
822 -> [(SourceRepositoryPackage f
, FilePath)]
823 -> IO [MonitorFilePath
]
824 vcsSyncRepos _ _
[] = return []
828 ((primaryRepo
, primaryLocalDir
) : secondaryRepos
) = do
829 vcsSyncRepo verbosity pijulProg primaryRepo primaryLocalDir Nothing
831 [ vcsSyncRepo verbosity pijulProg repo localDir
(Just primaryLocalDir
)
832 |
(repo
, localDir
) <- secondaryRepos
835 [ monitorDirectoryExistence dir
836 | dir
<- (primaryLocalDir
: map snd secondaryRepos
)
839 vcsSyncRepo verbosity pijulProg SourceRepositoryPackage
{..} localDir peer
= do
840 exists
<- doesDirectoryExist localDir
842 then pijul localDir
["pull"] -- TODO: this probably doesn't work.
843 else pijul
(takeDirectory localDir
) cloneArgs
844 pijul localDir checkoutArgs
846 pijul
:: FilePath -> [String] -> IO ()
848 runProgramInvocation verbosity
$
849 (programInvocation pijulProg args
)
850 { progInvokeCwd
= Just cwd
853 cloneArgs
:: [String]
855 ["clone", loc
, localDir
]
858 Just peerLocalDir
-> [peerLocalDir
]
861 checkoutArgs
:: [String]
862 checkoutArgs
= "checkout" : ["--force", checkoutTarget
, "--"]
863 checkoutTarget
= fromMaybe "HEAD" (srpBranch `mplus` srpTag
) -- TODO: this is definitely wrong.
865 pijulProgram
:: Program
867 (simpleProgram
"pijul")
868 { programFindVersion
= findProgramVersion
"--version" $ \str
->
871 (_
: ver
: _
) |
all isTypical ver
-> ver
875 isNum c
= c
>= '0' && c
<= '9'
876 isTypical c
= isNum c || c
== '.'