All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.lunatech.cmt.admin.command.Delinearize.scala Maven / Gradle / Ivy

There is a newer version: 2.0.19
Show newest version
package com.lunatech.cmt.admin.command

import caseapp.{AppName, CommandName, ExtraName, HelpMessage, Recurse, RemainingArgs, ValueDescription}
import com.lunatech.cmt.*
import com.lunatech.cmt.Helpers.*
import com.lunatech.cmt.ProcessDSL.toProcessCmd
import com.lunatech.cmt.admin.Domain.LinearizeBaseDirectory
import com.lunatech.cmt.admin.cli.SharedOptions
import com.lunatech.cmt.core.execution.Executable
import com.lunatech.cmt.core.validation.Validatable
import sbt.io.IO as sbtio
import sbt.io.syntax.*
import com.lunatech.cmt.admin.cli.ArgParsers.linearizeBaseDirectoryArgParser
import com.lunatech.cmt.core.cli.CmtCommand

object Delinearize:

  @AppName("delinearize")
  @CommandName("delinearize")
  @HelpMessage("'Delinearizes' an existing master repository")
  final case class Options(
      @ExtraName("d")
      @ValueDescription("Folder which contains a 'linearized' repo of the main repo")
      linearizeBaseDirectory: LinearizeBaseDirectory,
      @Recurse shared: SharedOptions)

  given Validatable[Delinearize.Options] with
    extension (options: Delinearize.Options)
      def validated(): Either[CmtError, Delinearize.Options] =
        Right(options)
  end given

  case class ExerciseNameAndSHA(exName: String, exSHA: String)

  given Executable[Delinearize.Options] with
    extension (options: Delinearize.Options)
      def execute(): Either[CmtError, String] = {
        import DelinearizeHelpers.*

        val mainRepository = options.shared.mainRepository
        val config = new CMTaConfig(mainRepository.value, options.shared.maybeConfigFile.map(_.value))

        for {
          _ <- Right(()).withLeft[CmtError]
          _ = println(s"De-linearizing ${toConsoleGreen(mainRepository.value.getPath)} to ${toConsoleGreen(
              options.linearizeBaseDirectory.value.getPath)}")

          mainRepoName = mainRepository.value.getName

          ExercisesMetadata(_, exercisesInMain, _) <- getExerciseMetadata(mainRepository.value)(config)

          linearizedRootFolder = options.linearizeBaseDirectory.value / mainRepoName

          exercisesAndSHAsInLinearized <- getExercisesAndSHAs(linearizedRootFolder)

          _ <- checkReposMatch(exercisesInMain, exercisesAndSHAsInLinearized)

          _ <- putBackToMain(mainRepository.value, linearizedRootFolder, exercisesAndSHAsInLinearized)(config)

          successMessage <- Right(s"Successfully delinearised ${options.linearizeBaseDirectory.value.getPath}")
        } yield successMessage
      }

  private object DelinearizeHelpers:
    def getExercisesAndSHAs(linearizedRootFolder: File): Either[CmtError, Vector[ExerciseNameAndSHA]] =
      "git log --oneline".toProcessCmd(linearizedRootFolder).runAndReadOutput().map(processGitLogOutput)
    end getExercisesAndSHAs

    def processGitLogOutput(gitLogOutput: String): Vector[ExerciseNameAndSHA] =
      gitLogOutput.split("""\n""").toVector.map(splitSHAandExName).map(convertToExNameAndSHA).reverse
    end processGitLogOutput

    def convertToExNameAndSHA(v: Vector[String]): ExerciseNameAndSHA =
      v match
        case sha +: name +: _ => ExerciseNameAndSHA(name, sha)
        case _                => ???
    end convertToExNameAndSHA

    def splitSHAandExName(shaAndExname: String): Vector[String] =
      shaAndExname.split("""\s+""").toVector

    def checkReposMatch(
        exercisesInMain: Seq[String],
        exercisesAndSHAs: Vector[ExerciseNameAndSHA]): Either[CmtError, Unit] =
      // TODO: in case repos are incompatible, print out the exercise list on both ends (if any)
      if exercisesInMain == exercisesAndSHAs.map(_.exName) then Right(())
      else Left(s"Cannot de-linearize: repositories are incompatible".toExecuteCommandErrorMessage)
    end checkReposMatch

    def putBackToMain(mainRepo: File, linearizedRepo: File, exercisesAndSHAs: Vector[ExerciseNameAndSHA])(
        config: CMTaConfig): Either[CmtError, Unit] =

      val mainRepoActiveExerciseFolder = mainRepo / config.mainRepoExerciseFolder
      val linearizedActiveExerciseFolder =
        linearizedRepo / config.linearizedRepoActiveExerciseFolder

      for {
        _ <- checkoutAndCopy(
          mainRepoActiveExerciseFolder,
          linearizedRepo,
          linearizedActiveExerciseFolder,
          exercisesAndSHAs)
        result <- s"git checkout main"
          .toProcessCmd(linearizedRepo)
          .runWithStatus(toConsoleRed(s"Unable to checkout main in linearized repo"))
      } yield result
    end putBackToMain

    @scala.annotation.tailrec
    def checkoutAndCopy(
        mainRepoActiveExerciseFolder: File,
        linearizedRepo: File,
        linearizedActiveExerciseFolder: File,
        exercisesAndSHAs: Seq[ExerciseNameAndSHA]): Either[CmtError, Unit] =
      exercisesAndSHAs match
        case ExerciseNameAndSHA(exercise, sha) +: remaining =>
          s"git checkout $sha"
            .toProcessCmd(linearizedRepo)
            .runWithStatus(toConsoleRed(s"Unable to checkout commit($sha) corresponding to exercise: $exercise")) match
            case l @ Left(_) => l
            case r @ Right(_) =>
              sbtio.delete(mainRepoActiveExerciseFolder / exercise)
              sbtio.copyDirectory(
                linearizedActiveExerciseFolder,
                mainRepoActiveExerciseFolder / exercise,
                preserveLastModified = true)
              checkoutAndCopy(mainRepoActiveExerciseFolder, linearizedRepo, linearizedActiveExerciseFolder, remaining)
        case Nil => Right(())
    end checkoutAndCopy
  end DelinearizeHelpers

  val command = new CmtCommand[Delinearize.Options] {
    def run(options: Delinearize.Options, args: RemainingArgs): Unit =
      options.validated().flatMap(_.execute()).printResult()
  }

end Delinearize




© 2015 - 2024 Weber Informatics LLC | Privacy Policy