~jan0sch/smederee
Showing details for patch c7a3dab1e84024471d31b11a58acf1798e06b7a7.
diff -rN -u old-smederee/modules/darcs/src/main/scala/de/smederee/darcs/DarcsCommands.scala new-smederee/modules/darcs/src/main/scala/de/smederee/darcs/DarcsCommands.scala --- old-smederee/modules/darcs/src/main/scala/de/smederee/darcs/DarcsCommands.scala 2025-02-02 09:48:36.768627283 +0000 +++ new-smederee/modules/darcs/src/main/scala/de/smederee/darcs/DarcsCommands.scala 2025-02-02 09:48:36.772627289 +0000 @@ -73,6 +73,31 @@ final class DarcsCommands[F[_]: Sync](val darcsBinary: Path) { private val log = LoggerFactory.getLogger(getClass) + /** Clone a darcs repository from one directory into another one. This function is intended to be used for + * server side forking or repositories. + * + * @param sourceRepositoryPath + * The path of the source repository. + * @param targetRespositoryPath + * The path of the target repository. + * @param options + * Additional options for the clone command. + * @return + * The output of the darcs command. + */ + def clone(sourceRepositoryPath: Path, targetRespositoryPath: Path)( + options: Chain[String] + ): F[DarcsCommandOutput] = { + val source = sourceRepositoryPath.toAbsolutePath + val target = targetRespositoryPath.toAbsolutePath + log.trace(s"Execute $darcsBinary clone $sourceRepositoryPath to $targetRespositoryPath with $options") + val darcsOptions = List("clone") ::: options.toList ::: List(source.toString, target.toString) + val externalCommand = os.proc(darcsBinary.toString, darcsOptions) + for { + process <- Sync[F].delay(externalCommand.call(check = false)) + } yield DarcsCommandOutput(process.exitCode, Chain(process.out.text()), Chain(process.err.text())) + } + /** Initialize a darcs repository under the given base path with the provided name. This is done by running * the external darcs binary with the appropriate parameters. * diff -rN -u old-smederee/modules/darcs/src/test/scala/de/smederee/darcs/DarcsCommandsTest.scala new-smederee/modules/darcs/src/test/scala/de/smederee/darcs/DarcsCommandsTest.scala --- old-smederee/modules/darcs/src/test/scala/de/smederee/darcs/DarcsCommandsTest.scala 2025-02-02 09:48:36.768627283 +0000 +++ new-smederee/modules/darcs/src/test/scala/de/smederee/darcs/DarcsCommandsTest.scala 2025-02-02 09:48:36.772627289 +0000 @@ -37,6 +37,39 @@ override def munitFixtures = List(workingDirectory) + test("darcs clone must create a proper copy") { + val cmd = new DarcsCommands[IO](darcsBinary) + val source = Paths.get(workingDirectory().toString, "source") + val target = Paths.get(workingDirectory().toString, "target") + val test = + for { + init <- cmd.initialize(workingDirectory())("source")(Chain.empty) + clone <- cmd.clone(source, target)(Chain.empty) + } yield (init, clone) + test.map { output => + val (init, clone) = output + assert(init.exitValue === 0, "darcs init did not finish with exit code 0!") + assert(Files.exists(source.toAbsolutePath), "Source repository directory does not exist!") + assert(clone.exitValue === 0, "darcs clone did not finish with exit code 0!") + assert(Files.exists(target.toAbsolutePath), "Target repository directory does not exist!") + } + } + + test("darcs clone must fail if the source does not exist") { + val cmd = new DarcsCommands[IO](darcsBinary) + val source = Paths.get(workingDirectory().toString, "source-should-not-exist") + val target = Paths.get(workingDirectory().toString, "target-should-not-be-created") + val test = + for { + clone <- cmd.clone(source, target)(Chain.empty) + } yield clone + test.map { clone => + assert(!Files.exists(source.toAbsolutePath), "Source repository directory must not exist!") + assert(clone.exitValue =!= 0, "darcs clone must not finish with exit code 0!") + assert(!Files.exists(target.toAbsolutePath), "Target repository directory must not exist!") + } + } + test("darcs initialize must create a new repository") { val cmd = new DarcsCommands[IO](darcsBinary) val repo = "test-repository" diff -rN -u old-smederee/modules/hub/src/main/resources/messages_en.properties new-smederee/modules/hub/src/main/resources/messages_en.properties --- old-smederee/modules/hub/src/main/resources/messages_en.properties 2025-02-02 09:48:36.768627283 +0000 +++ new-smederee/modules/hub/src/main/resources/messages_en.properties 2025-02-02 09:48:36.772627289 +0000 @@ -25,6 +25,7 @@ form.create-repo.website=Website form.create-repo.website.placeholder=https://example.com form.create-repo.website.help=An optional URI pointing to the website of your project. +form.fork.button.submit=Clone to your account. form.login.button.submit=Login form.login.password=Password form.login.password.placeholder=Please enter your password here. @@ -130,6 +131,7 @@ repository.menu.website.tooltip=Click here to open the project website ({0}) in a new tab or window. repository.description.title=Summary: +repository.overview.clone.fork=Create your personal fork. repository.overview.clone.title=Clone this repository repository.overview.clone.read-only=read-only repository.overview.clone.read-write=read-write diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-02-02 09:48:36.768627283 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-02-02 09:48:36.772627289 +0000 @@ -532,6 +532,73 @@ } yield resp } + private val forkRepository: AuthedRoutes[Account, F] = AuthedRoutes.of { + case ar @ POST -> Root / UsernamePathParameter(repositoryOwnerName) / VcsRepositoryNamePathParameter( + repositoryName + ) / "fork" as user => + ar.req.decodeStrict[F, UrlForm] { urlForm => + for { + csrf <- Sync[F].delay(ar.req.getCsrfToken) + owner <- vcsMetadataRepo.findVcsRepositoryOwner(repositoryOwnerName) + loadedSourceRepo <- owner match { + case None => Sync[F].pure(None) + case Some(owner) => vcsMetadataRepo.findVcsRepository(owner, repositoryName) + } + // TODO Replace with whatever we implement as proper permission model. ;-) + sourceRepository = loadedSourceRepo.filter(r => r.isPrivate === false) + // Check if a repository with that name already exists for the user. + loadedTargetRepo <- vcsMetadataRepo.findVcsRepository(user.toVcsRepositoryOwner, repositoryName) + targetRepository = loadedTargetRepo.fold( + sourceRepository.map(_.copy(owner = user.toVcsRepositoryOwner)) + )(_ => None) + targetUri <- Sync[F].delay( + linkConfig.createFullUri( + Uri(path = + Uri.Path( + Vector( + Uri.Path.Segment(s"~${user.name.toString}"), + Uri.Path.Segment(repositoryName.toString) + ) + ) + ) + ) + ) + sourceDirectory <- Sync[F].delay( + Paths.get( + darcsConfig.repositoriesDirectory.toString, + repositoryOwnerName.toString, + repositoryName.toString + ) + ) + targetDirectory <- Sync[F].delay( + Paths.get( + darcsConfig.repositoriesDirectory.toString, + user.name.toString, + repositoryName.toString + ) + ) + output <- targetRepository match { + case None => Sync[F].pure(0) + case Some(repoMetadata) => + for { + _ <- Sync[F].delay( + Files.createDirectories( + Paths.get(darcsConfig.repositoriesDirectory.toString, repoMetadata.owner.name.toString) + ) + ) + darcsClone <- darcs.clone(sourceDirectory, targetDirectory)(Chain("--complete")) + createRepo <- + if (darcsClone.exitValue === 0) + vcsMetadataRepo.createVcsRepository(repoMetadata) + else + Sync[F].pure(0) // Do not create DB entry if darcs init failed! + } yield createRepo + } + resp <- SeeOther(Location(targetUri)) + } yield resp + } + } + private val parseCreateRepositoryForm: AuthedRoutes[Account, F] = AuthedRoutes.of { case ar @ POST -> Root / "repo" / "create" as user => ar.req.decodeStrict[F, UrlForm] { urlForm => @@ -725,7 +792,7 @@ } val protectedRoutes = - showAllRepositories <+> showRepositories <+> parseCreateRepositoryForm <+> showCreateRepositoryForm <+> showRepositoryOverview <+> showRepositoryHistory <+> showRepositoryFiles + forkRepository <+> showAllRepositories <+> showRepositories <+> parseCreateRepositoryForm <+> showCreateRepositoryForm <+> showRepositoryOverview <+> showRepositoryHistory <+> showRepositoryFiles val routes = cloneRepository <+> showAllRepositoriesForGuests <+> showRepositoriesForGuests <+> showRepositoryOverviewForGuests <+> showRepositoryHistoryForGuests <+> showRepositoryFilesForGuests diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html 2025-02-02 09:48:36.768627283 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html 2025-02-02 09:48:36.772627289 +0000 @@ -51,6 +51,23 @@ </fieldset> </form> </dd> + @for(account <- user) { + @if(vcsRepository.owner === account.toVcsRepositoryOwner) { + <!-- Cloning/Forking a repo to ourself is disabled. --> + } else { + <dt>@Messages("repository.overview.clone.fork")</dt> + <dd> + <form action="@actionBaseUri.addSegment("fork")" method="POST" accept-charset="UTF-8" class="pure-form"> + <fieldset> + @csrfToken(csrf) + <div class="pure-controls"> + <button type="submit" class="pure-button">@Messages("form.fork.button.submit")</button> + </div> + </fieldset> + </form> + </dd> + } + } </dl> </div> </div>