Commit 440e152
Changed files (1)
cmd
cmd/init.go
@@ -22,6 +22,7 @@ const (
func NewInitCmd() *cobra.Command {
var issuesDir string
var issuesBranch string
+ var remoteName string
var cmd = &cobra.Command{
Use: "init",
@@ -29,18 +30,215 @@ func NewInitCmd() *cobra.Command {
Long: `Initialize tissue issue tracking by creating an orphan 'issues' branch
with proper structure and setting up a git worktree for easy access.`,
RunE: func(cmd *cobra.Command, args []string) error {
- return initRunE(cmd, args, issuesDir, issuesBranch)
+ return initRunE(cmd, args, issuesDir, issuesBranch, remoteName)
},
}
// flags
cmd.Flags().StringVar(&issuesDir, "issues-dir", _issuesDirDefault, "Directory name for the issues worktree (git worktree)")
cmd.Flags().StringVar(&issuesBranch, "issues-branch", _issuesBranchDefault, "Branch name for issues tracking")
+ cmd.Flags().StringVar(&remoteName, "remote", "", "Remote to use for pushing the issues branch")
return cmd
}
-func initRunE(cmd *cobra.Command, args []string, issuesDir string, issuesBranch string) error {
+// checkRemoteBranches checks all remotes for the existence of a branch
+func checkRemoteBranches(repo *git.Repository, branchName string) (map[string]bool, error) {
+ remoteBranches := make(map[string]bool)
+
+ remotes, err := repo.Remotes()
+ if err != nil {
+ return nil, err
+ }
+
+ for _, remote := range remotes {
+ // List remote references
+ refs, err := remote.List(&git.ListOptions{})
+ if err != nil {
+ // Skip unreachable remotes
+ fmt.Printf("Warning: Could not list refs for remote %s: %v\n", remote.Config().Name, err)
+ continue
+ }
+
+ // Check if the branch exists on this remote
+ for _, ref := range refs {
+ if ref.Name() == plumbing.ReferenceName("refs/heads/"+branchName) {
+ remoteBranches[remote.Config().Name] = true
+ break
+ }
+ }
+ }
+
+ return remoteBranches, nil
+}
+
+// determineTargetRemote determines which remote to use for push operations
+func determineTargetRemote(repo *git.Repository, requestedRemote string, numRemotes int) string {
+ if requestedRemote != "" {
+ // Validate that requested remote exists
+ remotes, _ := repo.Remotes()
+ for _, remote := range remotes {
+ if remote.Config().Name == requestedRemote {
+ return requestedRemote
+ }
+ }
+ // Remote doesn't exist - will error later
+ return ""
+ }
+
+ if numRemotes == 0 {
+ return ""
+ }
+
+ // Case 2: Single remote (use it regardless of name)
+ if numRemotes == 1 {
+ remotes, _ := repo.Remotes()
+ return remotes[0].Config().Name
+ }
+
+ // Case 3: Multiple remotes without --remote flag
+ // Don't push automatically
+ return ""
+}
+
+// checkoutAndTrackRemoteBranch fetches and tracks an existing remote branch
+func checkoutAndTrackRemoteBranch(repo *git.Repository, remoteName, branchName, issuesDir string) error {
+ // Fetch the remote branch
+ remote, err := repo.Remote(remoteName)
+ if err != nil {
+ return fmt.Errorf("getting remote %s: %w", remoteName, err)
+ }
+
+ fmt.Printf("Fetching '%s' branch from remote '%s'...\n", branchName, remoteName)
+ err = remote.Fetch(&git.FetchOptions{
+ RefSpecs: []config.RefSpec{
+ config.RefSpec(fmt.Sprintf("+refs/heads/%s:refs/remotes/%s/%s", branchName, remoteName, branchName)),
+ },
+ })
+ if err != nil && err != git.NoErrAlreadyUpToDate {
+ return fmt.Errorf("fetching remote branch: %w", err)
+ }
+
+ // Create local branch tracking the remote
+ refName := plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branchName))
+ remoteRef := plumbing.ReferenceName(fmt.Sprintf("refs/remotes/%s/%s", remoteName, branchName))
+
+ // Get the remote branch reference
+ ref, err := repo.Reference(remoteRef, true)
+ if err != nil {
+ return fmt.Errorf("getting remote branch reference: %w", err)
+ }
+
+ // Create local branch pointing to same commit
+ err = repo.Storer.SetReference(plumbing.NewHashReference(refName, ref.Hash()))
+ if err != nil {
+ return fmt.Errorf("creating local branch: %w", err)
+ }
+
+ // Set up tracking
+ cfg, err := repo.Config()
+ if err != nil {
+ return fmt.Errorf("getting repo config: %w", err)
+ }
+
+ cfg.Branches[branchName] = &config.Branch{
+ Name: branchName,
+ Remote: remoteName,
+ Merge: refName,
+ }
+
+ err = repo.Storer.SetConfig(cfg)
+ if err != nil {
+ return fmt.Errorf("setting branch tracking: %w", err)
+ }
+
+ // Setup worktree
+ fmt.Printf("Setting up git worktree at %s...\n", issuesDir)
+ worktreeCmd := exec.Command("git", "worktree", "add", issuesDir, branchName)
+ worktreeCmd.Stdout = os.Stdout
+ worktreeCmd.Stderr = os.Stderr
+ err = worktreeCmd.Run()
+ if err != nil {
+ fmt.Printf("Manual worktree setup: git worktree add %s %s\n", issuesDir, branchName)
+ return fmt.Errorf("creating worktree: %w", err)
+ }
+
+ // Add issuesDir/ to .gitignore in main branch
+ worktree, err := repo.Worktree()
+ if err != nil {
+ return fmt.Errorf("getting worktree: %w", err)
+ }
+
+ // Get current branch to return to it
+ head, err := repo.Head()
+ if err != nil {
+ return fmt.Errorf("getting HEAD: %w", err)
+ }
+
+ gitignoreMainPath := ".gitignore"
+ var gitignoreMainContent []byte
+
+ // Check if .gitignore exists
+ if file, err := os.Open(gitignoreMainPath); err == nil {
+ defer file.Close()
+ info, _ := file.Stat()
+ gitignoreMainContent = make([]byte, info.Size())
+ file.Read(gitignoreMainContent)
+ }
+
+ // Append issuesDir/ if not already present
+ gitignoreStr := string(gitignoreMainContent)
+ gitignoreEntry := issuesDir + "/"
+ if !contains(gitignoreStr, gitignoreEntry) {
+ if len(gitignoreStr) > 0 && gitignoreStr[len(gitignoreStr)-1] != '\n' {
+ gitignoreStr += "\n"
+ }
+ gitignoreStr += gitignoreEntry + "\n"
+
+ err = os.WriteFile(gitignoreMainPath, []byte(gitignoreStr), 0644)
+ if err != nil {
+ return fmt.Errorf("updating .gitignore: %w", err)
+ }
+
+ // Make sure we're on the main branch to commit .gitignore
+ err = worktree.Checkout(&git.CheckoutOptions{
+ Branch: head.Name(),
+ })
+ if err != nil {
+ fmt.Printf("Warning: Could not checkout main branch for .gitignore update: %v\n", err)
+ }
+
+ // Stage and commit .gitignore update
+ _, err = worktree.Add(gitignoreMainPath)
+ if err != nil {
+ fmt.Printf("Warning: Could not stage .gitignore: %v\n", err)
+ } else {
+ _, err = worktree.Commit(fmt.Sprintf("Add %s worktree to gitignore", issuesDir), &git.CommitOptions{
+ Author: &object.Signature{
+ Name: "Tissue CLI",
+ Email: "tissue@example.com",
+ When: time.Now(),
+ },
+ })
+ if err != nil {
+ fmt.Printf("Warning: Could not commit .gitignore update: %v\n", err)
+ }
+ }
+ }
+
+ fmt.Printf("Git worktree created at ./%s\n", issuesDir)
+ fmt.Println("\n✓ Tissue initialized successfully!")
+ fmt.Printf("\nTracking '%s' branch from remote '%s'\n", branchName, remoteName)
+ fmt.Printf("Issues directory: ./%s\n", issuesDir)
+ fmt.Println("\nNext steps:")
+ fmt.Println(" - Create your first issue: tissue new")
+ fmt.Println(" - List issues: tissue list")
+ fmt.Println(" - Check status: tissue status")
+
+ return nil
+}
+
+func initRunE(cmd *cobra.Command, args []string, issuesDir string, issuesBranch string, remoteName string) error {
// Step 1: Open and validate repository
fmt.Println("Initializing tissue issue tracking...")
@@ -58,66 +256,145 @@ func initRunE(cmd *cobra.Command, args []string, issuesDir string, issuesBranch
}
originalBranch := head.Name()
- // Check if issues branch already exists
+ // Check if issues branch already exists locally
branchRefName := plumbing.ReferenceName("refs/heads/" + issuesBranch)
_, err = repo.Reference(branchRefName, false)
- if err == nil {
- err = fmt.Errorf("%s branch already exists - use 'tissue status' to check current setup", issuesBranch)
- return err
+ localBranchExists := err == nil
+
+ // Check if worktree already exists
+ worktreeExists := false
+ if _, err := os.Stat(issuesDir); err == nil {
+ // Check if it's actually a git worktree
+ worktreeCheckCmd := exec.Command("git", "worktree", "list")
+ output, err := worktreeCheckCmd.Output()
+ if err == nil && contains(string(output), issuesDir) {
+ worktreeExists = true
+ }
}
- // Step 2: Create orphan branch
- fmt.Printf("Creating orphan '%s' branch...\n", issuesBranch)
- orphanRef := plumbing.NewSymbolicReference(
- plumbing.HEAD,
- branchRefName,
- )
- err = repo.Storer.SetReference(orphanRef)
- if err != nil {
- err = fmt.Errorf("creating orphan branch: %w", err)
+ // If both branch and worktree exist, error out
+ if localBranchExists && worktreeExists {
+ err = fmt.Errorf("%s branch and worktree already exist - use 'tissue status' to check current setup", issuesBranch)
return err
}
- // Step 3: Get worktree and initialize files
- worktree, err := repo.Worktree()
- if err != nil {
- err = fmt.Errorf("getting worktree: %w", err)
- return err
+ // If branch exists but worktree doesn't, we'll set up the worktree later
+ var skipBranchCreation bool
+ if localBranchExists && !worktreeExists {
+ fmt.Printf("Found existing local '%s' branch, setting up worktree...\n", issuesBranch)
+ skipBranchCreation = true
}
- // Clear the index and working directory for orphan branch
- // This is essential to create a true orphan branch with no parent commits
- fmt.Println("Clearing working directory for orphan branch...")
+ if !skipBranchCreation {
+ // Check remote branches
+ remotesWithBranch, err := checkRemoteBranches(repo, issuesBranch)
+ if err != nil {
+ // Log warning but continue - remotes might be temporarily unavailable
+ fmt.Printf("Warning: Could not check remote branches: %v\n", err)
+ }
- // Get the status to find all tracked files
- status, err := worktree.Status()
- if err != nil {
- err = fmt.Errorf("getting worktree status: %w", err)
- return err
- }
+ remotes, _ := repo.Remotes()
+ numRemotes := len(remotes)
+
+ // Validate --remote flag if provided
+ if remoteName != "" {
+ found := false
+ for _, remote := range remotes {
+ if remote.Config().Name == remoteName {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return fmt.Errorf("remote '%s' does not exist", remoteName)
+ }
+ }
+
+ // Case 4: Multiple remotes, one has issues branch
+ if numRemotes > 1 && len(remotesWithBranch) == 1 {
+ for remote := range remotesWithBranch {
+ fmt.Printf("Found existing '%s' branch on remote '%s'\n", issuesBranch, remote)
+ // Track the existing remote branch
+ return checkoutAndTrackRemoteBranch(repo, remote, issuesBranch, issuesDir)
+ }
+ }
+
+ // Case 5: Multiple remotes, multiple have issues branch
+ if len(remotesWithBranch) > 1 && remoteName == "" {
+ remoteList := []string{}
+ for r := range remotesWithBranch {
+ remoteList = append(remoteList, r)
+ }
+ return fmt.Errorf("branch '%s' exists on multiple remotes: %v. Use --remote flag to specify which remote to use",
+ issuesBranch, remoteList)
+ }
- // Remove all files from both the index and working directory
- for filepath := range status {
- _, err := worktree.Remove(filepath)
+ // If --remote was specified and that remote has the branch, track it
+ if remoteName != "" && remotesWithBranch[remoteName] {
+ fmt.Printf("Found existing '%s' branch on remote '%s'\n", issuesBranch, remoteName)
+ return checkoutAndTrackRemoteBranch(repo, remoteName, issuesBranch, issuesDir)
+ }
+
+ // Single remote with issues branch
+ if numRemotes == 1 && len(remotesWithBranch) == 1 {
+ for remote := range remotesWithBranch {
+ fmt.Printf("Found existing '%s' branch on remote '%s'\n", issuesBranch, remote)
+ return checkoutAndTrackRemoteBranch(repo, remote, issuesBranch, issuesDir)
+ }
+ }
+
+ // Step 2: Create orphan branch
+ fmt.Printf("Creating orphan '%s' branch...\n", issuesBranch)
+ orphanRef := plumbing.NewSymbolicReference(
+ plumbing.HEAD,
+ branchRefName,
+ )
+ err = repo.Storer.SetReference(orphanRef)
if err != nil {
- // Continue even if some files fail to be removed
- fmt.Printf("Warning: could not remove %s: %v\n", filepath, err)
+ err = fmt.Errorf("creating orphan branch: %w", err)
+ return err
}
- // Also physically delete the file from disk
- os.Remove(filepath)
- }
- // Use RemoveGlob as a fallback to ensure everything is cleared
- err = worktree.RemoveGlob("*")
- if err != nil && err != git.ErrGlobNoMatches {
- fmt.Printf("Warning: RemoveGlob failed: %v\n", err)
- }
+ // Step 3: Get worktree and initialize files
+ worktree, err := repo.Worktree()
+ if err != nil {
+ err = fmt.Errorf("getting worktree: %w", err)
+ return err
+ }
+
+ // Clear the index and working directory for orphan branch
+ // This is essential to create a true orphan branch with no parent commits
+ fmt.Println("Clearing working directory for orphan branch...")
+
+ // Get the status to find all tracked files
+ status, err := worktree.Status()
+ if err != nil {
+ err = fmt.Errorf("getting worktree status: %w", err)
+ return err
+ }
- // Now we have a clean slate for the orphan branch
- fs := osfs.New(".")
+ // Remove all files from both the index and working directory
+ for filepath := range status {
+ _, err := worktree.Remove(filepath)
+ if err != nil {
+ // Continue even if some files fail to be removed
+ fmt.Printf("Warning: could not remove %s: %v\n", filepath, err)
+ }
+ // Also physically delete the file from disk
+ os.Remove(filepath)
+ }
+
+ // Use RemoveGlob as a fallback to ensure everything is cleared
+ err = worktree.RemoveGlob("*")
+ if err != nil && err != git.ErrGlobNoMatches {
+ fmt.Printf("Warning: RemoveGlob failed: %v\n", err)
+ }
+
+ // Now we have a clean slate for the orphan branch
+ fs := osfs.New(".")
- // Create README.md
- readmePath := "README.md"
+ // Create README.md
+ readmePath := "README.md"
readmeContent := `# Issue Tracker
This branch contains all issues for this repository.
@@ -146,95 +423,104 @@ Use the tissue CLI to manage issues:
- ` + "`tissue view <id>`" + ` - View an issue
- ` + "`tissue update <id>`" + ` - Update an issue
`
- readmeFile, err := fs.Create(readmePath)
- if err != nil {
- err = fmt.Errorf("creating README.md: %w", err)
- return err
- }
- _, err = readmeFile.Write([]byte(readmeContent))
- if err != nil {
+ readmeFile, err := fs.Create(readmePath)
+ if err != nil {
+ err = fmt.Errorf("creating README.md: %w", err)
+ return err
+ }
+ _, err = readmeFile.Write([]byte(readmeContent))
+ if err != nil {
+ readmeFile.Close()
+ err = fmt.Errorf("writing README.md: %w", err)
+ return err
+ }
readmeFile.Close()
- err = fmt.Errorf("writing README.md: %w", err)
- return err
- }
- readmeFile.Close()
- // Create .gitignore
- gitignorePath := ".gitignore"
+ // Create .gitignore
+ gitignorePath := ".gitignore"
gitignoreContent := `# Ignore everything except markdown files and directories
/*
!*.md
!*/
!.gitignore
`
- gitignoreFile, err := fs.Create(gitignorePath)
- if err != nil {
- err = fmt.Errorf("creating .gitignore: %w", err)
- return err
- }
- _, err = gitignoreFile.Write([]byte(gitignoreContent))
- if err != nil {
+ gitignoreFile, err := fs.Create(gitignorePath)
+ if err != nil {
+ err = fmt.Errorf("creating .gitignore: %w", err)
+ return err
+ }
+ _, err = gitignoreFile.Write([]byte(gitignoreContent))
+ if err != nil {
+ gitignoreFile.Close()
+ err = fmt.Errorf("writing .gitignore: %w", err)
+ return err
+ }
gitignoreFile.Close()
- err = fmt.Errorf("writing .gitignore: %w", err)
- return err
- }
- gitignoreFile.Close()
-
- // Stage files
- fmt.Println("Creating initial structure...")
- _, err = worktree.Add(readmePath)
- if err != nil {
- err = fmt.Errorf("staging README.md: %w", err)
- return err
- }
- _, err = worktree.Add(gitignorePath)
- if err != nil {
- err = fmt.Errorf("staging .gitignore: %w", err)
- return err
- }
- // Commit
- commit, err := worktree.Commit(fmt.Sprintf("Initialize %s branch for issue tracking", issuesBranch), &git.CommitOptions{
- Author: &object.Signature{
- Name: "Tissue CLI",
- Email: "tissue@example.com",
- When: time.Now(),
- },
- })
- if err != nil {
- err = fmt.Errorf("committing initial structure: %w", err)
- return err
- }
- fmt.Printf("Created initial commit: %s\n", commit.String()[:7])
+ // Stage files
+ fmt.Println("Creating initial structure...")
+ _, err = worktree.Add(readmePath)
+ if err != nil {
+ err = fmt.Errorf("staging README.md: %w", err)
+ return err
+ }
+ _, err = worktree.Add(gitignorePath)
+ if err != nil {
+ err = fmt.Errorf("staging .gitignore: %w", err)
+ return err
+ }
- // Step 4: Push to remote if it exists
- remotes, err := repo.Remotes()
- if err == nil && len(remotes) > 0 {
- fmt.Println("Pushing to remote...")
- refSpec := fmt.Sprintf("refs/heads/%s:refs/heads/%s", issuesBranch, issuesBranch)
- err = repo.Push(&git.PushOptions{
- RemoteName: "origin",
- RefSpecs: []config.RefSpec{
- config.RefSpec(refSpec),
+ // Commit
+ commit, err := worktree.Commit(fmt.Sprintf("Initialize %s branch for issue tracking", issuesBranch), &git.CommitOptions{
+ Author: &object.Signature{
+ Name: "Tissue CLI",
+ Email: "tissue@example.com",
+ When: time.Now(),
},
})
- if err != nil && err != git.NoErrAlreadyUpToDate {
- fmt.Printf("Warning: Could not push to remote: %v\n", err)
- fmt.Printf("You can push manually later with: git push -u origin %s\n", issuesBranch)
- } else if err == nil {
- fmt.Printf("Pushed %s branch to remote\n", issuesBranch)
+ if err != nil {
+ err = fmt.Errorf("committing initial structure: %w", err)
+ return err
+ }
+ fmt.Printf("Created initial commit: %s\n", commit.String()[:7])
+
+ // Step 4: Push to remote if appropriate
+ targetRemote := determineTargetRemote(repo, remoteName, numRemotes)
+
+ if targetRemote != "" {
+ fmt.Printf("Pushing to remote '%s'...\n", targetRemote)
+ refSpec := fmt.Sprintf("refs/heads/%s:refs/heads/%s", issuesBranch, issuesBranch)
+ err = repo.Push(&git.PushOptions{
+ RemoteName: targetRemote,
+ RefSpecs: []config.RefSpec{
+ config.RefSpec(refSpec),
+ },
+ })
+ if err != nil && err != git.NoErrAlreadyUpToDate {
+ fmt.Printf("Warning: Could not push to remote: %v\n", err)
+ fmt.Printf("You can push manually later with: git push -u %s %s\n", targetRemote, issuesBranch)
+ } else if err == nil {
+ fmt.Printf("Pushed '%s' branch to remote '%s'\n", issuesBranch, targetRemote)
+ }
+ } else if numRemotes > 1 && remoteName == "" {
+ // Case 3: Multiple remotes, no issues branch, no --remote flag
+ fmt.Println("\nMultiple remotes detected. Not pushing to any remote.")
+ fmt.Println("To push to a specific remote, run:")
+ fmt.Println(" tissue init --remote <remote-name>")
+ fmt.Println("\nOr push manually with git:")
+ fmt.Printf(" git push -u <remote-name> %s\n", issuesBranch)
}
- }
- // Step 5: Return to original branch
- err = worktree.Checkout(&git.CheckoutOptions{
- Branch: originalBranch,
- })
- if err != nil {
- err = fmt.Errorf("returning to original branch: %w", err)
- return err
- }
- fmt.Printf("Returned to branch: %s\n", originalBranch.Short())
+ // Step 5: Return to original branch
+ err = worktree.Checkout(&git.CheckoutOptions{
+ Branch: originalBranch,
+ })
+ if err != nil {
+ err = fmt.Errorf("returning to original branch: %w", err)
+ return err
+ }
+ fmt.Printf("Returned to branch: %s\n", originalBranch.Short())
+ } // End of !skipBranchCreation
// Step 6: Setup worktree (always done)
fmt.Printf("Setting up git worktree at %s...\n", issuesDir)
@@ -251,47 +537,50 @@ Use the tissue CLI to manage issues:
return err
}
- // Add issuesDir/ to .gitignore in main branch
- gitignoreMainPath := ".gitignore"
- var gitignoreMainContent []byte
-
- // Check if .gitignore exists
- if file, err := os.Open(gitignoreMainPath); err == nil {
- defer file.Close()
- info, _ := file.Stat()
- gitignoreMainContent = make([]byte, info.Size())
- file.Read(gitignoreMainContent)
- }
-
- // Append issuesDir/ if not already present
- gitignoreStr := string(gitignoreMainContent)
- gitignoreEntry := issuesDir + "/"
- if !contains(gitignoreStr, gitignoreEntry) {
- if len(gitignoreStr) > 0 && gitignoreStr[len(gitignoreStr)-1] != '\n' {
- gitignoreStr += "\n"
+ // Add issuesDir/ to .gitignore in main branch (but only if we're not tracking existing branch)
+ if !skipBranchCreation {
+ worktree, _ := repo.Worktree()
+ gitignoreMainPath := ".gitignore"
+ var gitignoreMainContent []byte
+
+ // Check if .gitignore exists
+ if file, err := os.Open(gitignoreMainPath); err == nil {
+ defer file.Close()
+ info, _ := file.Stat()
+ gitignoreMainContent = make([]byte, info.Size())
+ file.Read(gitignoreMainContent)
}
- gitignoreStr += gitignoreEntry + "\n"
- err = os.WriteFile(gitignoreMainPath, []byte(gitignoreStr), 0644)
- if err != nil {
- err = fmt.Errorf("updating .gitignore: %w", err)
- return err
- }
+ // Append issuesDir/ if not already present
+ gitignoreStr := string(gitignoreMainContent)
+ gitignoreEntry := issuesDir + "/"
+ if !contains(gitignoreStr, gitignoreEntry) {
+ if len(gitignoreStr) > 0 && gitignoreStr[len(gitignoreStr)-1] != '\n' {
+ gitignoreStr += "\n"
+ }
+ gitignoreStr += gitignoreEntry + "\n"
- // Stage and commit .gitignore update
- _, err = worktree.Add(gitignoreMainPath)
- if err != nil {
- fmt.Printf("Warning: Could not stage .gitignore: %v\n", err)
- } else {
- _, err = worktree.Commit(fmt.Sprintf("Add %s worktree to gitignore", issuesDir), &git.CommitOptions{
- Author: &object.Signature{
- Name: "Tissue CLI",
- Email: "tissue@example.com",
- When: time.Now(),
- },
- })
+ err = os.WriteFile(gitignoreMainPath, []byte(gitignoreStr), 0644)
if err != nil {
- fmt.Printf("Warning: Could not commit .gitignore update: %v\n", err)
+ err = fmt.Errorf("updating .gitignore: %w", err)
+ return err
+ }
+
+ // Stage and commit .gitignore update
+ _, err = worktree.Add(gitignoreMainPath)
+ if err != nil {
+ fmt.Printf("Warning: Could not stage .gitignore: %v\n", err)
+ } else {
+ _, err = worktree.Commit(fmt.Sprintf("Add %s worktree to gitignore", issuesDir), &git.CommitOptions{
+ Author: &object.Signature{
+ Name: "Tissue CLI",
+ Email: "tissue@example.com",
+ When: time.Now(),
+ },
+ })
+ if err != nil {
+ fmt.Printf("Warning: Could not commit .gitignore update: %v\n", err)
+ }
}
}
}