package main import ( "context" "flag" "fmt" "net/url" "os" "path/filepath" "strings" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/storage/memory" yamlcodec "go.unistack.org/micro-codec-yaml/v3" flagconfig "go.unistack.org/micro-config-flag/v3" "go.unistack.org/micro/v3/config" "go.unistack.org/micro/v3/logger" "go.unistack.org/micro/v3/logger/slog" ) type Config struct { DstDir string `default:"." flag:"name=dstdir,desc='destination dir',default='.'"` Action string `default:"update" flag:"name=action,desc='action',default='update'"` Clean bool `default:"false" flag:"name=clean,desc='cleaup destination dir',default='false'"` } func main() { var err error ctx, cancel := context.WithCancel(context.Background()) defer cancel() cfg := &Config{} log := slog.NewLogger() if err = log.Init(); err != nil { log.Fatal(ctx, err) } logger.DefaultLogger = log if err = config.Load(ctx, []config.Config{ config.NewConfig(config.Struct(cfg)), flagconfig.NewConfig(config.Struct(cfg), config.Codec(yamlcodec.NewCodec())), }); err != nil { log.Fatal(ctx, err) } if cfg.DstDir == "" || cfg.DstDir == "." { if cfg.DstDir, err = os.Getwd(); err != nil { log.Fatal(ctx, err) } log.Info(ctx, "dstdir not specified, use current dir: %s", cfg.DstDir) } flagUrl := strings.Join(flag.Args(), " ") u, err := url.Parse(flagUrl) if err != nil { log.Fatal(ctx, err) } var rev string if idx := strings.Index(u.Path, "@"); idx > 0 { rev = u.Path[idx+1:] } cloneOpts := &git.CloneOptions{ URL: flagUrl, Progress: os.Stdout, } if len(rev) == 0 { cloneOpts.SingleBranch = true cloneOpts.Depth = 1 } if err := cloneOpts.Validate(); err != nil { log.Fatal(ctx, err) } log.Info(ctx, `try to fetch `+flagUrl) repo, err := git.CloneContext(ctx, memory.NewStorage(), nil, cloneOpts) if err != nil { log.Fatal(ctx, err) } ref, err := repo.Head() if err != nil { log.Fatal(ctx, "failed to get head: %v", err) } fmt.Println(ref.Hash()) commit, err := repo.CommitObject(ref.Hash()) if err != nil { log.Fatal(ctx, "failed to get commit: %v", err) } tree, err := commit.Tree() if err != nil { log.Fatal(ctx, err) } if err := os.MkdirAll(cfg.DstDir, os.FileMode(0o755)); err != nil { log.Fatal(ctx, "failed to create dir: %v", err) } if cfg.Clean { if err := cleanDir(ctx, cfg.DstDir); err != nil { log.Fatal(ctx, "failed to clean dir: %v", err) } } err = tree.Files().ForEach(func(file *object.File) error { if file == nil { return fmt.Errorf("file pointer is nil") } fmode, err := file.Mode.ToOSFileMode() if err != nil { return err } switch file.Mode { case filemode.Executable: return writeFile(ctx, file, cfg.DstDir, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fmode) case filemode.Dir: return os.MkdirAll(filepath.Join(cfg.DstDir, file.Name), fmode) case filemode.Regular: return writeFile(ctx, file, cfg.DstDir, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fmode) default: return fmt.Errorf("unsupported filetype %v for %s", file.Mode, file.Name) } }) if err != nil { log.Fatal(ctx, "failed to exctract file: %v", err) } }