commit f249cc2a4d0d3d70f5a402dcab24c01f71d3c0a1 Author: onyx-and-iris Date: Thu May 29 00:30:08 2025 +0100 exclude initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd6b23f --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Auto-generated .gitignore by gignore: github.com/onyx-and-iris/gignore + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin/ + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +# End of gignore: github.com/onyx-and-iris/gignore diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..29058bb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright © 2025 onyx-and-iris + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this 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. + +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. diff --git a/cmd/add.go b/cmd/add.go new file mode 100644 index 0000000..0a8b3e3 --- /dev/null +++ b/cmd/add.go @@ -0,0 +1,41 @@ +package cmd + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" +) + +// addCmd represents the add command +var addCmd = &cobra.Command{ + Use: "add", + Short: "Add patterns to the exclude file", + Long: `The add command allows you to add one or more patterns to the .git/info/exclude file. +This is useful for excluding files or directories from version control without modifying the .gitignore file.`, + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + f, ok := FileFromContext(cmd.Context()) + if !ok { + return fmt.Errorf("no exclude file found in context") + } + return runAddCommand(f, args) + }, +} + +func init() { + rootCmd.AddCommand(addCmd) +} + +func runAddCommand(f io.Writer, args []string) error { + for _, pattern := range args { + if _, err := fmt.Fprintln(f, pattern); err != nil { + return fmt.Errorf("error writing to exclude file: %w", err) + } + } + fmt.Println("Patterns added to .git/info/exclude file:") + for _, pattern := range args { + fmt.Println(" -", pattern) + } + return nil +} diff --git a/cmd/context.go b/cmd/context.go new file mode 100644 index 0000000..3343063 --- /dev/null +++ b/cmd/context.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "context" + "os" +) + +type contextKey string + +const fileContextKey contextKey = "excludeFile" + +// ContextWithFile returns a new context with the given file set. +func ContextWithFile(ctx context.Context, file *os.File) context.Context { + return context.WithValue(ctx, fileContextKey, file) +} + +// FileFromContext retrieves the file from the context, if it exists. +func FileFromContext(ctx context.Context) (*os.File, bool) { + file, ok := ctx.Value(fileContextKey).(*os.File) + return file, ok +} diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..9cf9e05 --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,59 @@ +package cmd + +import ( + "bufio" + "fmt" + "io" + "strings" + + "github.com/spf13/cobra" +) + +// listCmd represents the list command +var listCmd = &cobra.Command{ + Use: "list", + Short: "List all patterns in the exclude file", + Long: `The list command reads the .git/info/exclude file and prints all patterns +that are not empty and do not start with a comment (#). +This is useful for reviewing which files or directories are currently excluded from version control.`, + Args: cobra.NoArgs, // No arguments expected + RunE: func(cmd *cobra.Command, args []string) error { + f, ok := FileFromContext(cmd.Context()) + if !ok { + return fmt.Errorf("no exclude file found in context") + } + return runListCommand(f, args) + }, +} + +func init() { + rootCmd.AddCommand(listCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // listCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // listCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +// runListCommand is the function that will be executed when the list command is called +func runListCommand(f io.Reader, _ []string) error { + // Read from the exclude file line by line + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + if line != "" && !strings.HasPrefix(line, "#") { + fmt.Println(line) // Print each non-empty, non-comment line + } + } + if err := scanner.Err(); err != nil { + return fmt.Errorf("error reading exclude file: %w", err) + } + + // You can add more functionality as needed + return nil +} diff --git a/cmd/reset.go b/cmd/reset.go new file mode 100644 index 0000000..83ad6f8 --- /dev/null +++ b/cmd/reset.go @@ -0,0 +1,50 @@ +package cmd + +import ( + _ "embed" + "fmt" + "os" + + "github.com/spf13/cobra" +) + +// resetCmd represents the reset command +var resetCmd = &cobra.Command{ + Use: "reset", + Short: "Reset the exclude file", + Long: `The reset command clears all patterns from the .git/info/exclude file. +This is useful for starting fresh or removing all exclusions at once.`, + RunE: func(cmd *cobra.Command, _ []string) error { + f, ok := FileFromContext(cmd.Context()) + if !ok { + return fmt.Errorf("no exclude file found in context") + } + return runResetCommand(f) + }, +} + +func init() { + rootCmd.AddCommand(resetCmd) +} + +//go:embed template/exclude +var defaultExcludeFile string + +// runResetCommand clears the exclude file +func runResetCommand(f *os.File) error { + // Clear the exclude file by truncating it + if err := f.Truncate(0); err != nil { + return fmt.Errorf("error truncating exclude file: %w", err) + } + // Reset the file pointer to the beginning + if _, err := f.Seek(0, 0); err != nil { + return fmt.Errorf("error seeking to the beginning of exclude file: %w", err) + } + + // Write the default exclude patterns to the file + if _, err := f.WriteString(defaultExcludeFile); err != nil { + return fmt.Errorf("error writing default exclude file: %w", err) + } + + return nil +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..2a437fe --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,65 @@ +package cmd + +import ( + "context" + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "exclude", + Short: "A command line tool to manage .git/info/exclude files", + Long: `Exclude is a command line tool designed to help you manage your .git/info/exclude files. +It allows you to add, list, and remove patterns from the exclude file easily. +This tool is particularly useful for developers who want to keep their repository clean +by excluding certain files or directories from version control.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + // This function runs before any command is executed. + // You can use it to set up global configurations or checks. + // For example, you might want to check if the .git directory exists + if _, err := os.Stat(".git"); os.IsNotExist(err) { + cmd.Println("Error: This command must be run in a Git repository.") + os.Exit(1) + } + + f, err := os.OpenFile(".git/info/exclude", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + cmd.Println("Error opening .git/info/exclude file:", err) + os.Exit(1) + } + + ctx := context.WithValue(context.Background(), fileContextKey, f) + cmd.SetContext(ctx) + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + if f, ok := FileFromContext(cmd.Context()); ok { + defer f.Close() + } + }, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.exclude.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/template/exclude b/cmd/template/exclude new file mode 100644 index 0000000..a5196d1 --- /dev/null +++ b/cmd/template/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/go.mod b/go.mod new file mode 100644 index 0000000..e69be8d --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/onyx-and-iris/exclude + +go 1.24.3 + +require github.com/spf13/cobra v1.9.1 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.6 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ffae55e --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..ab28313 --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/onyx-and-iris/exclude/cmd" + +func main() { + cmd.Execute() +}