diff --git a/go.mod b/go.mod index 3ed2e3d639..f99eecc523 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/dagu-org/dagu -go 1.23 +go 1.23.0 require ( github.com/Masterminds/sprig/v3 v3.2.3 @@ -8,6 +8,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 +37,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 +46,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 +58,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 +87,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 +98,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 +107,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 +126,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 +136,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 +153,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 +200,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 +212,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 +222,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 +251,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 +275,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 +295,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..f8ce841889 --- /dev/null +++ b/internal/digraph/executor/checkout.go @@ -0,0 +1,503 @@ +package executor + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "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" + fileProtocol = "file" + httpProtocol = "http" + httpsProtocol = "https" +) + +var _ Executor = (*gitCheckout)(nil) + +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 + Path string + Depth int + Progress bool + Cache bool + Auth gitCheckoutExecAuthConfigDefinition +} + +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 + // 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) +} + +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 + 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) + } + + 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 *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 + } + + if endpoint.Protocol == httpProtocol || endpoint.Protocol == httpsProtocol { + return g.Auth.httpAuthMethod() + } + + return g.Auth.sshAuthMethod() +} + +type gitCheckoutExecConfig struct { + repo string + ref string + refType refType + 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, executorRefType refType) *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(), + refType: executorRefType, + }, + authMethod: authMethod, + } +} + +func newCheckout(_ context.Context, step digraph.Step) (Executor, error) { + var ( + def = &gitCheckoutExecConfigDefinition{} + authMethod transport.AuthMethod + executorRefType refType + 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.authMethod(); err != nil { + return nil, err + } + + 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 { + 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, + 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)), + }, + Force: true, + } + ) + + if g.config.progress { + fetchOptions.Progress = g.stdout + } + + if g.config.refType == refTypeRefs { + fetchOptions.RefSpecs = []config.RefSpec{config.RefSpec(fmt.Sprintf("+%s:%s", g.config.ref, g.config.ref))} + } + + 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.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 + } + + 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 +} + +func (g *gitCheckout) getCheckoutOptions(repo *git.Repository) (*git.CheckoutOptions, error) { + var ( + 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 && !isRefs { + 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 + } + + return g.saveCache() +} diff --git a/internal/digraph/executor/checkout_test.go b/internal/digraph/executor/checkout_test.go new file mode 100644 index 0000000000..e36eb8e1c6 --- /dev/null +++ b/internal/digraph/executor/checkout_test.go @@ -0,0 +1,574 @@ +package executor + +import ( + "context" + "errors" + "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/go-git/go-git/v5/plumbing/transport/ssh" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + testTagRef = "v1.0.0" + testBranchRef = "master" + testCommitRef = "e25aed53c630415498734a05f6d76011e32acdea" + testBranchStartWithRef = "refs/heads/master" + testTagStartWithRef = "refs/tags/v1.0.0" +) + +var ( + testPath = "." + "/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 + defRefType refType + err error + ) + + if defAuthMethod, err = def.authMethod(); err != nil { + panic(err) + } + + if defRefType, err = def.getRefType(); err != nil { + panic(err) + } + + return convertFromDef(def, defAuthMethod, defRefType) +} + +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 +} + +// 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{ + { + 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, + }, + { + msg: "branch reference start with refs", + ref: testBranchStartWithRef, + cache: false, + judge: branchStartWithRefJudgeFunc, + }, + { + msg: "tag reference start with refs", + ref: testTagStartWithRef, + cache: false, + judge: tagStartWithRefJudgeFunc, + }, + } +} + +func getFileRemotePath(rootPath string) (string, error) { + var ( + err error + ) + + testRepoPath := filepath.Join(rootPath, "testdata", "test-repo.git") + + if _, err = os.Stat(testRepoPath); os.IsNotExist(err) { + return "", err + } + + 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 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(rootPath); 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 + ) + + 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) { + cacheIsExists = false + } else { + t.Fatalf("failed to check cache path: %v", err) + } + } + + assert.Equal(t, testCase.cache, cacheIsExists) + assert.NoError(t, testCase.cleanUp(testGitCheckout)) + }) + } +} + +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() + rootPath = getProjectRoot(t) + firstRun = testcaseList[0] + secondRun = testcaseList[1] + firstExecutor *gitCheckout + secondExecutor *gitCheckout + repo string + err error + ) + 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 + assert.NoError(t, firstExecutor.Run(context.Background())) + + // check first run + assert.NoError(t, firstRun.judge(firstExecutor.config.path)) + + // check if the cache path exists + _, err = os.Stat(firstExecutor.config.repoCachePath) + assert.NoError(t, err) + + // check first run cache path + assert.NoError(t, firstRun.judge(firstExecutor.config.repoCachePath)) + + // second run + secondExecutor = getTestGitCheckout(repo, secondRun.ref, secondRun.cache) + assert.NoError(t, secondExecutor.Run(context.Background())) + + assert.NoError(t, secondRun.judge(secondExecutor.config.path)) + + // check if the cache path exists + _, err = os.Stat(secondExecutor.config.repoCachePath) + assert.NoError(t, err) + + // check second run cache path + assert.NoError(t, firstRun.judge(secondExecutor.config.repoCachePath)) + + // clean up + assert.NoError(t, firstRun.cleanUp(firstExecutor)) + assert.NoError(t, secondRun.cleanUp(secondExecutor)) +} + +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) + } + }) + } +} + +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 + 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 + } + + return []*authMethodTestcase{ + { + 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()) + } + }) + } +} 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..231ccea5f5 --- /dev/null +++ b/internal/testdata/test-repo.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true +[uploadpack] + allowReachableSHA1InWant = 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 0000000000..6bed66b00e Binary files /dev/null and b/internal/testdata/test-repo.git/objects/28/a7358ec92ae3bc8ee8329bc436712eb37c8a25 differ 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 0000000000..94b67cab57 Binary files /dev/null and b/internal/testdata/test-repo.git/objects/4f/ef81a1ab5e07bbf87b678a7857a12b352b1f39 differ 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/ݒ-