From 9eac36a446307c97dcb299d879f6613217b732cb Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Fri, 2 May 2025 15:24:33 +0800 Subject: [PATCH 01/23] feat: add git-checkout executor --- go.mod | 40 ++- go.sum | 88 +++-- internal/digraph/executor/checkout.go | 465 ++++++++++++++++++++++++++ 3 files changed, 561 insertions(+), 32 deletions(-) create mode 100644 internal/digraph/executor/checkout.go diff --git a/go.mod b/go.mod index 3ed2e3d639..e150677cc2 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/dagu-org/dagu -go 1.23 +go 1.23.0 + +toolchain go1.24.1 require ( github.com/Masterminds/sprig/v3 v3.2.3 @@ -8,6 +10,7 @@ require ( github.com/docker/docker v27.4.1+incompatible github.com/fsnotify/fsnotify v1.7.0 github.com/go-chi/chi/v5 v5.0.8 + github.com/go-git/go-git/v5 v5.16.0 github.com/go-openapi/errors v0.22.0 github.com/go-openapi/loads v0.22.0 github.com/go-openapi/runtime v0.28.0 @@ -36,7 +39,7 @@ require ( github.com/stretchr/testify v1.10.0 go.uber.org/goleak v1.3.0 golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 - golang.org/x/text v0.21.0 + golang.org/x/text v0.24.0 gopkg.in/yaml.v2 v2.4.0 gotest.tools/gotestsum v1.12.0 mvdan.cc/sh/v3 v3.10.0 @@ -45,6 +48,7 @@ require ( require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect + dario.cat/mergo v1.0.0 // indirect github.com/4meepo/tagalign v1.3.4 // indirect github.com/Abirdcfly/dupword v0.1.3 // indirect github.com/Antonboom/errname v1.0.0 // indirect @@ -56,8 +60,9 @@ require ( github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect - github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/alecthomas/go-check-sumtype v0.2.0 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect @@ -84,8 +89,10 @@ require ( github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect github.com/ckaznocha/intrange v0.2.1 // indirect + github.com/cloudflare/circl v1.6.1 // indirect github.com/containerd/log v0.1.0 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/daixiang0/gci v0.13.5 // indirect github.com/dave/dst v0.27.3 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect @@ -93,6 +100,7 @@ require ( github.com/dnephin/pflag v1.0.7 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect @@ -101,6 +109,8 @@ require ( github.com/fzipp/gocyclo v0.6.0 // indirect github.com/ghostiam/protogetter v0.3.8 // indirect github.com/go-critic/go-critic v0.11.5 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.23.0 // indirect @@ -118,7 +128,8 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/go-printf-func-name v0.1.0 // indirect github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 // indirect @@ -127,7 +138,7 @@ require ( github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/golangci/revgrep v0.5.3 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-github/v66 v66.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect @@ -144,12 +155,14 @@ require ( github.com/huandu/xstrings v1.4.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/itchyny/timefmt-go v0.1.5 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jjti/go-spancheck v0.6.2 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/julz/importas v0.1.0 // indirect github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kisielk/errcheck v1.8.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -189,6 +202,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/polyfloyd/go-errorlint v1.7.0 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect @@ -200,7 +214,7 @@ require ( github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/raeperd/recvcheck v0.1.2 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect @@ -210,11 +224,13 @@ require ( github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect github.com/securego/gosec/v2 v2.21.4 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/tenv v1.12.1 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect github.com/sonatard/noctx v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect @@ -237,6 +253,7 @@ require ( github.com/uudashr/gocognit v1.1.3 // indirect github.com/uudashr/iface v1.2.1 // indirect github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect @@ -260,14 +277,15 @@ require ( golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/term v0.27.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/term v0.31.0 // indirect golang.org/x/tools v0.27.0 // indirect google.golang.org/protobuf v1.35.2 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect gopkg.in/fsnotify.v1 v1.4.7 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect honnef.co/go/tools v0.5.1 // indirect mvdan.cc/gofumpt v0.7.0 // indirect mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect @@ -279,8 +297,8 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/samber/lo v1.38.1 - golang.org/x/crypto v0.31.0 - golang.org/x/net v0.32.0 - golang.org/x/sys v0.28.0 + golang.org/x/crypto v0.37.0 + golang.org/x/net v0.39.0 + golang.org/x/sys v0.32.0 gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index bac874ffab..8709e9dd42 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= @@ -64,10 +66,13 @@ github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+ github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= -github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= -github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/adrg/xdg v0.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY= github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= @@ -90,6 +95,10 @@ github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pO github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= @@ -140,6 +149,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/ckaznocha/intrange v0.2.1 h1:M07spnNEQoALOJhwrImSrJLaxwuiQK+hA2DeajBlwYk= github.com/ckaznocha/intrange v0.2.1/go.mod h1:7NEhVyf8fzZO5Ds7CRaqPEm52Ut83hsTiL5zbER/HYk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= @@ -147,6 +158,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= @@ -169,6 +182,10 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -193,10 +210,20 @@ github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghostiam/protogetter v0.3.8 h1:LYcXbYvybUyTIxN2Mj9h6rHrDZBDwZloPoKctWrFyJY= github.com/ghostiam/protogetter v0.3.8/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-critic/go-critic v0.11.5 h1:TkDTOn5v7EEngMxu8KbuFqFR43USaaH8XRJLz1jhVYA= github.com/go-critic/go-critic v0.11.5/go.mod h1:wu6U7ny9PiaHaZHcvMDmdysMqvDem162Rh3zWTrqk8M= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9AQ= +github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -278,6 +305,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -301,8 +330,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= @@ -338,8 +367,9 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v66 v66.0.0 h1:ADJsaXj9UotwdgK8/iFZtv7MLc8E8WBl62WLd/D/9+M= github.com/google/go-github/v66 v66.0.0/go.mod h1:+4SO9Zkuyf8ytMj0csN1NR/5OTR+MfqPp8P8dVlcvY4= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -406,6 +436,8 @@ github.com/itchyny/gojq v0.12.12 h1:x+xGI9BXqKoJQZkr95ibpe3cdrTbY8D9lonrK433rcA= github.com/itchyny/gojq v0.12.12/go.mod h1:j+3sVkjxwd7A7Z5jrbKibgOLn0ZfLWkV+Awxr/pyzJE= github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty/v6 v6.3.6 h1:A6w2BuyPMtf7M82BGRBys9bAba2C26ZX9lrlrZ7uH6U= github.com/jedib0t/go-pretty/v6 v6.3.6/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= @@ -433,6 +465,8 @@ github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.8.0 h1:ZX/URYa7ilESY19ik/vBmCn6zdGQLxACwjAcWbHlYlg= github.com/kisielk/errcheck v1.8.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= @@ -550,6 +584,8 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -607,8 +643,8 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= @@ -634,8 +670,8 @@ github.com/securego/gosec/v2 v2.21.4 h1:Le8MSj0PDmOnHJgUATjD96PaXRvCpKC+DGJvwyy0 github.com/securego/gosec/v2 v2.21.4/go.mod h1:Jtb/MwRQfRxCXyCm1rfM1BEiiiTfUOdyzzAhlr6lUTA= github.com/segmentio/golines v0.12.2 h1:1aktcB7R/mJchuQePC50Sni6DOE8QqOwFsOoG9Wt9Ho= github.com/segmentio/golines v0.12.2/go.mod h1:jrFsBVuqmgT8WKC7tgtkCQnHColYb1eesCef2Rrclg8= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -646,12 +682,15 @@ github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOms github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -721,6 +760,8 @@ github.com/uudashr/iface v1.2.1 h1:vHHyzAUmWZ64Olq6NZT3vg/z1Ws56kyPdBOd5kTXDF8= github.com/uudashr/iface v1.2.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= @@ -788,14 +829,15 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -880,6 +922,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= @@ -890,8 +933,8 @@ golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -915,8 +958,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -930,6 +973,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -976,8 +1020,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -990,8 +1034,8 @@ golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1006,8 +1050,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1181,6 +1225,8 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/digraph/executor/checkout.go b/internal/digraph/executor/checkout.go new file mode 100644 index 0000000000..3c2001b2b1 --- /dev/null +++ b/internal/digraph/executor/checkout.go @@ -0,0 +1,465 @@ +package executor + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "regexp" + "strings" + + "github.com/dagu-org/dagu/internal/digraph" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/transport" + gitHttp "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/go-git/go-git/v5/plumbing/transport/ssh" + "github.com/go-git/go-git/v5/storage/filesystem" + "github.com/go-viper/mapstructure/v2" +) + +const ( + gitCheckOutExecutorType = "git-checkout" + defaultSSHUser = "git" +) + +var ( + errGitCheckoutAuthConfigInvalid = fmt.Errorf("invalid git checkout auth config") +) + +var _ Executor = (*gitCheckout)(nil) + +func init() { + Register(gitCheckOutExecutorType, newCheckout) +} + +type gitCheckoutExecConfigDefinition struct { + Repo string + Ref string + Path string + Depth int + Progress bool + Cache bool + Auth gitCheckoutExecAuthConfigDefinition +} + +func (g *gitCheckoutExecConfigDefinition) getRepoCachePath() string { + // https://github.com/dagu-org/dagu.git -> github.com/dagu-org/dagu.git + // git@github.com:dagu-org/dagu.git -> github.com/dagu-org/dagu.git + + repo := strings.TrimPrefix(g.Repo, "https://") + repo = strings.TrimPrefix(repo, "git@") + + return fmt.Sprintf("~/.cache/dagu/git/%s", repo) +} + +type gitCheckoutExecAuthConfigDefinition struct { + Method string + TokenEnv string + UserName string + Password string + SSHUser string + SSHKey string + SSHKeyPassword string + SSHAgent bool +} + +// httpAuthMethod returns http auth method, if auth config is not set, return nil +func (g *gitCheckoutExecAuthConfigDefinition) httpAuthMethod() (transport.AuthMethod, error) { + if g.TokenEnv != "" { + g.UserName = g.SSHUser + g.Password = os.Getenv(g.TokenEnv) + } + + if len(g.UserName) == 0 || len(g.Password) == 0 { + return nil, errGitCheckoutAuthConfigInvalid + } + + return &gitHttp.BasicAuth{ + Username: g.UserName, + Password: g.Password, + }, nil +} + +// sshAuthMethod returns ssh auth method, if auth config is not set, return nil +func (g *gitCheckoutExecAuthConfigDefinition) sshAuthMethod() (transport.AuthMethod, error) { + var ( + authMethod transport.AuthMethod + publicKey *ssh.PublicKeys + err error + ) + + if g.SSHAgent { + if authMethod, err = ssh.NewSSHAgentAuth(g.SSHUser); err != nil { + return nil, fmt.Errorf("failed to create ssh agent auth: %w", err) + } + + return authMethod, nil + } + + if _, err = os.Stat(g.SSHKey); err != nil { + return nil, fmt.Errorf("failed to find ssh key file: %w", err) + } + + if publicKey, err = ssh.NewPublicKeysFromFile(g.SSHUser, g.SSHKey, g.SSHKeyPassword); err != nil { + return nil, fmt.Errorf("failed to create ssh public keys: %w", err) + } + + return publicKey, nil +} + +func (g *gitCheckoutExecAuthConfigDefinition) authMethod(repo string) (transport.AuthMethod, error) { + if len(g.SSHUser) == 0 { + g.SSHUser = defaultSSHUser + } + + // example: https://github.com/dagu-org/dagu.git, use http auth + if strings.HasPrefix(repo, "https://") { + return g.httpAuthMethod() + } + + // example: git@github.com:dagu-org/dagu.git, use ssh auth + return g.sshAuthMethod() +} + +type gitCheckoutExecConfig struct { + repo string + ref string + path string + depth int + progress bool + cache bool + repoCachePath string +} + +type gitCheckout struct { + stdout io.Writer + stderr io.Writer + authMethod transport.AuthMethod + config *gitCheckoutExecConfig +} + +func convertFromDef(def *gitCheckoutExecConfigDefinition, authMethod transport.AuthMethod) *gitCheckout { + return &gitCheckout{ + stdout: os.Stdout, + stderr: os.Stderr, + config: &gitCheckoutExecConfig{ + repo: def.Repo, + ref: def.Ref, + path: def.Path, + depth: def.Depth, + progress: def.Progress, + cache: def.Cache, + repoCachePath: def.getRepoCachePath(), + }, + authMethod: authMethod, + } +} + +func newCheckout(_ context.Context, step digraph.Step) (Executor, error) { + var ( + def = &gitCheckoutExecConfigDefinition{} + authMethod transport.AuthMethod + err error + ) + + if err = decodeGitCheckoutConfig(step.ExecutorConfig.Config, def); err != nil { + return nil, fmt.Errorf("failed to decode git checkout config: %w", err) + } + + if authMethod, err = def.Auth.authMethod(def.Repo); err != nil { + return nil, err + } + + return convertFromDef(def, authMethod), nil +} + +func decodeGitCheckoutConfig(data map[string]any, config *gitCheckoutExecConfigDefinition) error { + var ( + mapDecoder *mapstructure.Decoder + decodeConfig = &mapstructure.DecoderConfig{ + Result: config, + WeaklyTypedInput: true, + } + err error + ) + + if mapDecoder, err = mapstructure.NewDecoder(decodeConfig); err != nil { + return fmt.Errorf("failed to create map decoder: %w", err) + } + + if err = mapDecoder.Decode(data); err != nil { + return fmt.Errorf("failed to decode git checkout config: %w", err) + } + + return nil +} + +func (g *gitCheckout) SetStdout(out io.Writer) { + g.stdout = out +} + +func (g *gitCheckout) SetStderr(out io.Writer) { + g.stderr = out +} + +func (g *gitCheckout) Kill(_ os.Signal) error { + return nil +} + +func (g *gitCheckout) rmWorkPathIfExists() error { + var ( + err error + ) + + if err = os.RemoveAll(g.config.path); err != nil { + return fmt.Errorf("failed to remove %s: %w", g.config.path, err) + } + + return nil +} + +// getFetchOptions returns the fetch options +// ref may be in the form of +// branch : "refs/heads/main" or "main" +// tag : "refs/tags/v1.0" or "v1.0" +// commit : "refs/commits/abc123" or "abc123" +func (g *gitCheckout) getFetchOptions() (*git.FetchOptions, error) { + var ( + fetchOptions = &git.FetchOptions{ + RemoteName: git.DefaultRemoteName, + RemoteURL: g.config.repo, + Auth: g.authMethod, + Depth: g.config.depth, + RefSpecs: []config.RefSpec{ + config.RefSpec(fmt.Sprintf("+refs/heads/%s:refs/remotes/origin/%s", g.config.ref, g.config.ref)), + }, + } + ) + + if g.config.progress { + fetchOptions.Progress = g.stdout + } + + if len(g.config.ref) == 0 { + return nil, fmt.Errorf("ref is empty") + } + + if strings.HasPrefix(g.config.ref, "refs/") { + fetchOptions.RefSpecs = []config.RefSpec{config.RefSpec(g.config.ref)} + } + + if g.isTag() { + fetchOptions.RefSpecs = []config.RefSpec{config.RefSpec(fmt.Sprintf("+refs/tags/%s:refs/tags/%s", g.config.ref, g.config.ref))} + } + + if g.isHash() { + hash := plumbing.NewHash(g.config.ref) + fetchOptions.RefSpecs = []config.RefSpec{config.RefSpec(fmt.Sprintf("+%[1]s:%[1]s", hash, hash))} + fetchOptions.Tags = git.NoTags + } + + return fetchOptions, nil +} + +// initRepo initializes the git repository +func (g *gitCheckout) initRepo() (*git.Repository, error) { + var ( + repo *git.Repository + err error + ) + + if repo, err = git.PlainInit(g.config.path, false); err != nil { + return nil, fmt.Errorf("failed to init git repository: %w", err) + } + + if _, err = repo.CreateRemote(&config.RemoteConfig{ + Name: git.DefaultRemoteName, + URLs: []string{g.config.repo}, + }); err != nil { + return nil, fmt.Errorf("failed to create remote repository: %w", err) + } + + return repo, nil +} + +func (g *gitCheckout) setRepoAlternate(repo *git.Repository) error { + var ( + err error + ) + + storage, ok := repo.Storer.(*filesystem.Storage) + if !ok { + return fmt.Errorf("unexpected storage type") + } + + if err = storage.AddAlternate(g.config.repoCachePath); err != nil { + return fmt.Errorf("failed to add alternate: %w", err) + } + + return nil +} + +func (g *gitCheckout) getRepo() (*git.Repository, error) { + var ( + repo *git.Repository + err error + ) + + if repo, err = g.initRepo(); err != nil { + return nil, err + } + + if !g.config.cache { + return repo, nil + } + + if err = g.setRepoAlternate(repo); err != nil { + return nil, err + } + + return repo, nil +} + +func (g *gitCheckout) fetch(ctx context.Context, repo *git.Repository) error { + var ( + fetchOptions *git.FetchOptions + err error + ) + + if fetchOptions, err = g.getFetchOptions(); err != nil { + return fmt.Errorf("failed to get fetch options: %w", err) + } + + if err = repo.FetchContext(ctx, fetchOptions); err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { + return fmt.Errorf("failed to fetch repository: %w", err) + } + + return nil +} + +// isTag checks if the ref is a tag or a short tag +func (g *gitCheckout) isTag() bool { + var ( + tagRegex = regexp.MustCompile(`^refs/tags/.*$`) + shortTagRegex = regexp.MustCompile(`^v(\d+\.)+\d+$`) + ) + + return tagRegex.MatchString(g.config.ref) || shortTagRegex.MatchString(g.config.ref) +} + +// isHash checks if the ref is a hash +func (g *gitCheckout) isHash() bool { + var ( + hashRegex = regexp.MustCompile(`^[0-9a-f]{40}$`) + ) + + return hashRegex.MatchString(g.config.ref) +} + +func (g *gitCheckout) getCheckoutOptions(repo *git.Repository) (*git.CheckoutOptions, error) { + var ( + isHash = g.isHash() + isTag = g.isTag() + checkoutOptions = &git.CheckoutOptions{} + revHash *plumbing.Hash + err error + ) + + if !isHash && !isTag { + checkoutOptions.Force = true + + localBranchReferenceName := plumbing.NewBranchReferenceName(g.config.ref) + remoteReferenceName := plumbing.NewRemoteReferenceName(git.DefaultRemoteName, g.config.ref) + newReference := plumbing.NewSymbolicReference(localBranchReferenceName, remoteReferenceName) + + if err = repo.Storer.SetReference(newReference); err != nil { + return nil, fmt.Errorf("failed to set reference: %w", err) + } + checkoutOptions.Branch = localBranchReferenceName + + return checkoutOptions, nil + } + + if revHash, err = repo.ResolveRevision(plumbing.Revision(g.config.ref)); err != nil { + return nil, fmt.Errorf("failed to resolve revision: %w", err) + } + + checkoutOptions.Hash = plumbing.NewHash(revHash.String()) + + return checkoutOptions, nil +} + +func (g *gitCheckout) checkout(repo *git.Repository) error { + var ( + worktree *git.Worktree + checkoutOption *git.CheckoutOptions + err error + ) + + if worktree, err = repo.Worktree(); err != nil { + return fmt.Errorf("failed to get worktree: %w", err) + } + + if checkoutOption, err = g.getCheckoutOptions(repo); err != nil { + return err + } + + if err = worktree.Checkout(checkoutOption); err != nil { + return fmt.Errorf("failed to checkout branch: %w", err) + } + + return nil +} + +func (g *gitCheckout) saveCache() error { + var ( + workDir = os.DirFS(g.config.path) + isExisted bool + err error + ) + + if _, err = os.Stat(g.config.repoCachePath); err == nil { + isExisted = true + } + + if g.config.cache && !isExisted { + if err = os.CopyFS(g.config.repoCachePath, workDir); err != nil { + return fmt.Errorf("failed to copy git cache: %w", err) + } + } + + return nil +} + +func (g *gitCheckout) Run(ctx context.Context) error { + var ( + repo *git.Repository + err error + ) + + if err = g.rmWorkPathIfExists(); err != nil { + return err + } + + if repo, err = g.getRepo(); err != nil { + return err + } + + if err = g.fetch(ctx, repo); err != nil { + return err + } + + if err = g.checkout(repo); err != nil { + return err + } + + if err = g.saveCache(); err != nil { + return err + } + + return nil +} From 283ed757c86f6c5c9af78b922c58ad188dc0e0a6 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Mon, 12 May 2025 19:01:36 +0800 Subject: [PATCH 02/23] feat: change authMethod to gitCheckoutExecConfigDefinition method --- internal/digraph/executor/checkout.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/internal/digraph/executor/checkout.go b/internal/digraph/executor/checkout.go index 3c2001b2b1..80b8bda7d9 100644 --- a/internal/digraph/executor/checkout.go +++ b/internal/digraph/executor/checkout.go @@ -111,18 +111,29 @@ func (g *gitCheckoutExecAuthConfigDefinition) sshAuthMethod() (transport.AuthMet return publicKey, nil } -func (g *gitCheckoutExecAuthConfigDefinition) authMethod(repo string) (transport.AuthMethod, error) { - if len(g.SSHUser) == 0 { - g.SSHUser = defaultSSHUser +func (g *gitCheckoutExecConfigDefinition) authMethod() (transport.AuthMethod, error) { + var ( + endpoint *transport.Endpoint + err error + ) + + if endpoint, err = transport.NewEndpoint(g.Repo); err != nil { + return nil, fmt.Errorf("failed to create endpoint: %w", err) + } + + if len(g.Auth.SSHUser) == 0 { + g.Auth.SSHUser = defaultSSHUser + } + + if endpoint.Protocol == fileProtocol { + return nil, nil } - // example: https://github.com/dagu-org/dagu.git, use http auth - if strings.HasPrefix(repo, "https://") { - return g.httpAuthMethod() + if endpoint.Protocol == httpProtocol || endpoint.Protocol == httpsProtocol { + return g.Auth.httpAuthMethod() } - // example: git@github.com:dagu-org/dagu.git, use ssh auth - return g.sshAuthMethod() + return g.Auth.sshAuthMethod() } type gitCheckoutExecConfig struct { From 8800644a45e6daa46da17fbc274f1c5c5258d2b5 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Mon, 12 May 2025 19:02:29 +0800 Subject: [PATCH 03/23] feat: remove check logic --- internal/digraph/executor/checkout.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/digraph/executor/checkout.go b/internal/digraph/executor/checkout.go index 80b8bda7d9..0914225488 100644 --- a/internal/digraph/executor/checkout.go +++ b/internal/digraph/executor/checkout.go @@ -74,10 +74,6 @@ func (g *gitCheckoutExecAuthConfigDefinition) httpAuthMethod() (transport.AuthMe g.Password = os.Getenv(g.TokenEnv) } - if len(g.UserName) == 0 || len(g.Password) == 0 { - return nil, errGitCheckoutAuthConfigInvalid - } - return &gitHttp.BasicAuth{ Username: g.UserName, Password: g.Password, From 3ca0832557b6a86b242b6fc44880134669b94a4b Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Mon, 12 May 2025 19:24:53 +0800 Subject: [PATCH 04/23] feat: add file protocol support --- internal/digraph/executor/checkout.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/internal/digraph/executor/checkout.go b/internal/digraph/executor/checkout.go index 0914225488..2324ac054a 100644 --- a/internal/digraph/executor/checkout.go +++ b/internal/digraph/executor/checkout.go @@ -24,10 +24,9 @@ import ( const ( gitCheckOutExecutorType = "git-checkout" defaultSSHUser = "git" -) - -var ( - errGitCheckoutAuthConfigInvalid = fmt.Errorf("invalid git checkout auth config") + fileProtocol = "file" + httpProtocol = "http" + httpsProtocol = "https" ) var _ Executor = (*gitCheckout)(nil) @@ -48,16 +47,19 @@ type gitCheckoutExecConfigDefinition struct { func (g *gitCheckoutExecConfigDefinition) getRepoCachePath() string { // https://github.com/dagu-org/dagu.git -> github.com/dagu-org/dagu.git + // http://github.com/dagu-org/dagu.git -> github.com/dagu-org/dagu.git // git@github.com:dagu-org/dagu.git -> github.com/dagu-org/dagu.git + // file://github.com/dagu-org/dagu.git -> github.com/dagu-org/dagu.git repo := strings.TrimPrefix(g.Repo, "https://") + repo = strings.TrimPrefix(g.Repo, "http://") repo = strings.TrimPrefix(repo, "git@") + repo = strings.TrimPrefix(repo, "file://") return fmt.Sprintf("~/.cache/dagu/git/%s", repo) } type gitCheckoutExecAuthConfigDefinition struct { - Method string TokenEnv string UserName string Password string @@ -177,7 +179,7 @@ func newCheckout(_ context.Context, step digraph.Step) (Executor, error) { return nil, fmt.Errorf("failed to decode git checkout config: %w", err) } - if authMethod, err = def.Auth.authMethod(def.Repo); err != nil { + if authMethod, err = def.authMethod(); err != nil { return nil, err } From 243b743983a50c7db9a93289a28a0c2336b1e5ad Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Mon, 12 May 2025 19:25:08 +0800 Subject: [PATCH 05/23] fix: change use clone --- internal/digraph/executor/checkout.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/internal/digraph/executor/checkout.go b/internal/digraph/executor/checkout.go index 2324ac054a..69a0f57935 100644 --- a/internal/digraph/executor/checkout.go +++ b/internal/digraph/executor/checkout.go @@ -240,7 +240,6 @@ func (g *gitCheckout) getFetchOptions() (*git.FetchOptions, error) { var ( fetchOptions = &git.FetchOptions{ RemoteName: git.DefaultRemoteName, - RemoteURL: g.config.repo, Auth: g.authMethod, Depth: g.config.depth, RefSpecs: []config.RefSpec{ @@ -281,15 +280,13 @@ func (g *gitCheckout) initRepo() (*git.Repository, error) { err error ) - if repo, err = git.PlainInit(g.config.path, false); err != nil { - return nil, fmt.Errorf("failed to init git repository: %w", err) - } - - if _, err = repo.CreateRemote(&config.RemoteConfig{ - Name: git.DefaultRemoteName, - URLs: []string{g.config.repo}, + if repo, err = git.PlainCloneContext(context.Background(), g.config.path, false, &git.CloneOptions{ + URL: g.config.repo, + Progress: g.stdout, + Auth: g.authMethod, + Depth: g.config.depth, }); err != nil { - return nil, fmt.Errorf("failed to create remote repository: %w", err) + return nil, err } return repo, nil From c962c00a8ea0aab772bc8132a39fac0882fc8dd8 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Mon, 12 May 2025 19:56:13 +0800 Subject: [PATCH 06/23] feat: remove clone --- internal/digraph/executor/checkout.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/internal/digraph/executor/checkout.go b/internal/digraph/executor/checkout.go index 69a0f57935..a9b6cdc285 100644 --- a/internal/digraph/executor/checkout.go +++ b/internal/digraph/executor/checkout.go @@ -245,6 +245,7 @@ func (g *gitCheckout) getFetchOptions() (*git.FetchOptions, error) { RefSpecs: []config.RefSpec{ config.RefSpec(fmt.Sprintf("+refs/heads/%s:refs/remotes/origin/%s", g.config.ref, g.config.ref)), }, + Force: true, } ) @@ -280,13 +281,15 @@ func (g *gitCheckout) initRepo() (*git.Repository, error) { err error ) - if repo, err = git.PlainCloneContext(context.Background(), g.config.path, false, &git.CloneOptions{ - URL: g.config.repo, - Progress: g.stdout, - Auth: g.authMethod, - Depth: g.config.depth, + if repo, err = git.PlainInit(g.config.path, false); err != nil { + return nil, fmt.Errorf("failed to init git repository: %w", err) + } + + if _, err = repo.CreateRemote(&config.RemoteConfig{ + Name: git.DefaultRemoteName, + URLs: []string{g.config.repo}, }); err != nil { - return nil, err + return nil, fmt.Errorf("failed to create remote repository: %w", err) } return repo, nil From d17e9d06812db9c04a8899e530ca2b99852ddec0 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Mon, 12 May 2025 21:06:42 +0800 Subject: [PATCH 07/23] fix: cache path error --- internal/digraph/executor/checkout.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/internal/digraph/executor/checkout.go b/internal/digraph/executor/checkout.go index a9b6cdc285..f2ee45e383 100644 --- a/internal/digraph/executor/checkout.go +++ b/internal/digraph/executor/checkout.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "path/filepath" "regexp" "strings" @@ -46,6 +47,19 @@ type gitCheckoutExecConfigDefinition struct { } func (g *gitCheckoutExecConfigDefinition) getRepoCachePath() string { + var ( + homeDir string + err error + ) + + if homeDir, err = os.UserHomeDir(); err != nil { + if os.PathSeparator == '\\' { + homeDir = "C:\\Users\\Default" + } else { + homeDir = "/home/default" + } + } + // https://github.com/dagu-org/dagu.git -> github.com/dagu-org/dagu.git // http://github.com/dagu-org/dagu.git -> github.com/dagu-org/dagu.git // git@github.com:dagu-org/dagu.git -> github.com/dagu-org/dagu.git @@ -54,9 +68,11 @@ func (g *gitCheckoutExecConfigDefinition) getRepoCachePath() string { repo := strings.TrimPrefix(g.Repo, "https://") repo = strings.TrimPrefix(g.Repo, "http://") repo = strings.TrimPrefix(repo, "git@") - repo = strings.TrimPrefix(repo, "file://") + repo = strings.TrimPrefix(repo, "file:////") + + cacheDir := filepath.Join(homeDir, ".cache", "dagu", "git") - return fmt.Sprintf("~/.cache/dagu/git/%s", repo) + return filepath.Join(cacheDir, repo) } type gitCheckoutExecAuthConfigDefinition struct { From b24396b563a34df1c0fbea1d886688a4164fb4d7 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Mon, 12 May 2025 21:20:59 +0800 Subject: [PATCH 08/23] feat: add test-repo.git --- internal/testdata/test-repo.git | 1 + 1 file changed, 1 insertion(+) create mode 160000 internal/testdata/test-repo.git diff --git a/internal/testdata/test-repo.git b/internal/testdata/test-repo.git new file mode 160000 index 0000000000..07bb696689 --- /dev/null +++ b/internal/testdata/test-repo.git @@ -0,0 +1 @@ +Subproject commit 07bb6966890ae7733bd32f057f2131d48047e9b5 From a552f83eb0619884e5d60886dbdbed27485f0e4f Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Mon, 12 May 2025 21:21:14 +0800 Subject: [PATCH 09/23] feat: add git checkout executor uint test --- internal/digraph/executor/checkout_test.go | 309 +++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 internal/digraph/executor/checkout_test.go diff --git a/internal/digraph/executor/checkout_test.go b/internal/digraph/executor/checkout_test.go new file mode 100644 index 0000000000..32909eb5f3 --- /dev/null +++ b/internal/digraph/executor/checkout_test.go @@ -0,0 +1,309 @@ +package executor + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/transport" +) + +const ( + testTagRef = "v1.0.0" + testBranchRef = "main" + testCommitRef = "e2f9e8b0f3574ad7e12aa82c5b7c4c203fd0c4b0" +) + +var ( + testPath = os.TempDir() + "/test-git-checkout" +) + +func getTestGitCheckout(repo string, ref string, cache bool) *gitCheckout { + var ( + def = &gitCheckoutExecConfigDefinition{ + Ref: ref, + Repo: repo, + Path: testPath, + Depth: 1, + Progress: true, + Cache: cache, + } + defAuthMethod transport.AuthMethod + err error + ) + + if defAuthMethod, err = def.authMethod(); err != nil { + panic(err) + } + + return convertFromDef(def, defAuthMethod) +} + +type gitCheckoutCheckoutJudgeFunc func(path string) error + +type TestGitCheckoutTestCase struct { + msg string + ref string + cache bool + judge gitCheckoutCheckoutJudgeFunc +} + +func (t *TestGitCheckoutTestCase) cleanUp(executor *gitCheckout) error { + var ( + err error + ) + + if err = os.RemoveAll(executor.config.path); err != nil { + return err + } + + if executor.config.cache { + if err = os.RemoveAll(executor.config.repoCachePath); err != nil { + return err + } + } + + return nil +} + +// tagRefJudgeFunc is a judge function for tag reference +func tagRefJudgeFunc(path string) error { + var ( + repo *git.Repository + err error + ) + + if repo, err = git.PlainOpen(path); err != nil { + return err + } + + // Check if the tag reference exists + if _, err = repo.Tag(testTagRef); err != nil { + return err + } + + return nil +} + +// branchRefJudgeFunc is a judge function for branch reference +func branchRefJudgeFunc(path string) error { + var ( + repo *git.Repository + err error + ) + + if repo, err = git.PlainOpen(path); err != nil { + return err + } + + if _, err = repo.Reference(plumbing.NewBranchReferenceName(testBranchRef), false); err != nil { + if errors.Is(err, plumbing.ErrReferenceNotFound) { + // Return a friendly error for this one, versus just ReferenceNotFound. + return fmt.Errorf("branch %s not found", testBranchRef) + } + + return err + + } + + return nil +} + +// commitRefJudgeFunc is a judge function for commit reference +func commitRefJudgeFunc(path string) error { + var ( + repo *git.Repository + commitHash = plumbing.NewHash(testCommitRef) + err error + ) + + if repo, err = git.PlainOpen(path); err != nil { + return err + } + + // Check if the commit reference exists + if _, err = repo.CommitObject(commitHash); err != nil { + return err + } + + return nil +} + +func getTestGitCheckoutTestCases() []TestGitCheckoutTestCase { + return []TestGitCheckoutTestCase{ + { + msg: "tag reference", + ref: testTagRef, + cache: false, + judge: tagRefJudgeFunc, + }, + { + msg: "branch reference", + ref: testBranchRef, + cache: false, + judge: branchRefJudgeFunc, + }, + { + msg: "commit reference", + ref: testCommitRef, + cache: false, + judge: commitRefJudgeFunc, + }, + } +} + +func getFileRemotePath() (string, error) { + var ( + currentDir string + err error + ) + + if currentDir, err = os.Getwd(); err != nil { + return "", err + } + + // Get the current working directory,currentDir : "workspace/internal/digraph/executor" + // return the path of "workspace/internal/testdata/testrepo.git" + currentDir = currentDir[:len(currentDir)-len("/internal/digraph/executor")] + + return currentDir + "/internal/testdata/test-repo.git", nil +} + +// TestGitCheckout_Run is a test function for the GitCheckout executor. By git file protocol +func TestGitCheckout_Run(t *testing.T) { + var ( + testCases = getTestGitCheckoutTestCases() + repo string + err error + ) + + if repo, err = getFileRemotePath(); err != nil { + t.Fatalf("failed to get file remote path: %v", err) + } + + repo = fmt.Sprintf("file:///%s", filepath.ToSlash(repo)) + + for _, testCase := range testCases { + t.Run(testCase.msg, func(t *testing.T) { + var ( + testGitCheckout = getTestGitCheckout(repo, testCase.ref, testCase.cache) + ctx = context.Background() + cacheIsExists = true + ) + + if err = testGitCheckout.Run(ctx); err != nil { + t.Fatalf("failed to run git checkout: %v", err) + } + + if err = testCase.judge(testGitCheckout.config.path); err != nil { + t.Fatalf("failed to judge git checkout: %v", err) + } + + if _, err = os.Stat(testGitCheckout.config.repoCachePath); err != nil { + if errors.Is(err, os.ErrNotExist) { + cacheIsExists = false + } else { + t.Fatalf("failed to check cache path: %v", err) + } + } + + if testCase.cache != cacheIsExists { + t.Fatalf("cache path exists: %v, expected: %v", cacheIsExists, testCase.cache) + } + + if err = testCase.cleanUp(testGitCheckout); err != nil { + t.Fatalf("failed to clean up: %v", err) + } + }) + } +} + +func getCacheTestCache() []*TestGitCheckoutTestCase { + return []*TestGitCheckoutTestCase{ + { + msg: "first run with cache", + ref: testBranchRef, + cache: true, + judge: branchRefJudgeFunc, + }, + { + msg: "second run with cache", + ref: testTagRef, + cache: true, + judge: tagRefJudgeFunc, + }, + } +} + +func TestGitCheckoutWithCache(t *testing.T) { + var ( + testcaseList = getCacheTestCache() + firstRun = testcaseList[0] + secondRun = testcaseList[1] + firstExecutor *gitCheckout + secondExecutor *gitCheckout + repo string + err error + ) + + if repo, err = getFileRemotePath(); err != nil { + t.Fatalf("failed to get file remote path: %v", err) + } + + repo = fmt.Sprintf("file:///%s", filepath.ToSlash(repo)) + + firstExecutor = getTestGitCheckout(repo, firstRun.ref, firstRun.cache) + + // first run + if err = firstExecutor.Run(context.Background()); err != nil { + t.Fatalf("failed to run git checkout: %v", err) + } + + // check first run + if err = firstRun.judge(firstExecutor.config.path); err != nil { + t.Fatalf("failed to judge git checkout: %v", err) + } + + // check if the cache path exists + if _, err = os.Stat(firstExecutor.config.repoCachePath); err != nil { + t.Fatalf("failed to check cache path: %v", err) + } + + // check first run cache path + if err = firstRun.judge(firstExecutor.config.repoCachePath); err != nil { + t.Fatalf("failed to judge git checkout cache: %v", err) + } + + // second run + secondExecutor = getTestGitCheckout(repo, secondRun.ref, secondRun.cache) + if err = secondExecutor.Run(context.Background()); err != nil { + t.Fatalf("failed to run git checkout: %v", err) + } + + if err = secondRun.judge(secondExecutor.config.path); err != nil { + t.Fatalf("failed to judge git checkout: %v", err) + } + + // check if the cache path exists + if _, err = os.Stat(secondExecutor.config.repoCachePath); err != nil { + t.Fatalf("failed to check cache path: %v", err) + } + + // check second run cache path + if err = firstRun.judge(secondExecutor.config.repoCachePath); err != nil { + t.Fatalf("failed to judge git checkout cache: %v", err) + } + + if err = firstRun.cleanUp(firstExecutor); err != nil { + t.Fatalf("failed to clean up: %v", err) + } + + if err = secondRun.cleanUp(secondExecutor); err != nil { + t.Fatalf("failed to clean up: %v", err) + } +} From fb87259b789456fb11377ba793e4a9b5e3598a31 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Mon, 12 May 2025 21:30:45 +0800 Subject: [PATCH 10/23] fix: go version error --- go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.mod b/go.mod index e150677cc2..f99eecc523 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/dagu-org/dagu go 1.23.0 -toolchain go1.24.1 - require ( github.com/Masterminds/sprig/v3 v3.2.3 github.com/adrg/xdg v0.5.0 From e84915939cc0e671de7d709793d785da105e2a2f Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Mon, 12 May 2025 21:34:49 +0800 Subject: [PATCH 11/23] fix: style error --- internal/digraph/executor/checkout.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/internal/digraph/executor/checkout.go b/internal/digraph/executor/checkout.go index f2ee45e383..0d6a2e3b01 100644 --- a/internal/digraph/executor/checkout.go +++ b/internal/digraph/executor/checkout.go @@ -16,7 +16,7 @@ import ( "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/transport" - gitHttp "github.com/go-git/go-git/v5/plumbing/transport/http" + githttp "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/plumbing/transport/ssh" "github.com/go-git/go-git/v5/storage/filesystem" "github.com/go-viper/mapstructure/v2" @@ -65,14 +65,14 @@ func (g *gitCheckoutExecConfigDefinition) getRepoCachePath() string { // git@github.com:dagu-org/dagu.git -> github.com/dagu-org/dagu.git // file://github.com/dagu-org/dagu.git -> github.com/dagu-org/dagu.git - repo := strings.TrimPrefix(g.Repo, "https://") - repo = strings.TrimPrefix(g.Repo, "http://") - repo = strings.TrimPrefix(repo, "git@") - repo = strings.TrimPrefix(repo, "file:////") + repoName := strings.TrimPrefix(g.Repo, "https://") + repoName = strings.TrimPrefix(g.Repo, "http://") + repoName = strings.TrimPrefix(repoName, "git@") + repoName = strings.TrimPrefix(repoName, "file:////") cacheDir := filepath.Join(homeDir, ".cache", "dagu", "git") - return filepath.Join(cacheDir, repo) + return filepath.Join(cacheDir, repoName) } type gitCheckoutExecAuthConfigDefinition struct { @@ -92,7 +92,7 @@ func (g *gitCheckoutExecAuthConfigDefinition) httpAuthMethod() (transport.AuthMe g.Password = os.Getenv(g.TokenEnv) } - return &gitHttp.BasicAuth{ + return &githttp.BasicAuth{ Username: g.UserName, Password: g.Password, }, nil @@ -482,9 +482,5 @@ func (g *gitCheckout) Run(ctx context.Context) error { return err } - if err = g.saveCache(); err != nil { - return err - } - - return nil + return g.saveCache() } From 20f514990cec547f3df3d17fbaa6e346b820ed90 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Mon, 12 May 2025 21:55:34 +0800 Subject: [PATCH 12/23] feat: use regexp --- internal/digraph/executor/checkout.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/internal/digraph/executor/checkout.go b/internal/digraph/executor/checkout.go index 0d6a2e3b01..7a188d09bb 100644 --- a/internal/digraph/executor/checkout.go +++ b/internal/digraph/executor/checkout.go @@ -64,15 +64,11 @@ func (g *gitCheckoutExecConfigDefinition) getRepoCachePath() string { // http://github.com/dagu-org/dagu.git -> github.com/dagu-org/dagu.git // git@github.com:dagu-org/dagu.git -> github.com/dagu-org/dagu.git // file://github.com/dagu-org/dagu.git -> github.com/dagu-org/dagu.git - - repoName := strings.TrimPrefix(g.Repo, "https://") - repoName = strings.TrimPrefix(g.Repo, "http://") - repoName = strings.TrimPrefix(repoName, "git@") - repoName = strings.TrimPrefix(repoName, "file:////") - + re := regexp.MustCompile(`^(https?://|git@|file:////)`) + cleaned := re.ReplaceAllString(g.Repo, "") cacheDir := filepath.Join(homeDir, ".cache", "dagu", "git") - return filepath.Join(cacheDir, repoName) + return filepath.Join(cacheDir, cleaned) } type gitCheckoutExecAuthConfigDefinition struct { From de5bca1785df893c3e0b2f333b70d1f704905e15 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Mon, 12 May 2025 21:56:21 +0800 Subject: [PATCH 13/23] fix: ssh with ":" --- internal/digraph/executor/checkout.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/digraph/executor/checkout.go b/internal/digraph/executor/checkout.go index 7a188d09bb..0e7b2d13fa 100644 --- a/internal/digraph/executor/checkout.go +++ b/internal/digraph/executor/checkout.go @@ -66,6 +66,7 @@ func (g *gitCheckoutExecConfigDefinition) getRepoCachePath() string { // file://github.com/dagu-org/dagu.git -> github.com/dagu-org/dagu.git re := regexp.MustCompile(`^(https?://|git@|file:////)`) cleaned := re.ReplaceAllString(g.Repo, "") + cleaned = strings.ReplaceAll(cleaned, ":", "/") cacheDir := filepath.Join(homeDir, ".cache", "dagu", "git") return filepath.Join(cacheDir, cleaned) From de6b21e1d3df0df6875b79bb6273a77c84273c29 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Mon, 12 May 2025 21:56:53 +0800 Subject: [PATCH 14/23] feat: add get repo cache path unit test --- internal/digraph/executor/checkout_test.go | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/internal/digraph/executor/checkout_test.go b/internal/digraph/executor/checkout_test.go index 32909eb5f3..7ade1bd184 100644 --- a/internal/digraph/executor/checkout_test.go +++ b/internal/digraph/executor/checkout_test.go @@ -307,3 +307,71 @@ func TestGitCheckoutWithCache(t *testing.T) { t.Fatalf("failed to clean up: %v", err) } } + +type repoCachePathTestcase struct { + msg string + repo string + expectRepo string +} + +func getRepoCachePathTestcaseList() []*repoCachePathTestcase { + var ( + err error + homeDir string + expectRepo string + ) + + if homeDir, err = os.UserHomeDir(); err != nil { + if os.PathSeparator == '\\' { + homeDir = "C:\\Users\\Default" + } else { + homeDir = "/home/default" + } + } + + expectRepo = filepath.Join(homeDir, ".cache", "dagu", "git", "github.com", "dagu", "dagu.git") + + return []*repoCachePathTestcase{ + { + msg: "https protocol", + repo: "https://github.com/dagu/dagu.git", + expectRepo: expectRepo, + }, + { + msg: "http protocol", + repo: "http://github.com/dagu/dagu.git", + expectRepo: expectRepo, + }, + { + msg: "ssh protocol", + repo: "git@github.com:dagu/dagu.git", + expectRepo: expectRepo, + }, + { + msg: "file protocol", + repo: "file:////github.com/dagu/dagu.git", + expectRepo: expectRepo, + }, + } +} + +func TestGetRepoCachePath(t *testing.T) { + var ( + testCases = getRepoCachePathTestcaseList() + ) + + for _, testCase := range testCases { + t.Run(testCase.msg, func(t *testing.T) { + var ( + def = &gitCheckoutExecConfigDefinition{ + Repo: testCase.repo, + } + result = def.getRepoCachePath() + ) + + if result != testCase.expectRepo { + t.Fatalf("expected %s, got %s", testCase.expectRepo, result) + } + }) + } +} From a758732437e8d5df3a0906e04b9fae21ab2c0ee8 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Wed, 14 May 2025 16:26:32 +0800 Subject: [PATCH 15/23] feat: change root dir get method --- internal/digraph/executor/checkout_test.go | 32 +++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/internal/digraph/executor/checkout_test.go b/internal/digraph/executor/checkout_test.go index 7ade1bd184..89888a50a5 100644 --- a/internal/digraph/executor/checkout_test.go +++ b/internal/digraph/executor/checkout_test.go @@ -6,11 +6,13 @@ import ( "fmt" "os" "path/filepath" + "runtime" "testing" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/stretchr/testify/require" ) const ( @@ -157,32 +159,41 @@ func getTestGitCheckoutTestCases() []TestGitCheckoutTestCase { } } -func getFileRemotePath() (string, error) { +func getFileRemotePath(rootPath string) (string, error) { var ( - currentDir string - err error + err error ) - if currentDir, err = os.Getwd(); err != nil { + testRepoPath := filepath.Join(rootPath, "testdata", "test-repo.git") + + if _, err = os.Stat(testRepoPath); os.IsNotExist(err) { return "", err } - // Get the current working directory,currentDir : "workspace/internal/digraph/executor" - // return the path of "workspace/internal/testdata/testrepo.git" - currentDir = currentDir[:len(currentDir)-len("/internal/digraph/executor")] + return testRepoPath, nil +} + +// getProjectRoot returns the root directory of the project. +func getProjectRoot(t *testing.T) string { + t.Helper() + + _, filename, _, ok := runtime.Caller(1) + require.True(t, ok, "failed to get caller information") + rootDir := filepath.Join(filepath.Dir(filename), "..", "..") - return currentDir + "/internal/testdata/test-repo.git", nil + return filepath.Clean(rootDir) } // TestGitCheckout_Run is a test function for the GitCheckout executor. By git file protocol func TestGitCheckout_Run(t *testing.T) { var ( testCases = getTestGitCheckoutTestCases() + rootPath = getProjectRoot(t) repo string err error ) - if repo, err = getFileRemotePath(); err != nil { + if repo, err = getFileRemotePath(rootPath); err != nil { t.Fatalf("failed to get file remote path: %v", err) } @@ -243,6 +254,7 @@ func getCacheTestCache() []*TestGitCheckoutTestCase { func TestGitCheckoutWithCache(t *testing.T) { var ( testcaseList = getCacheTestCache() + rootPath = getProjectRoot(t) firstRun = testcaseList[0] secondRun = testcaseList[1] firstExecutor *gitCheckout @@ -251,7 +263,7 @@ func TestGitCheckoutWithCache(t *testing.T) { err error ) - if repo, err = getFileRemotePath(); err != nil { + if repo, err = getFileRemotePath(rootPath); err != nil { t.Fatalf("failed to get file remote path: %v", err) } From 6f1522870ec3f7d65d48d28372aedcb34bf5f5ab Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Wed, 14 May 2025 16:36:02 +0800 Subject: [PATCH 16/23] feat: change use testify assert --- internal/digraph/executor/checkout_test.go | 70 +++++++--------------- 1 file changed, 21 insertions(+), 49 deletions(-) diff --git a/internal/digraph/executor/checkout_test.go b/internal/digraph/executor/checkout_test.go index 89888a50a5..aadb464965 100644 --- a/internal/digraph/executor/checkout_test.go +++ b/internal/digraph/executor/checkout_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "os" "path/filepath" "runtime" @@ -12,6 +13,7 @@ import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -207,13 +209,8 @@ func TestGitCheckout_Run(t *testing.T) { cacheIsExists = true ) - if err = testGitCheckout.Run(ctx); err != nil { - t.Fatalf("failed to run git checkout: %v", err) - } - - if err = testCase.judge(testGitCheckout.config.path); err != nil { - t.Fatalf("failed to judge git checkout: %v", err) - } + assert.NoError(t, testGitCheckout.Run(ctx)) + assert.NoError(t, testCase.judge(testGitCheckout.config.path)) if _, err = os.Stat(testGitCheckout.config.repoCachePath); err != nil { if errors.Is(err, os.ErrNotExist) { @@ -223,13 +220,8 @@ func TestGitCheckout_Run(t *testing.T) { } } - if testCase.cache != cacheIsExists { - t.Fatalf("cache path exists: %v, expected: %v", cacheIsExists, testCase.cache) - } - - if err = testCase.cleanUp(testGitCheckout); err != nil { - t.Fatalf("failed to clean up: %v", err) - } + assert.Equal(t, testCase.cache, cacheIsExists) + assert.NoError(t, testCase.cleanUp(testGitCheckout)) }) } } @@ -262,62 +254,42 @@ func TestGitCheckoutWithCache(t *testing.T) { repo string err error ) - - if repo, err = getFileRemotePath(rootPath); err != nil { - t.Fatalf("failed to get file remote path: %v", err) - } + repo, err = getFileRemotePath(rootPath) + assert.NoError(t, err) repo = fmt.Sprintf("file:///%s", filepath.ToSlash(repo)) firstExecutor = getTestGitCheckout(repo, firstRun.ref, firstRun.cache) // first run - if err = firstExecutor.Run(context.Background()); err != nil { - t.Fatalf("failed to run git checkout: %v", err) - } + assert.NoError(t, firstExecutor.Run(context.Background())) // check first run - if err = firstRun.judge(firstExecutor.config.path); err != nil { - t.Fatalf("failed to judge git checkout: %v", err) - } + assert.NoError(t, firstRun.judge(firstExecutor.config.path)) // check if the cache path exists - if _, err = os.Stat(firstExecutor.config.repoCachePath); err != nil { - t.Fatalf("failed to check cache path: %v", err) - } + _, err = os.Stat(firstExecutor.config.repoCachePath) + assert.NoError(t, err) // check first run cache path - if err = firstRun.judge(firstExecutor.config.repoCachePath); err != nil { - t.Fatalf("failed to judge git checkout cache: %v", err) - } + assert.NoError(t, firstRun.judge(firstExecutor.config.repoCachePath)) // second run secondExecutor = getTestGitCheckout(repo, secondRun.ref, secondRun.cache) - if err = secondExecutor.Run(context.Background()); err != nil { - t.Fatalf("failed to run git checkout: %v", err) - } + assert.NoError(t, secondExecutor.Run(context.Background())) - if err = secondRun.judge(secondExecutor.config.path); err != nil { - t.Fatalf("failed to judge git checkout: %v", err) - } + assert.NoError(t, secondRun.judge(secondExecutor.config.path)) // check if the cache path exists - if _, err = os.Stat(secondExecutor.config.repoCachePath); err != nil { - t.Fatalf("failed to check cache path: %v", err) - } + _, err = os.Stat(secondExecutor.config.repoCachePath) + assert.NoError(t, err) // check second run cache path - if err = firstRun.judge(secondExecutor.config.repoCachePath); err != nil { - t.Fatalf("failed to judge git checkout cache: %v", err) - } + assert.NoError(t, firstRun.judge(secondExecutor.config.repoCachePath)) - if err = firstRun.cleanUp(firstExecutor); err != nil { - t.Fatalf("failed to clean up: %v", err) - } - - if err = secondRun.cleanUp(secondExecutor); err != nil { - t.Fatalf("failed to clean up: %v", err) - } + // clean up + assert.NoError(t, firstRun.cleanUp(firstExecutor)) + assert.NoError(t, secondRun.cleanUp(secondExecutor)) } type repoCachePathTestcase struct { From 950df0d8e7f403f6c7c326c45f15221d07ad4036 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Wed, 14 May 2025 17:00:17 +0800 Subject: [PATCH 17/23] feat: missing test-repo.git --- internal/testdata/test-repo.git | 1 - internal/testdata/test-repo.git/HEAD | 1 + internal/testdata/test-repo.git/config | 4 + internal/testdata/test-repo.git/description | 1 + .../test-repo.git/hooks/applypatch-msg.sample | 15 ++ .../test-repo.git/hooks/commit-msg.sample | 24 +++ .../hooks/fsmonitor-watchman.sample | 173 ++++++++++++++++++ .../test-repo.git/hooks/post-update.sample | 8 + .../test-repo.git/hooks/pre-applypatch.sample | 14 ++ .../test-repo.git/hooks/pre-commit.sample | 49 +++++ .../hooks/pre-merge-commit.sample | 13 ++ .../test-repo.git/hooks/pre-push.sample | 53 ++++++ .../test-repo.git/hooks/pre-rebase.sample | 169 +++++++++++++++++ .../test-repo.git/hooks/pre-receive.sample | 24 +++ .../hooks/prepare-commit-msg.sample | 42 +++++ .../hooks/push-to-checkout.sample | 78 ++++++++ .../test-repo.git/hooks/update.sample | 128 +++++++++++++ internal/testdata/test-repo.git/info/exclude | 6 + .../28/a7358ec92ae3bc8ee8329bc436712eb37c8a25 | Bin 0 -> 51 bytes .../4f/ef81a1ab5e07bbf87b678a7857a12b352b1f39 | Bin 0 -> 388 bytes .../d6/7fa53fd53611c90e6ff9af7d678e463c4016a6 | 1 + .../da/8a998d46f08ebac3f830cf9e87e4b21841fbf5 | Bin 0 -> 67 bytes .../e2/5aed53c630415498734a05f6d76011e32acdea | Bin 0 -> 348 bytes .../e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 | Bin 0 -> 15 bytes .../ed/ea1fe69fedabca523de095086d97528e08e256 | Bin 0 -> 58 bytes .../testdata/test-repo.git/refs/heads/master | 1 + .../testdata/test-repo.git/refs/tags/v1.0.0 | 1 + 27 files changed, 805 insertions(+), 1 deletion(-) delete mode 160000 internal/testdata/test-repo.git create mode 100644 internal/testdata/test-repo.git/HEAD create mode 100644 internal/testdata/test-repo.git/config create mode 100644 internal/testdata/test-repo.git/description create mode 100755 internal/testdata/test-repo.git/hooks/applypatch-msg.sample create mode 100755 internal/testdata/test-repo.git/hooks/commit-msg.sample create mode 100755 internal/testdata/test-repo.git/hooks/fsmonitor-watchman.sample create mode 100755 internal/testdata/test-repo.git/hooks/post-update.sample create mode 100755 internal/testdata/test-repo.git/hooks/pre-applypatch.sample create mode 100755 internal/testdata/test-repo.git/hooks/pre-commit.sample create mode 100755 internal/testdata/test-repo.git/hooks/pre-merge-commit.sample create mode 100755 internal/testdata/test-repo.git/hooks/pre-push.sample create mode 100755 internal/testdata/test-repo.git/hooks/pre-rebase.sample create mode 100755 internal/testdata/test-repo.git/hooks/pre-receive.sample create mode 100755 internal/testdata/test-repo.git/hooks/prepare-commit-msg.sample create mode 100755 internal/testdata/test-repo.git/hooks/push-to-checkout.sample create mode 100755 internal/testdata/test-repo.git/hooks/update.sample create mode 100644 internal/testdata/test-repo.git/info/exclude create mode 100644 internal/testdata/test-repo.git/objects/28/a7358ec92ae3bc8ee8329bc436712eb37c8a25 create mode 100644 internal/testdata/test-repo.git/objects/4f/ef81a1ab5e07bbf87b678a7857a12b352b1f39 create mode 100644 internal/testdata/test-repo.git/objects/d6/7fa53fd53611c90e6ff9af7d678e463c4016a6 create mode 100644 internal/testdata/test-repo.git/objects/da/8a998d46f08ebac3f830cf9e87e4b21841fbf5 create mode 100644 internal/testdata/test-repo.git/objects/e2/5aed53c630415498734a05f6d76011e32acdea create mode 100644 internal/testdata/test-repo.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 create mode 100644 internal/testdata/test-repo.git/objects/ed/ea1fe69fedabca523de095086d97528e08e256 create mode 100644 internal/testdata/test-repo.git/refs/heads/master create mode 100644 internal/testdata/test-repo.git/refs/tags/v1.0.0 diff --git a/internal/testdata/test-repo.git b/internal/testdata/test-repo.git deleted file mode 160000 index 07bb696689..0000000000 --- a/internal/testdata/test-repo.git +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 07bb6966890ae7733bd32f057f2131d48047e9b5 diff --git a/internal/testdata/test-repo.git/HEAD b/internal/testdata/test-repo.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/internal/testdata/test-repo.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/internal/testdata/test-repo.git/config b/internal/testdata/test-repo.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/internal/testdata/test-repo.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/internal/testdata/test-repo.git/description b/internal/testdata/test-repo.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/internal/testdata/test-repo.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/internal/testdata/test-repo.git/hooks/applypatch-msg.sample b/internal/testdata/test-repo.git/hooks/applypatch-msg.sample new file mode 100755 index 0000000000..a5d7b84a67 --- /dev/null +++ b/internal/testdata/test-repo.git/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/internal/testdata/test-repo.git/hooks/commit-msg.sample b/internal/testdata/test-repo.git/hooks/commit-msg.sample new file mode 100755 index 0000000000..b58d1184a9 --- /dev/null +++ b/internal/testdata/test-repo.git/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/internal/testdata/test-repo.git/hooks/fsmonitor-watchman.sample b/internal/testdata/test-repo.git/hooks/fsmonitor-watchman.sample new file mode 100755 index 0000000000..14ed0aa42d --- /dev/null +++ b/internal/testdata/test-repo.git/hooks/fsmonitor-watchman.sample @@ -0,0 +1,173 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 2) and last update token +# formatted as a string and outputs to stdout a new update token and +# all files that have been modified since the update token. Paths must +# be relative to the root of the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $last_update_token) = @ARGV; + +# Uncomment for debugging +# print STDERR "$0 $version $last_update_token\n"; + +# Check the hook interface version +if ($version ne 2) { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree = get_working_dir(); + +my $retry = 1; + +my $json_pkg; +eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; +} or do { + require JSON::PP; + $json_pkg = "JSON::PP"; +}; + +launch_watchman(); + +sub launch_watchman { + my $o = watchman_query(); + if (is_work_tree_watched($o)) { + output_result($o->{clock}, @{$o->{files}}); + } +} + +sub output_result { + my ($clockid, @files) = @_; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # binmode $fh, ":utf8"; + # print $fh "$clockid\n@files\n"; + # close $fh; + + binmode STDOUT, ":utf8"; + print $clockid; + print "\0"; + local $, = "\0"; + print @files; +} + +sub watchman_clock { + my $response = qx/watchman clock "$git_work_tree"/; + die "Failed to get clock id on '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + return $json_pkg->new->utf8->decode($response); +} + +sub watchman_query { + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $last_update_token but not from the .git folder. + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + if (substr($last_update_token, 0, 1) eq "c") { + $last_update_token = "\"$last_update_token\""; + } + my $query = <<" END"; + ["query", "$git_work_tree", { + "since": $last_update_token, + "fields": ["name"], + "expression": ["not", ["dirname", ".git"]] + }] + END + + # Uncomment for debugging the watchman query + # open (my $fh, ">", ".git/watchman-query.json"); + # print $fh $query; + # close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + # Uncomment for debugging the watch response + # open ($fh, ">", ".git/watchman-response.json"); + # print $fh $response; + # close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + return $json_pkg->new->utf8->decode($response); +} + +sub is_work_tree_watched { + my ($output) = @_; + my $error = $output->{error}; + if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { + $retry--; + my $response = qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + $output = $json_pkg->new->utf8->decode($response); + $error = $output->{error}; + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # close $fh; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + my $o = watchman_clock(); + $error = $output->{error}; + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + output_result($o->{clock}, ("/")); + $last_update_token = $o->{clock}; + + eval { launch_watchman() }; + return 0; + } + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + return 1; +} + +sub get_working_dir { + my $working_dir; + if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $working_dir = Win32::GetCwd(); + $working_dir =~ tr/\\/\//; + } else { + require Cwd; + $working_dir = Cwd::cwd(); + } + + return $working_dir; +} diff --git a/internal/testdata/test-repo.git/hooks/post-update.sample b/internal/testdata/test-repo.git/hooks/post-update.sample new file mode 100755 index 0000000000..ec17ec1939 --- /dev/null +++ b/internal/testdata/test-repo.git/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/internal/testdata/test-repo.git/hooks/pre-applypatch.sample b/internal/testdata/test-repo.git/hooks/pre-applypatch.sample new file mode 100755 index 0000000000..4142082bcb --- /dev/null +++ b/internal/testdata/test-repo.git/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/internal/testdata/test-repo.git/hooks/pre-commit.sample b/internal/testdata/test-repo.git/hooks/pre-commit.sample new file mode 100755 index 0000000000..e144712c85 --- /dev/null +++ b/internal/testdata/test-repo.git/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --type=bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/internal/testdata/test-repo.git/hooks/pre-merge-commit.sample b/internal/testdata/test-repo.git/hooks/pre-merge-commit.sample new file mode 100755 index 0000000000..399eab1924 --- /dev/null +++ b/internal/testdata/test-repo.git/hooks/pre-merge-commit.sample @@ -0,0 +1,13 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git merge" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message to +# stderr if it wants to stop the merge commit. +# +# To enable this hook, rename this file to "pre-merge-commit". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" +: diff --git a/internal/testdata/test-repo.git/hooks/pre-push.sample b/internal/testdata/test-repo.git/hooks/pre-push.sample new file mode 100755 index 0000000000..4ce688d32b --- /dev/null +++ b/internal/testdata/test-repo.git/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/internal/testdata/test-repo.git/hooks/pre-rebase.sample b/internal/testdata/test-repo.git/hooks/pre-rebase.sample new file mode 100755 index 0000000000..6cbef5c370 --- /dev/null +++ b/internal/testdata/test-repo.git/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/internal/testdata/test-repo.git/hooks/pre-receive.sample b/internal/testdata/test-repo.git/hooks/pre-receive.sample new file mode 100755 index 0000000000..a1fd29ec14 --- /dev/null +++ b/internal/testdata/test-repo.git/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/internal/testdata/test-repo.git/hooks/prepare-commit-msg.sample b/internal/testdata/test-repo.git/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000..10fa14c5ab --- /dev/null +++ b/internal/testdata/test-repo.git/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/internal/testdata/test-repo.git/hooks/push-to-checkout.sample b/internal/testdata/test-repo.git/hooks/push-to-checkout.sample new file mode 100755 index 0000000000..af5a0c0018 --- /dev/null +++ b/internal/testdata/test-repo.git/hooks/push-to-checkout.sample @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin &2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --type=bool hooks.allowunannotated) +allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) +denycreatebranch=$(git config --type=bool hooks.denycreatebranch) +allowdeletetag=$(git config --type=bool hooks.allowdeletetag) +allowmodifytag=$(git config --type=bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero=$(git hash-object --stdin &2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/internal/testdata/test-repo.git/info/exclude b/internal/testdata/test-repo.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/internal/testdata/test-repo.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/internal/testdata/test-repo.git/objects/28/a7358ec92ae3bc8ee8329bc436712eb37c8a25 b/internal/testdata/test-repo.git/objects/28/a7358ec92ae3bc8ee8329bc436712eb37c8a25 new file mode 100644 index 0000000000000000000000000000000000000000..6bed66b00e16bbd0a42af0f2d97bbcfdf59f6a6c GIT binary patch literal 51 zcmbN$tS--9r`^A+@P()U2NwqC H140r2>BJO; literal 0 HcmV?d00001 diff --git a/internal/testdata/test-repo.git/objects/4f/ef81a1ab5e07bbf87b678a7857a12b352b1f39 b/internal/testdata/test-repo.git/objects/4f/ef81a1ab5e07bbf87b678a7857a12b352b1f39 new file mode 100644 index 0000000000000000000000000000000000000000..94b67cab573b004d55460cb1e670c450f56ab4b4 GIT binary patch literal 388 zcmV-~0ek*<0hN$jZ<|06h5Nj}V&APcyDXO_YO6YiI@mx;VC%+j%U%Ej#vd z>RVrWq>;{iqZy5k82Y|3&>{GP8I^*PQ||jz+O9@am0S=_2@={@)Khk8VQOPtY6qP7 zsE!6o*VEh~S~|qVSolb}TKimk(|{`5CBjD7 z#L$0**t0zg6XaQNfha;?qSJpy{r_fls-Bw)ew=j|uA>y@`8~{|b-FCrWBA4aWH^l& z>h71bvZ;b(cCjGo5bu7r$${adXlGVgKJ)lxL?k=cmZHB8+`jHSdB`<@*0#7|->GwR zVMmk3S2k}1d#gjUdDs4xufyqDG_`ld%vL0QEl zrKawUXnu8@q?Ii8cV=NdRPAZet*sxyPF9)IcKH}MW0@tg#BLD)D6^+zqZ1^CeDlCo iD#GzH`~u$N2-DzSVF0bTxrJOxSdRQCYVZe1w4uco63SBm literal 0 HcmV?d00001 diff --git a/internal/testdata/test-repo.git/objects/d6/7fa53fd53611c90e6ff9af7d678e463c4016a6 b/internal/testdata/test-repo.git/objects/d6/7fa53fd53611c90e6ff9af7d678e463c4016a6 new file mode 100644 index 0000000000..28aea3d03a --- /dev/null +++ b/internal/testdata/test-repo.git/objects/d6/7fa53fd53611c90e6ff9af7d678e463c4016a6 @@ -0,0 +1 @@ +xOo@{OaYڪ`؀IM`0qO_h[.}Ho~yM6Mc/}V+UO)3ELA =30:Ae:'iHIP8tDpwm|ڝ`|o%M-m_JM2:Fb:׫,ű,QSg88pFG8zaZeFL.*|YH¹#|}1eV]bUA0Itk9vj2w%W;C;pd3_aFv:ᑮOSPq/ݒ->H6mbYT{PlyGz3pWu zxydz}oM%~5LY?BfvXBxYDhM{HjC)8(9K}>hicv3$b=)Nyk`X2`703XhDz|w7 z_sON&R^kAke%Yh0r0WyYb3YY+_CKD%tqzCTf0Y5&Qk0KStXA+=JNO2co|0F`0Hlfl literal 0 HcmV?d00001 diff --git a/internal/testdata/test-repo.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/internal/testdata/test-repo.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 new file mode 100644 index 0000000000000000000000000000000000000000..711223894375fe1186ac5bfffdc48fb1fa1e65cc GIT binary patch literal 15 Wcmb6WiT`_Ff%bxNY2mA%`9PfHuur&O&6~@dv|NDE04Ny=t)oT QM5v0A#B{9E06Y*Nua}k>?f?J) literal 0 HcmV?d00001 diff --git a/internal/testdata/test-repo.git/refs/heads/master b/internal/testdata/test-repo.git/refs/heads/master new file mode 100644 index 0000000000..a2a7bcbf58 --- /dev/null +++ b/internal/testdata/test-repo.git/refs/heads/master @@ -0,0 +1 @@ +4fef81a1ab5e07bbf87b678a7857a12b352b1f39 diff --git a/internal/testdata/test-repo.git/refs/tags/v1.0.0 b/internal/testdata/test-repo.git/refs/tags/v1.0.0 new file mode 100644 index 0000000000..31aeb64082 --- /dev/null +++ b/internal/testdata/test-repo.git/refs/tags/v1.0.0 @@ -0,0 +1 @@ +d67fa53fd53611c90e6ff9af7d678e463c4016a6 From 99cf469dfb23a11592b5ce38472ba4f8bcd005ee Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Wed, 14 May 2025 17:10:25 +0800 Subject: [PATCH 18/23] feat: change ref & add allowReachableSHA1InWant = true config --- internal/digraph/executor/checkout_test.go | 4 ++-- internal/testdata/test-repo.git/config | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/digraph/executor/checkout_test.go b/internal/digraph/executor/checkout_test.go index aadb464965..dcc5f6068b 100644 --- a/internal/digraph/executor/checkout_test.go +++ b/internal/digraph/executor/checkout_test.go @@ -19,8 +19,8 @@ import ( const ( testTagRef = "v1.0.0" - testBranchRef = "main" - testCommitRef = "e2f9e8b0f3574ad7e12aa82c5b7c4c203fd0c4b0" + testBranchRef = "master" + testCommitRef = "e25aed53c630415498734a05f6d76011e32acdea" ) var ( diff --git a/internal/testdata/test-repo.git/config b/internal/testdata/test-repo.git/config index 07d359d07c..231ccea5f5 100644 --- a/internal/testdata/test-repo.git/config +++ b/internal/testdata/test-repo.git/config @@ -2,3 +2,5 @@ repositoryformatversion = 0 filemode = true bare = true +[uploadpack] + allowReachableSHA1InWant = true From e1af37f6bb6bc81293c74a34a05db74cd0571246 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Wed, 14 May 2025 21:13:36 +0800 Subject: [PATCH 19/23] feat: checkout add refType --- internal/digraph/executor/checkout.go | 90 ++++++++++++++++----------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/internal/digraph/executor/checkout.go b/internal/digraph/executor/checkout.go index 0e7b2d13fa..f8ce841889 100644 --- a/internal/digraph/executor/checkout.go +++ b/internal/digraph/executor/checkout.go @@ -36,6 +36,16 @@ func init() { Register(gitCheckOutExecutorType, newCheckout) } +type refType string + +const ( + refTypeEmpty refType = "" + refTypeBranch refType = "branch" + refTypeTag refType = "tag" + refTypeCommit refType = "commit" + refTypeRefs refType = "refs" +) + type gitCheckoutExecConfigDefinition struct { Repo string Ref string @@ -72,6 +82,31 @@ func (g *gitCheckoutExecConfigDefinition) getRepoCachePath() string { return filepath.Join(cacheDir, cleaned) } +func (g *gitCheckoutExecConfigDefinition) getRefType() (refType, error) { + var ( + tagRegex = regexp.MustCompile(`^v(\d+\.)+\d+$`) + hashRegex = regexp.MustCompile(`^[0-9a-f]{40}$`) + ) + + if g.Ref == "" { + return refTypeEmpty, errors.New("ref is required") + } + + if strings.HasPrefix(g.Ref, "refs") { + return refTypeRefs, nil + } + + if tagRegex.MatchString(g.Ref) { + return refTypeTag, nil + } + + if hashRegex.MatchString(g.Ref) { + return refTypeCommit, nil + } + + return refTypeBranch, nil +} + type gitCheckoutExecAuthConfigDefinition struct { TokenEnv string UserName string @@ -150,6 +185,7 @@ func (g *gitCheckoutExecConfigDefinition) authMethod() (transport.AuthMethod, er type gitCheckoutExecConfig struct { repo string ref string + refType refType path string depth int progress bool @@ -164,7 +200,7 @@ type gitCheckout struct { config *gitCheckoutExecConfig } -func convertFromDef(def *gitCheckoutExecConfigDefinition, authMethod transport.AuthMethod) *gitCheckout { +func convertFromDef(def *gitCheckoutExecConfigDefinition, authMethod transport.AuthMethod, executorRefType refType) *gitCheckout { return &gitCheckout{ stdout: os.Stdout, stderr: os.Stderr, @@ -176,6 +212,7 @@ func convertFromDef(def *gitCheckoutExecConfigDefinition, authMethod transport.A progress: def.Progress, cache: def.Cache, repoCachePath: def.getRepoCachePath(), + refType: executorRefType, }, authMethod: authMethod, } @@ -183,9 +220,10 @@ func convertFromDef(def *gitCheckoutExecConfigDefinition, authMethod transport.A func newCheckout(_ context.Context, step digraph.Step) (Executor, error) { var ( - def = &gitCheckoutExecConfigDefinition{} - authMethod transport.AuthMethod - err error + def = &gitCheckoutExecConfigDefinition{} + authMethod transport.AuthMethod + executorRefType refType + err error ) if err = decodeGitCheckoutConfig(step.ExecutorConfig.Config, def); err != nil { @@ -196,7 +234,11 @@ func newCheckout(_ context.Context, step digraph.Step) (Executor, error) { return nil, err } - return convertFromDef(def, authMethod), nil + if executorRefType, err = def.getRefType(); err != nil { + return nil, fmt.Errorf("failed to parse ref type: %w", err) + } + + return convertFromDef(def, authMethod, executorRefType), nil } func decodeGitCheckoutConfig(data map[string]any, config *gitCheckoutExecConfigDefinition) error { @@ -266,19 +308,15 @@ func (g *gitCheckout) getFetchOptions() (*git.FetchOptions, error) { fetchOptions.Progress = g.stdout } - if len(g.config.ref) == 0 { - return nil, fmt.Errorf("ref is empty") + if g.config.refType == refTypeRefs { + fetchOptions.RefSpecs = []config.RefSpec{config.RefSpec(fmt.Sprintf("+%s:%s", g.config.ref, g.config.ref))} } - if strings.HasPrefix(g.config.ref, "refs/") { - fetchOptions.RefSpecs = []config.RefSpec{config.RefSpec(g.config.ref)} - } - - if g.isTag() { + if g.config.refType == refTypeTag { fetchOptions.RefSpecs = []config.RefSpec{config.RefSpec(fmt.Sprintf("+refs/tags/%s:refs/tags/%s", g.config.ref, g.config.ref))} } - if g.isHash() { + if g.config.refType == refTypeCommit { hash := plumbing.NewHash(g.config.ref) fetchOptions.RefSpecs = []config.RefSpec{config.RefSpec(fmt.Sprintf("+%[1]s:%[1]s", hash, hash))} fetchOptions.Tags = git.NoTags @@ -363,35 +401,17 @@ func (g *gitCheckout) fetch(ctx context.Context, repo *git.Repository) error { return nil } -// isTag checks if the ref is a tag or a short tag -func (g *gitCheckout) isTag() bool { - var ( - tagRegex = regexp.MustCompile(`^refs/tags/.*$`) - shortTagRegex = regexp.MustCompile(`^v(\d+\.)+\d+$`) - ) - - return tagRegex.MatchString(g.config.ref) || shortTagRegex.MatchString(g.config.ref) -} - -// isHash checks if the ref is a hash -func (g *gitCheckout) isHash() bool { - var ( - hashRegex = regexp.MustCompile(`^[0-9a-f]{40}$`) - ) - - return hashRegex.MatchString(g.config.ref) -} - func (g *gitCheckout) getCheckoutOptions(repo *git.Repository) (*git.CheckoutOptions, error) { var ( - isHash = g.isHash() - isTag = g.isTag() + isHash = g.config.refType == refTypeCommit + isTag = g.config.refType == refTypeTag + isRefs = g.config.refType == refTypeRefs checkoutOptions = &git.CheckoutOptions{} revHash *plumbing.Hash err error ) - if !isHash && !isTag { + if !isHash && !isTag && !isRefs { checkoutOptions.Force = true localBranchReferenceName := plumbing.NewBranchReferenceName(g.config.ref) From 6e49b2ce0c43c85da4d4c83bdc469a9b85e77254 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Wed, 14 May 2025 21:13:51 +0800 Subject: [PATCH 20/23] feat: add more testcase --- internal/digraph/executor/checkout_test.go | 58 ++++++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/internal/digraph/executor/checkout_test.go b/internal/digraph/executor/checkout_test.go index dcc5f6068b..48ee7a4c61 100644 --- a/internal/digraph/executor/checkout_test.go +++ b/internal/digraph/executor/checkout_test.go @@ -18,13 +18,15 @@ import ( ) const ( - testTagRef = "v1.0.0" - testBranchRef = "master" - testCommitRef = "e25aed53c630415498734a05f6d76011e32acdea" + testTagRef = "v1.0.0" + testBranchRef = "master" + testCommitRef = "e25aed53c630415498734a05f6d76011e32acdea" + testBranchStartWithRef = "refs/heads/master" + testTagStartWithRef = "refs/tags/v1.0.0" ) var ( - testPath = os.TempDir() + "/test-git-checkout" + testPath = "." + "/test-git-checkout" ) func getTestGitCheckout(repo string, ref string, cache bool) *gitCheckout { @@ -38,6 +40,7 @@ func getTestGitCheckout(repo string, ref string, cache bool) *gitCheckout { Cache: cache, } defAuthMethod transport.AuthMethod + defRefType refType err error ) @@ -45,7 +48,11 @@ func getTestGitCheckout(repo string, ref string, cache bool) *gitCheckout { panic(err) } - return convertFromDef(def, defAuthMethod) + if defRefType, err = def.getRefType(); err != nil { + panic(err) + } + + return convertFromDef(def, defAuthMethod, defRefType) } type gitCheckoutCheckoutJudgeFunc func(path string) error @@ -138,6 +145,35 @@ func commitRefJudgeFunc(path string) error { return nil } +// startWithRefJudgeFunc is a judge function for branch reference start with refs +func startWithRefJudgeFunc(path string, ref string) error { + var ( + repo *git.Repository + err error + ) + + if repo, err = git.PlainOpen(path); err != nil { + return err + } + + // Check if the branch reference exists + if _, err = repo.Reference(plumbing.ReferenceName(ref), false); err != nil { + return err + } + + return nil +} + +// branchStartWithRefJudgeFunc is a judge function for branch reference start with refs +func branchStartWithRefJudgeFunc(path string) error { + return startWithRefJudgeFunc(path, testBranchStartWithRef) +} + +// tagStartWithRefJudgeFunc is a judge function for tag reference start with refs +func tagStartWithRefJudgeFunc(path string) error { + return startWithRefJudgeFunc(path, testTagStartWithRef) +} + func getTestGitCheckoutTestCases() []TestGitCheckoutTestCase { return []TestGitCheckoutTestCase{ { @@ -158,6 +194,18 @@ func getTestGitCheckoutTestCases() []TestGitCheckoutTestCase { cache: false, judge: commitRefJudgeFunc, }, + { + msg: "branch reference start with refs", + ref: testBranchStartWithRef, + cache: false, + judge: branchStartWithRefJudgeFunc, + }, + { + msg: "tag reference start with refs", + ref: testTagStartWithRef, + cache: false, + judge: tagStartWithRefJudgeFunc, + }, } } From dd6eadba8d64c9f6e3e98f0fedb2ee91b2f8857d Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Fri, 16 May 2025 14:39:41 +0800 Subject: [PATCH 21/23] feat: add authMethod uint test --- internal/digraph/executor/checkout_test.go | 183 ++++++++++++++++++++- 1 file changed, 182 insertions(+), 1 deletion(-) diff --git a/internal/digraph/executor/checkout_test.go b/internal/digraph/executor/checkout_test.go index 48ee7a4c61..52de02588e 100644 --- a/internal/digraph/executor/checkout_test.go +++ b/internal/digraph/executor/checkout_test.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "os" "path/filepath" "runtime" @@ -13,6 +12,7 @@ import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/go-git/go-git/v5/plumbing/transport/ssh" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -407,3 +407,184 @@ func TestGetRepoCachePath(t *testing.T) { }) } } + +type authMethodTestcase struct { + msg string + def *gitCheckoutExecConfigDefinition + expectAuthName string + expectAuthMethod bool + expectErr bool +} + +const testRSAPrivateKey = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAovxthlB+eWsz4Vboz5wN1LzdXMroFsKZIJ70hNqKO0VK0eGP +9Ndigg+eAyHIFHdsJIIPQUUKLoSNkcR+PTDit7eXkIOf65kjMG5Pycj8z+eCE2Cz +xVmzLM6ZkKLYO/K8ROwJihKRGmdmPa6bGM1kWhXvrnPLK82KsJYUM3Ku7MLIedkw +5uKdWLaTniUgRaHoulOa7+1QTnZuK/KxW6U3Dhci43EOvEQ3ZJHpvio+jn1vC86R +PlkgaKUd+eOmSEDdGihs1CQvfQZMLVuiSUkFEkIacbA0QsQfZzZIJ0N6Q7oqjroO +gkw1sDE8pIaAJz7aNBi+kRaFMiFbgpsMrZltHQIDAQABAoIBACs131evuYg5Srzg +TMLV7bjMBagXR2bZWr2SRuN+CQ3jtg1kzsSr4br3pv3Pk/sRGkOnk6HLSwLAM8RE +ou9YKZNpgi5XJyvQIssxQ8gMmDIKf6rhhWe5+03SzFXTRp7GIPHo3jKT75JffXS2 ++PmfYo6bqDrJCkFnsfBVKa/mJMgyA45NUoZc3iAW7KMJhJWDLuJ+ZHJj85r+iAxG +Aszmu9soXdMBaA5l8YYICoTCM2hTcbVAGrtCT69o/VOEgZSG5ykSrRxBFmwMhjJY +R8XcaeOgI5pfKLMb/CJ/qUSsAovNpc7BLzcVYBIY3AOMTtbGTnobxWjKCAYc+QB2 +C0K5PSECgYEA1FKRWVE5T9lwP6hD2SMX74mPxEZbuxgzcedrs53raITmoMS1naDi +0ZqaTYOi4VYYgyojsP07ALow14QUMtBRLythl9z/CM5qtI61mkfJEkCNAxlRrxeQ +NmqND9jP591ymkPjFnhn3cp/Hx3SJdfodrxUkDPgHhkSaBLaPMGdkYUCgYEAxIOt +nHKeWSq/AH51zN+P/mGtWwi+RURf6EMVII7z66bJU+E+nbWGjrSBqHInLeq/zI/a +d2r9k9yXLvtUpK3PiOv3BgPqmov4KUPwr46DrgqJCKIhVGGTP79MNXU6DEA75nMj +BnlM5GgbE8+Dpt2XHEhdLyMB6K384rUPc14PdLkCgYEAnye9eHxgP7C4aZ9SLKQX +vyEYuYIcJOUBOzLEEwIfgluNHZoWobAGFiST4eL453zIJxohYvyPi/4FuqdxFJ3/ +HSKhp1qreghxCCOpkZqZ6KqmiVojVuKM4Z2BXA2j2ySuUWDuCtv6z9CI9eQ+sMtl +oAuQQAAC0cztdUIcgUqJOJkCgYAOzcChXX0SSIcU+XHUWi8VwbP2fKUgwLLc41jP +GBXF9c2K1RgLd2ZIj86IqvjKm7mRJnEVt+icX+y/rE1HDpTowqXcPSVKOSsbqLOT +9g9zZ/XEwbnzClq2XanXCRqzW49nn9rOnQqu1izcBDDtvBmrFsR2TZPSPHElfvBI +B5jweQKBgDth1kPUitS9Lvcrc8YHklxizQ0J3zUTBJUAjwPU25RhtG7qia6lNCUr +Clcy8Xl3GK8Gv72Sv1VMJsg0mjYSFa2eMLdnoKlN3oIOZ4238V4p+ZgG22/R1Kmr +lyln/UHGhbpjOyuBlhqm26eURqB1jlOC39vwPsvrwAa273Mw/kQN +-----END RSA PRIVATE KEY----- +` + +func getAuthMethodTestcaseList() ([]*authMethodTestcase, error) { + var ( + err error + testCaseList = make([]*authMethodTestcase, 0, 4) + tempRSAPrivateKeyFile *os.File + ) + + if tempRSAPrivateKeyFile, err = os.CreateTemp(".", "test_rsa"); err != nil { + return nil, err + } + + if _, err = tempRSAPrivateKeyFile.WriteString(testRSAPrivateKey); err != nil { + return nil, err + } + + testCaseList = append(testCaseList, &authMethodTestcase{}) + + return []*authMethodTestcase{ + { + msg: "ssh protocol with ssh agent", + def: &gitCheckoutExecConfigDefinition{ + Repo: "git@github.com:dagu/dagu.git", + Auth: gitCheckoutExecAuthConfigDefinition{ + SSHAgent: true, + SSHUser: "dagu", + }, + }, + expectAuthName: ssh.PublicKeysCallbackName, + expectAuthMethod: true, + expectErr: false, + }, + { + msg: "ssh protocol with ssh key file not exist", + def: &gitCheckoutExecConfigDefinition{ + Repo: "git@github.com:dagu/dagu.git", + Auth: gitCheckoutExecAuthConfigDefinition{ + SSHKey: "not_exist", + }, + }, + expectAuthMethod: false, + expectErr: true, + }, + { + msg: "ssh protocol with ssh key file exist", + def: &gitCheckoutExecConfigDefinition{ + Repo: "git@github.com:dagu/dagu.git", + Auth: gitCheckoutExecAuthConfigDefinition{ + SSHKey: tempRSAPrivateKeyFile.Name(), + }, + }, + expectAuthName: ssh.PublicKeysName, + expectAuthMethod: true, + expectErr: false, + }, + { + msg: "https protocol with username and password", + def: &gitCheckoutExecConfigDefinition{ + Repo: "https://github.com/dagu/dagu.git", + Auth: gitCheckoutExecAuthConfigDefinition{ + UserName: "dagu", + Password: "dagu", + }, + }, + expectAuthName: "http-basic-auth", + expectAuthMethod: true, + expectErr: false, + }, + { + msg: "https protocol with username and token", + def: &gitCheckoutExecConfigDefinition{ + Repo: "https://github.com/dagu/dagu.git", + Auth: gitCheckoutExecAuthConfigDefinition{ + UserName: "dagu", + TokenEnv: "DAGU_TOKEN", + }, + }, + expectAuthName: "http-basic-auth", + expectAuthMethod: true, + expectErr: false, + }, + { + msg: "http protocol with username and password", + def: &gitCheckoutExecConfigDefinition{ + Repo: "http://github.com/dagu/dagu.git", + Auth: gitCheckoutExecAuthConfigDefinition{ + UserName: "dagu", + Password: "dagu", + }, + }, + expectAuthName: "http-basic-auth", + expectAuthMethod: true, + expectErr: false, + }, + { + msg: "http protocol with username and token", + def: &gitCheckoutExecConfigDefinition{ + Repo: "http://github.com/dagu/dagu.git", + Auth: gitCheckoutExecAuthConfigDefinition{ + UserName: "dagu", + TokenEnv: "DAGU_TOKEN", + }, + }, + expectAuthName: "http-basic-auth", + expectAuthMethod: true, + expectErr: false, + }, + { + msg: "file protocol", + def: &gitCheckoutExecConfigDefinition{ + Repo: "file:///tmp/dagu", + }, + expectAuthMethod: false, + expectErr: false, + }, + }, nil +} + +func TestAuthMethod(t *testing.T) { + assert.NoError(t, os.Setenv("DAGU_TOKEN", "dagu")) + defer func() { + assert.NoError(t, os.Unsetenv("DAGU_TOKEN")) + }() + + testCaseList, err := getAuthMethodTestcaseList() + require.NoError(t, err) + + for _, testCase := range testCaseList { + t.Run(testCase.msg, func(t *testing.T) { + var ( + authMethod transport.AuthMethod + ) + + authMethod, err = testCase.def.authMethod() + + assert.Equal(t, testCase.expectAuthMethod, authMethod != nil) + assert.Equal(t, testCase.expectErr, err != nil) + if testCase.expectAuthMethod { + assert.Equal(t, testCase.expectAuthName, authMethod.Name()) + } + }) + } +} From 5ca727ba04bdeb5d47e9f4d88cb1d58b5d293880 Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Fri, 16 May 2025 14:41:57 +0800 Subject: [PATCH 22/23] feat: remove useless code --- internal/digraph/executor/checkout_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/digraph/executor/checkout_test.go b/internal/digraph/executor/checkout_test.go index 52de02588e..9d8f91f78f 100644 --- a/internal/digraph/executor/checkout_test.go +++ b/internal/digraph/executor/checkout_test.go @@ -449,7 +449,6 @@ lyln/UHGhbpjOyuBlhqm26eURqB1jlOC39vwPsvrwAa273Mw/kQN func getAuthMethodTestcaseList() ([]*authMethodTestcase, error) { var ( err error - testCaseList = make([]*authMethodTestcase, 0, 4) tempRSAPrivateKeyFile *os.File ) @@ -461,8 +460,6 @@ func getAuthMethodTestcaseList() ([]*authMethodTestcase, error) { return nil, err } - testCaseList = append(testCaseList, &authMethodTestcase{}) - return []*authMethodTestcase{ { msg: "ssh protocol with ssh agent", From 2f2c25d6eaf1b7750d0fe73309fde6ebe5f9a39e Mon Sep 17 00:00:00 2001 From: halalala222 <1741196223@qq.com> Date: Fri, 16 May 2025 15:09:22 +0800 Subject: [PATCH 23/23] feat: remove ssh agent test --- internal/digraph/executor/checkout_test.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/internal/digraph/executor/checkout_test.go b/internal/digraph/executor/checkout_test.go index 9d8f91f78f..e36eb8e1c6 100644 --- a/internal/digraph/executor/checkout_test.go +++ b/internal/digraph/executor/checkout_test.go @@ -461,19 +461,6 @@ func getAuthMethodTestcaseList() ([]*authMethodTestcase, error) { } return []*authMethodTestcase{ - { - msg: "ssh protocol with ssh agent", - def: &gitCheckoutExecConfigDefinition{ - Repo: "git@github.com:dagu/dagu.git", - Auth: gitCheckoutExecAuthConfigDefinition{ - SSHAgent: true, - SSHUser: "dagu", - }, - }, - expectAuthName: ssh.PublicKeysCallbackName, - expectAuthMethod: true, - expectErr: false, - }, { msg: "ssh protocol with ssh key file not exist", def: &gitCheckoutExecConfigDefinition{