This commit is contained in:
Frank Mayer 2025-01-05 06:30:54 +01:00
commit 0155bf8365
Signed by: tsukinoko-kun
GPG Key ID: 427B3E61E69C2E51
13 changed files with 590 additions and 0 deletions

26
LICENSE Normal file
View File

@ -0,0 +1,26 @@
MIT NON-AI License
Copyright (c) 2025, Frank Mayer
Permission is hereby granted, free of charge, to any person obtaining a copy of the software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
In addition, the following restrictions apply:
1. The Software and any modifications made to it may not be used for the purpose of training or improving machine learning algorithms,
including but not limited to artificial intelligence, natural language processing, or data mining. This condition applies to any derivatives,
modifications, or updates based on the Software code. Any usage of the Software in an AI-training dataset is considered a breach of this License.
2. The Software may not be included in any dataset used for training or improving machine learning algorithms,
including but not limited to artificial intelligence, natural language processing, or data mining.
3. Any person or organization found to be in violation of these restrictions will be subject to legal action and may be held liable
for any damages resulting from such use.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

24
cmd/init.go Normal file
View File

@ -0,0 +1,24 @@
package cmd
import (
"fmt"
"git-roast/internal/roast"
"os"
"github.com/spf13/cobra"
)
var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize a new git-roast repository.",
Run: func(cmd *cobra.Command, args []string) {
if err := roast.Init(); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
},
}
func init() {
rootCmd.AddCommand(initCmd)
}

70
cmd/request.go Normal file
View File

@ -0,0 +1,70 @@
package cmd
import (
"errors"
"fmt"
"git-roast/internal/roast"
"os"
"github.com/spf13/cobra"
)
var requestCmd = &cobra.Command{
Use: "request",
Short: "Request a merge from one branch into another.",
Example: "git roast req from feat1 into main",
RunE: func(cmd *cobra.Command, args []string) error {
roast, err := roast.Open()
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
return nil
}
var from string
var into string
switch len(args) {
case 2:
if args[0] == "into" {
if current, ok := roast.CurrentBranchName(); ok {
from = current
}
} else {
from = args[0]
}
into = args[1]
case 3:
if args[1] == "into" {
from = args[0]
into = args[2]
} else if args[1] == "from" {
into = args[0]
from = args[2]
}
case 4:
if args[0] == "from" && args[2] == "into" {
from = args[1]
into = args[3]
} else if args[0] == "into" && args[2] == "from" {
into = args[1]
from = args[3]
}
}
if len(from) == 0 || len(into) == 0 {
return errors.New("invalid `from` and `into` branch names")
}
if err := roast.Request(from, into); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
return nil
}
return nil
},
Aliases: []string{"req"},
}
func init() {
rootCmd.AddCommand(requestCmd)
}

29
cmd/root.go Normal file
View File

@ -0,0 +1,29 @@
package cmd
import (
"git-roast/internal/config"
"os"
"runtime/debug"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "git-roast",
Short: "Decentralized code review platform.",
}
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
if info, ok := debug.ReadBuildInfo(); ok {
rootCmd.Version = info.Main.Version
}
rootCmd.PersistentFlags().BoolVar(&config.Verbose, "verbose", false, "verbose output to stdout")
}

24
cmd/version.go Normal file
View File

