]> Cypherpunks.ru repositories - goredo.git/blobdiff - main.go
Download link for 2.6.2 release
[goredo.git] / main.go
diff --git a/main.go b/main.go
index a79d2b130dd9679b6006682553e54c245fe29b35..eb67c8069fa2534b645368e1504b8b6963bb7853 100644 (file)
--- a/main.go
+++ b/main.go
@@ -1,30 +1,29 @@
-/*
-goredo -- djb's redo implementation on pure Go
-Copyright (C) 2020-2023 Sergey Matveev <stargrave@stargrave.org>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, version 3 of the License.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
+// goredo -- djb's redo implementation on pure Go
+// Copyright (C) 2020-2024 Sergey Matveev <stargrave@stargrave.org>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 package main
 
 import (
        "bufio"
        "bytes"
-       "crypto/rand"
+       "encoding/hex"
        "errors"
        "flag"
        "fmt"
        "io"
+       "io/fs"
        "log"
        "os"
        "os/signal"
@@ -34,6 +33,7 @@ import (
        "strconv"
        "syscall"
 
+       "github.com/google/uuid"
        "go.cypherpunks.ru/recfile"
        "golang.org/x/sys/unix"
 )
@@ -44,6 +44,7 @@ const (
        CmdNameRedoAffects  = "redo-affects"
        CmdNameRedoAlways   = "redo-always"
        CmdNameRedoCleanup  = "redo-cleanup"
+       CmdNameRedoDep2Rec  = "redo-dep2rec"
        CmdNameRedoDepFix   = "redo-depfix"
        CmdNameRedoDot      = "redo-dot"
        CmdNameRedoIfchange = "redo-ifchange"
@@ -59,7 +60,7 @@ const (
 
 var (
        Cwd       string
-       BuildUUID string
+       BuildUUID uuid.UUID
        IsTopRedo bool // is it the top redo instance
        UmaskCur  int
 )
@@ -92,12 +93,23 @@ func main() {
 
        var symlinks *bool
        cmdName := CmdName()
-       if cmdName == "goredo" {
+       if cmdName == CmdNameGoredo {
                symlinks = flag.Bool("symlinks", false, "create necessary symlinks in current directory")
        }
 
        flag.Usage = func() { usage(os.Args[0]) }
-       flag.Parse()
+
+       BuildUUIDStr := os.Getenv(EnvBuildUUID)
+       IsTopRedo = BuildUUIDStr == ""
+
+       var args []string
+       if IsTopRedo {
+               flag.Parse()
+               args = flag.Args()
+       } else {
+               args = os.Args[1:]
+       }
+
        if *warranty {
                fmt.Println(Warranty)
                return
@@ -113,6 +125,7 @@ func main() {
                        CmdNameRedoAffects,
                        CmdNameRedoAlways,
                        CmdNameRedoCleanup,
+                       CmdNameRedoDep2Rec,
                        CmdNameRedoDepFix,
                        CmdNameRedoDot,
                        CmdNameRedoIfchange,
@@ -185,7 +198,7 @@ func main() {
        LogLock = os.Getenv(EnvLogLock) == "1"
        LogJS = os.Getenv(EnvLogJS) == "1"
        if Debug || os.Getenv(EnvLogPid) == "1" {
-               MyPid = os.Getpid()
+               MyPID = os.Getpid()
        }
        var traced bool
        if flagTraceAll != nil && *flagTraceAll {
@@ -277,27 +290,22 @@ func main() {
                fdDep = mustParseFd(v, EnvDepFd)
        }
 
-       tgts := make([]*Tgt, 0, len(flag.Args()))
-       for _, arg := range flag.Args() {
+       tgts := make([]*Tgt, 0, len(args))
+       for _, arg := range args {
                tgts = append(tgts, NewTgt(arg))
        }
-       BuildUUID = os.Getenv(EnvBuildUUID)
        tgtsWasEmpty := len(tgts) == 0
-       if BuildUUID == "" {
-               IsTopRedo = true
-               raw := new([16]byte)
-               if _, err = io.ReadFull(rand.Reader, raw[:]); err != nil {
-                       log.Fatal(err)
-               }
-               raw[6] = (raw[6] & 0x0F) | uint8(4<<4) // version 4
-               BuildUUID = fmt.Sprintf(
-                       "%x-%x-%x-%x-%x",
-                       raw[0:4], raw[4:6], raw[6:8], raw[8:10], raw[10:],
-               )
+       if BuildUUIDStr == "" {
+               BuildUUID = uuid.New()
                if tgtsWasEmpty {
                        tgts = append(tgts, NewTgt("all"))
                }
                tracef(CDebug, "inode-trust: %s", InodeTrust)
+       } else {
+               BuildUUID, err = uuid.Parse(BuildUUIDStr)
+               if err != nil {
+                       log.Fatal(err)
+               }
        }
 
        if cmdName == CmdNameRedo {
@@ -320,12 +328,9 @@ func main() {
 
        ok := true
        err = nil
-       tracef(
-               CDebug, "[%s] run: %s %s cwd:%s dirprefix:%s",
-               BuildUUID, cmdName, tgts, Cwd, DirPrefix,
-       )
+       tracef(CDebug, "[%s] run: %s %s cwd:%s dirprefix:%s",
+               BuildUUID, cmdName, tgts, Cwd, DirPrefix)
 
-CmdSwitch:
        switch cmdName {
        case CmdNameRedo:
                for _, tgt := range tgts {
@@ -343,20 +348,22 @@ CmdSwitch:
                if fdDep == nil {
                        log.Fatalln("no", EnvDepFd)
                }
+               fdDepW := bufio.NewWriter(fdDep)
                for _, tgt := range tgts {
-                       err = ifcreate(fdDep, tgt.RelTo(path.Join(Cwd, DirPrefix)))
+                       err = ifcreate(fdDepW, fdDep.Name(), tgt.RelTo(path.Join(Cwd, DirPrefix)))
                        if err != nil {
                                break
                        }
                }
+               err = fdDepW.Flush()
        case CmdNameRedoAlways:
                if fdDep == nil {
                        log.Fatalln("no", EnvDepFd)
                }
-               err = always(fdDep)
+               err = always(fdDep, fdDep.Name())
        case CmdNameRedoCleanup:
                for _, what := range tgts {
-                       err = cleanupWalker(Cwd, what.t)
+                       err = cleanupWalker(Cwd, path.Base(what.a))
                        if err != nil {
                                break
                        }
@@ -367,7 +374,12 @@ CmdSwitch:
                if fdDep == nil {
                        log.Fatalln("no", EnvDepFd)
                }
-               err = stamp(fdDep, os.Stdin)
+               var hsh Hash
+               hsh, err = fileHash(os.Stdin)
+               if err != nil {
+                       break
+               }
+               err = stamp(fdDep, fdDep.Name(), hsh)
        case CmdNameRedoLog:
                if len(tgts) != 1 {
                        log.Fatal("single target expected")
@@ -377,52 +389,18 @@ CmdSwitch:
                if len(tgts) != 1 {
                        log.Fatal("single target expected")
                }
-               var fdTmp *os.File
-               fdTmp, err = os.CreateTemp("", "whichdo")
-               if err != nil {
-                       err = ErrLine(err)
-                       break
-               }
-               err = ErrLine(os.Remove(fdTmp.Name()))
+               var dos []string
+               dos, err = whichdo(tgts[0])
                if err != nil {
-                       break
-               }
-               tgt := tgts[0]
-               var doFile string
-               var upLevels int
-               doFile, upLevels, err = findDo(fdTmp, tgt.h, tgt.t)
-               if err != nil {
-                       err = ErrLine(err)
-                       break
-               }
-               _, err = fdTmp.Seek(0, io.SeekStart)
-               if err != nil {
-                       err = ErrLine(err)
-                       break
-               }
-               r := recfile.NewReader(fdTmp)
-               for {
-                       m, err := r.NextMap()
-                       if err != nil {
-                               if errors.Is(err, io.EOF) {
-                                       break
-                               }
-                               err = ErrLine(err)
-                               break CmdSwitch
-                       }
-                       fmt.Println(cwdMustRel(tgt.h, m["Target"]))
-               }
-               if doFile == "" {
-                       ok = false
-               } else {
-                       p := make([]string, 0, upLevels+2)
-                       p = append(p, tgt.h)
-                       for i := 0; i < upLevels; i++ {
-                               p = append(p, "..")
+                       if errors.Is(err, fs.ErrNotExist) {
+                               err = nil
+                               ok = false
+                       } else {
+                               break
                        }
-                       p = append(p, doFile)
-                       rel := mustRel(Cwd, path.Join(p...))
-                       fmt.Println(rel)
+               }
+               for _, do := range dos {
+                       fmt.Println(do)
                }
        case CmdNameRedoTargets:
                raws := make([]string, 0, len(tgts))
@@ -455,7 +433,7 @@ CmdSwitch:
                        }
                        deps := make(map[string]map[string]*Tgt)
                        for _, tgt := range tgtsKnown {
-                               collectDeps(NewTgt(tgt), 0, deps, true, make(map[string]struct{}))
+                               collectDeps(NewTgt(tgt), 0, deps, true)
                        }
                        seen := make(map[string]*Tgt)
                        for _, tgt := range tgts {
@@ -529,6 +507,8 @@ CmdSwitch:
                        fmt.Println(src)
                }
        case CmdNameRedoDepFix:
+               IfchangeCache = nil
+               DepFixHashCache = make(map[string]Hash)
                err = depFix(Cwd)
        case CmdNameRedoInode:
                var inode *Inode
@@ -546,6 +526,68 @@ CmdSwitch:
                                break
                        }
                }
+       case CmdNameRedoDep2Rec:
+               var data []byte
+               data, err = os.ReadFile(tgts[0].a)
+               if err != nil {
+                       break
+               }
+               var build uuid.UUID
+               build, data, err = depHeadParse(data)
+               if err != nil {
+                       break
+               }
+               w := bufio.NewWriter(os.Stdout)
+               err = recfileWrite(w, []recfile.Field{
+                       {Name: "Build", Value: build.String()},
+               }...)
+               if err != nil {
+                       break
+               }
+               var typ byte
+               var chunk []byte
+               var inode Inode
+               for len(data) > 0 {
+                       typ, chunk, data, _ = chunkRead(data)
+                       switch typ {
+                       case DepTypeAlways:
+                               err = recfileWrite(w, []recfile.Field{
+                                       {Name: "Type", Value: "always"},
+                               }...)
+                       case DepTypeStamp:
+                               err = recfileWrite(w, []recfile.Field{
+                                       {Name: "Type", Value: "stamp"},
+                                       {Name: "Hash", Value: hex.EncodeToString(chunk)},
+                               }...)
+                       case DepTypeIfcreate:
+                               err = recfileWrite(w, []recfile.Field{
+                                       {Name: "Type", Value: "ifcreate"},
+                                       {Name: "Target", Value: string(chunk)},
+                               }...)
+                       case DepTypeIfchange:
+                               name := string(chunk[InodeLen+HashLen:])
+                               meta := chunk[:InodeLen+HashLen]
+                               fields := []recfile.Field{
+                                       {Name: "Type", Value: "ifchange"},
+                                       {Name: "Target", Value: name},
+                               }
+                               fields = append(fields, recfile.Field{
+                                       Name: "Hash", Value: Hash(meta[InodeLen:]).String(),
+                               })
+                               inode = Inode(meta[:][:InodeLen])
+                               fields = append(fields, inode.RecfileFields()...)
+                               err = recfileWrite(w, fields...)
+                       case DepTypeIfchangeNonex:
+                               err = recfileWrite(w, []recfile.Field{
+                                       {Name: "Type", Value: "ifchange"},
+                                       {Name: "Target", Value: string(chunk)},
+                               }...)
+                       }
+                       if err != nil {
+                               break
+                       }
+               }
+               err = w.Flush()
        default:
                log.Fatalln("unknown command", cmdName)
        }