diff --git a/src/Vervis/Client.hs b/src/Vervis/Client.hs index 9d48d22..6acfe57 100644 --- a/src/Vervis/Client.hs +++ b/src/Vervis/Client.hs @@ -30,6 +30,7 @@ module Vervis.Client --, unresolve offerPatches , offerMerge + , applyPatches , createDeck , createLoom , createRepo @@ -74,6 +75,7 @@ import Data.Either.Local import Database.Persist.Local import Vervis.ActivityPub +import Vervis.Cloth import Vervis.Data.Ticket import Vervis.FedURI import Vervis.Foundation @@ -722,6 +724,106 @@ offerMerge senderHash title desc uTracker uTargetRepo maybeTargetBranch uOriginR return (Nothing, AP.Audience recips [] [] [] [] [], ticket) +applyPatches + :: KeyHashid Person + -> FedURI + -> ExceptT Text Handler (Maybe HTML, Audience URIMode, Apply URIMode) +applyPatches senderHash uObject = do + + bundle <- parseProposalBundle "Apply object" uObject + mrInfo <- + bifor bundle + (\ (loomID, clothID, _) -> do + maybeCloth <- lift $ runDB $ getCloth loomID clothID + (Entity _ loom, Entity _ cloth, _, _, _, _) <- + fromMaybeE maybeCloth "Local bundle not found in DB" + return (loomID, clothID, loomRepo loom, ticketLoomBranch cloth) + ) + (\ uBundle -> do + manager <- asksSite appHttpManager + Doc h b <- AP.fetchAP_T manager $ Left uBundle + let mlocal = + case b of + BundleHosted ml _ -> (h,) <$> ml + BundleOffer ml _ -> ml + (hBundle, blocal) <- + fromMaybeE mlocal "Remote bundle doesn't have 'context'" + unless (hBundle == h) $ + throwE "Bundle @id mismatch!" + + Doc _ ticket <- + AP.fetchAP_T manager $ + Left $ ObjURI hBundle $ AP.bundleContext blocal + (hMR, mr) <- fromMaybeE (AP.ticketAttachment ticket) "Ticket doesn't have attachment" + (hT, tlocal) <- fromMaybeE (AP.ticketLocal ticket) "Ticket doesn't have followers" + unless (hT == hBundle) $ + throwE "Ticket @id mismatch!" + uContext@(ObjURI hC _) <- fromMaybeE (AP.ticketContext ticket) "Ticket doesn't have context" + unless (hC == hT) $ + throwE "Ticket and tracker on different instances" + + Doc hC' (AP.Actor aloc adet) <- AP.fetchAP_T manager $ Left uContext + unless (hC' == hC) $ + throwE "Tracker @id mismatch!" + unless (AP.actorType adet == AP.ActorTypePatchTracker) $ + throwE "Ticket context isn't a PatchTracker" + return + ( uContext + , AP.actorFollowers aloc + , AP.ticketParticipants tlocal + , bimap (ObjURI hMR) (hMR,) $ AP.mrTarget mr + ) + ) + + encodeRouteLocal <- getEncodeRouteLocal + encodeRouteHome <- getEncodeRouteHome + hashRepo <- getEncodeKeyHashid + hashLoom <- getEncodeKeyHashid + hashCloth <- getEncodeKeyHashid + hLocal <- asksSite siteInstanceHost + + let target = + case mrInfo of + Left (_, _, repoID, maybeBranch) -> + let luRepo = encodeRouteLocal $ RepoR $ hashRepo repoID + in case maybeBranch of + Nothing -> Left $ ObjURI hLocal luRepo + Just b -> + Right + ( hLocal + , AP.Branch + { AP.branchName = b + , AP.branchRef = "/refs/heads/" <> b + , AP.branchRepo = luRepo + } + ) + Right (_, _, _, remoteTarget) -> remoteTarget + + audAuthor = + AudLocal + [] + [LocalStagePersonFollowers senderHash] + audCloth = + case mrInfo of + Left (loomID, clothID, _, _) -> + let loomHash = hashLoom loomID + clothHash = hashCloth clothID + in AudLocal + [LocalActorLoom loomHash] + [ LocalStageLoomFollowers loomHash + , LocalStageClothFollowers loomHash clothHash + ] + Right (ObjURI h luTracker, mluFollowers, luTicketFollowers, _) -> + AudRemote h + [luTracker] + (catMaybes [mluFollowers, Just luTicketFollowers]) + + (_, _, _, audLocal, audRemote) = collectAudience [audAuthor, audCloth] + + recips = map encodeRouteHome audLocal ++ audRemote + + return (Nothing, Audience recips [] [] [] [] [], Apply uObject target) + createDeck :: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App) => KeyHashid Person diff --git a/src/Vervis/Darcs.hs b/src/Vervis/Darcs.hs index b7a783e..8b78366 100644 --- a/src/Vervis/Darcs.hs +++ b/src/Vervis/Darcs.hs @@ -21,6 +21,7 @@ module Vervis.Darcs --, lastChange , readPatch , writePostApplyHooks + , canApplyDarcsPatch , applyDarcsPatch ) where @@ -399,6 +400,11 @@ writePostApplyHooks = do liftIO $ writeDefaultsFile path hook authority (keyHashidText repoHash) +canApplyDarcsPatch repoPath patch = do + let input = BL.fromStrict $ TE.encodeUtf8 patch + exitCode <- runProcess $ setStdin (byteStringInput input) $ proc "darcs" ["apply", "--all", "--no-allow-conflicts", "--dry-run", "--repodir='" ++ repoPath ++ "'"] + return $ exitCode == ExitSuccess + applyDarcsPatch repoPath patch = do let input = BL.fromStrict $ TE.encodeUtf8 patch runProcessE "darcs apply" $ setStdin (byteStringInput input) $ proc "darcs" ["apply", "--all", "--no-allow-conflicts", "--repodir='" ++ repoPath ++ "'"] diff --git a/src/Vervis/Git.hs b/src/Vervis/Git.hs index e8f5ae0..f7a9ba0 100644 --- a/src/Vervis/Git.hs +++ b/src/Vervis/Git.hs @@ -22,6 +22,7 @@ module Vervis.Git --, lastCommitTime , writePostReceiveHooks , generateGitPatches + , canApplyGitPatches , applyGitPatches ) where @@ -54,6 +55,7 @@ import Data.Time.Clock.POSIX (posixSecondsToUTCTime) import Data.Traversable (for) import Data.Word (Word32) import Database.Persist +import System.Exit import System.FilePath import System.Hourglass (timeCurrent) import System.Process.Typed @@ -386,12 +388,20 @@ generateGitPatches targetRepoPath targetBranch originRepoURI originBranch tempDi ] Right t -> return t +canApplyGitPatches repoPath branch patches tempDir = do + runProcessE "git clone" $ proc "git" ["clone", "--verbose", "--single-branch", "--branch", branch, "--", repoPath, tempDir] + runProcessE "git config" $ proc "git" ["-C", tempDir, "config", "user.name", "vervis"] + runProcessE "git config" $ proc "git" ["-C", tempDir, "config", "user.email", "vervis@vervis.vervis"] + let input = BL.concat $ NE.toList $ NE.map (BL.fromStrict . TE.encodeUtf8) patches + exitCode <- lift $ runProcess $ setStdin (byteStringInput input) $ proc "git" ["-C", tempDir, "am"] + return $ exitCode == ExitSuccess + -- Since 'git am' doesn't work on a bare repo, clone target repo into the given -- temporary directory, apply there, and finally push applyGitPatches repoPath branch patches tempDir = do runProcessE "git clone" $ proc "git" ["clone", "--verbose", "--single-branch", "--branch", branch, "--", repoPath, tempDir] - let input = BL.concat $ NE.toList $ NE.map (BL.fromStrict . TE.encodeUtf8) patches - runProcessE "git am" $ setStdin (byteStringInput input) $ proc "git" ["-C", tempDir, "am"] runProcessE "git config" $ proc "git" ["-C", tempDir, "config", "user.name", "vervis"] runProcessE "git config" $ proc "git" ["-C", tempDir, "config", "user.email", "vervis@vervis.vervis"] + let input = BL.concat $ NE.toList $ NE.map (BL.fromStrict . TE.encodeUtf8) patches + runProcessE "git am" $ setStdin (byteStringInput input) $ proc "git" ["-C", tempDir, "am"] runProcessE "git push" $ proc "git" ["-C", tempDir, "push"] diff --git a/src/Vervis/Handler/Client.hs b/src/Vervis/Handler/Client.hs index ef7ccf0..5b6e4a0 100644 --- a/src/Vervis/Handler/Client.hs +++ b/src/Vervis/Handler/Client.hs @@ -29,6 +29,9 @@ module Vervis.Handler.Client , getPublishOfferMergeR , postPublishOfferMergeR + + , getPublishMergeR + , postPublishMergeR ) where @@ -1142,3 +1145,41 @@ postPublishOfferMergeR = do then setMessage "Merge Request created" else setMessage "Offer published" redirect dest + +mergeForm :: Form (FedURI, FedURI) +mergeForm = renderDivs $ (,) + <$> areq fedUriField "Patch bundle to apply" Nothing + <*> areq fedUriField "Grant activity to use for authorization" Nothing + +getPublishMergeR :: Handler Html +getPublishMergeR = do + ((_, widget), enctype) <- runFormPost mergeForm + defaultLayout + [whamlet| +