@ -0,0 +1,24 @@
package cmd
import (
"fmt"
"runtime/debug"
"github.com/spf13/cobra"
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "print the program version",
Run: func(cmd *cobra.Command, args []string) {
if info, ok := debug.ReadBuildInfo(); ok {
fmt.Println(info.Main.Version)
} else {
fmt.Println("(devel)")
}
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}

36
go.mod Normal file
View File

@ -0,0 +1,36 @@
module git-roast
go 1.23.3
require (
github.com/go-git/go-git/v5 v5.13.1
github.com/spf13/cobra v1.8.1
)
require (
dario.cat/mergo v1.0.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.1.3 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.1 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/mmcloughlin/avo v0.6.0 // indirect
github.com/pjbgf/sha1cd v0.3.1 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/tools v0.28.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

118
go.sum Normal file
View File

@ -0,0 +1,118 @@
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
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/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
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/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ=
github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
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.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA=
github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE=
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.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M=
github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc=
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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
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/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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY=
github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pjbgf/sha1cd v0.3.1 h1:Dh2GYdpJnO84lIw0LJwTFXjcNbasP/bklicSznyAaPI=
github.com/pjbgf/sha1cd v0.3.1/go.mod h1:Y8t7jSB/dEI/lQE04A1HVKteqjj9bX5O4+Cex0TCu8s=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
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/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
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/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

23
internal/config/config.go Normal file
View File

@ -0,0 +1,23 @@
package config
import (
"os"
"path/filepath"
)
var (
Verbose bool
ContentPath string
)
func init() {
if xdgDataHome, ok := os.LookupEnv("XDG_DATA_HOME"); ok {
ContentPath = filepath.Join(xdgDataHome, "git-roast")
} else if home, err := os.UserHomeDir(); err == nil {
ContentPath = filepath.Join(home, ".local", "git-roast")
}
if err := os.MkdirAll(ContentPath, 0700); err != nil {
panic(err)
}
}

16
internal/log/log.go Normal file
View File

@ -0,0 +1,16 @@
package log
import (
"fmt"
"git-roast/internal/config"
)
func Printf(format string, a ...any) {
_, _ = fmt.Printf(format, a...)
}
func Debugf(format string, a ...any) {
if config.Verbose {
_, _ = fmt.Printf(format, a...)
}
}

10
internal/roast/info.go Normal file
View File

@ -0,0 +1,10 @@
package roast
func (r *Roast) CurrentBranchName() (string, bool) {
head, err := r.repo.Head()
if err != nil {
return "", false
}
return head.Name().Short(), true
}

31
internal/roast/request.go Normal file
View File

@ -0,0 +1,31 @@
package roast
import (
"errors"
"fmt"
"git-roast/internal/log"
)
func (r *Roast) Request(from string, into string) error {
log.Debugf("requesting merge from %s into %s\n", from, into)
sourceBranch, err := r.repo.Branch(from)
if err != nil {
return errors.Join(fmt.Errorf("failed to get source branch %s", from), err)
}
targetBranch, err := r.repo.Branch(into)
if err != nil {
return errors.Join(fmt.Errorf("failed to get target branch %s", from), err)
}
id := fmt.Sprintf(
"from_%s_into_%s",
sourceBranch.Name,
targetBranch.Name,
)
fmt.Println(id)
return nil
}

176
internal/roast/roast.go Normal file
View File

@ -0,0 +1,176 @@
package roast
import (
"context"
"errors"
"fmt"
"git-roast/internal/config"
"git-roast/internal/log"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/go-git/go-git/v5"
)
type Roast struct {
repo *git.Repository
dir string
}
func getRepoOrigin() (string, error) {
repo, err := git.PlainOpen(".")
if err != nil {
return "", errors.Join(errors.New("failed to open repository"), err)
}
origin, err := repo.Remote("origin")
if err != nil {
return "", errors.Join(errors.New("failed to open remote origin"), err)
}
originConfig := origin.Config()
if originConfig == nil {
return "", errors.New("failed to read origin config")
}
if len(originConfig.URLs) == 0 {
return "", errors.New("no urls in remote origin")
}
return originConfig.URLs[0], nil
}
func Init() error {
repoOriginUrl, err := getRepoOrigin()
if err != nil {
return err
}
id := url.PathEscape(repoOriginUrl)
p := filepath.Join(config.ContentPath, id)
initSuccessful := false
defer func() {
if !initSuccessful {
_ = os.RemoveAll(p)
}
}()
if err := os.RemoveAll(p); err != nil && !os.IsNotExist(err) {
return err
}
var roastRepoOriginUrl string
if s, hasSuffix := strings.CutSuffix(repoOriginUrl, ".git"); hasSuffix {
roastRepoOriginUrl = s + ".roast.git"
} else {
roastRepoOriginUrl = s + ".roast"
}
{
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
cmd := exec.CommandContext(
ctx,
"git",
"clone",
roastRepoOriginUrl,
p,
)
if out, err := cmd.CombinedOutput(); err != nil {
log.Printf("failed to clone %s\n%s\n%s\n", roastRepoOriginUrl, out, err.Error())
} else {
log.Debugf("cloned %s\n", roastRepoOriginUrl)
repo, err := git.PlainOpen(p)
if err != nil {
return errors.Join(errors.New("failed to open roast repo"), err)
}
if commitIter, err := repo.CommitObjects(); err != nil {
return errors.Join(errors.New("failed to get commits for roast repo"), err)
} else if _, err := commitIter.Next(); err != nil {
// repo has no commits
// git commit --allow-empty -m init
cmd = exec.Command("git", "commit", "--allow-empty", "-m", "init")
cmd.Dir = p
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Join(fmt.Errorf("failed to add initial commit to roast repo\n%s\n", out), err)
}
// git push -u origin main
cmd = exec.Command("git", "push", "-u", "origin", "main")
cmd.Dir = p
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Join(fmt.Errorf("failed to push roast repo to %s\n%s\n", roastRepoOriginUrl, out), err)
}
log.Debugf("roast repository init pushed to %s\n", roastRepoOriginUrl)
}
initSuccessful = true
return nil
}
}
{
// git init
cmd := exec.Command("git", "init", p)
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Join(fmt.Errorf("failed to init %s\n%s\n", roastRepoOriginUrl, out), err)
}
log.Debugf("init %s successful\n", roastRepoOriginUrl)
// git remote add origin $roastRepoOriginUrl
cmd = exec.Command("git", "remote", "add", "origin", roastRepoOriginUrl)
cmd.Dir = p
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Join(fmt.Errorf("failed to add remote %s to roast repo\n%s\n", roastRepoOriginUrl, out), err)
}
// git commit --allow-empty -m init
cmd = exec.Command("git", "commit", "--allow-empty", "-m", "init")
cmd.Dir = p
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Join(fmt.Errorf("failed to add initial commit to roast repo\n%s\n", out), err)
}
// git push -u origin main
cmd = exec.Command("git", "push", "-u", "origin", "main")
cmd.Dir = p
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Join(fmt.Errorf("failed to push roast repo to %s\n%s\n", roastRepoOriginUrl, out), err)
}
log.Debugf("new roast repository pushed to %s\n", roastRepoOriginUrl)
}
initSuccessful = true
return nil
}
func Open() (*Roast, error) {
repoOriginUrl, err := getRepoOrigin()
if err != nil {
return nil, err
}
id := url.PathEscape(repoOriginUrl)
p := filepath.Join(config.ContentPath, id)
if stat, err := os.Stat(p); err != nil {
if os.IsNotExist(err) {
return nil, errors.New("roast repo not initialized, use `git roast init`")
} else {
return nil, errors.Join(errors.New("failed to check if roast dir for repo exists"), err)
}
} else if !stat.IsDir() {
return nil, errors.Join(errors.New("path for roast dir for repo exists but is not a directory"), err)
}
roastRepo, err := git.PlainOpen(p)
if err != nil {
return nil, errors.Join(fmt.Errorf("failed to open roast repo %s", p), err)
}
return &Roast{
repo: roastRepo,
dir: p,
}, nil
}

7
main.go Normal file
View File

@ -0,0 +1,7 @@
package main
import "git-roast/cmd"
func main() {
cmd.Execute()
}