A deployable markdown editor that connects with your self hosted files and lets you edit in a beautiful interface
at main 175 lines 4.8 kB view raw
1package git 2 3import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "log" 8 "time" 9 10 "github.com/go-git/go-git/v5" 11 "github.com/yourusername/markedit/internal/database" 12) 13 14// BranchManager handles branch lifecycle management 15type BranchManager struct { 16 db *database.DB 17 git *GitOperations 18} 19 20// NewBranchManager creates a new BranchManager 21func NewBranchManager(db *database.DB, git *GitOperations) *BranchManager { 22 return &BranchManager{ 23 db: db, 24 git: git, 25 } 26} 27 28// GetOrCreateBranch returns an existing branch or creates a new one based on the 4-hour rule 29func (bm *BranchManager) GetOrCreateBranch( 30 ctx context.Context, 31 userID int, 32 repoFullName string, 33 repo *git.Repository, 34 baseBranch string, 35) (string, error) { 36 // Check database for existing branch state 37 state, err := bm.db.GetBranchState(userID, repoFullName) 38 if err != nil { 39 return "", fmt.Errorf("failed to get branch state: %w", err) 40 } 41 42 // No existing branch state - create new branch 43 if state == nil { 44 return bm.createNewBranch(ctx, userID, repoFullName, repo, baseBranch) 45 } 46 47 // Calculate time since last push 48 hoursSincePush := time.Since(state.LastPushAt).Hours() 49 50 // If more than 4 hours have passed and no uncommitted changes, create new branch 51 if hoursSincePush > 4 && !state.HasUncommittedChanges { 52 log.Printf("Branch %s is older than 4 hours (%.1f hours), creating new branch", state.BranchName, hoursSincePush) 53 54 // Delete old branch locally 55 _ = bm.git.DeleteLocalBranch(repo, state.BranchName) 56 57 // Delete old branch state 58 _ = bm.db.DeleteBranchState(userID, repoFullName) 59 60 return bm.createNewBranch(ctx, userID, repoFullName, repo, baseBranch) 61 } 62 63 // Reuse existing branch 64 log.Printf("Reusing existing branch %s (%.1f hours old)", state.BranchName, hoursSincePush) 65 66 // Checkout the existing branch 67 if err := bm.git.CheckoutBranch(repo, state.BranchName); err != nil { 68 // If checkout fails, create new branch 69 log.Printf("Failed to checkout existing branch, creating new one: %v", err) 70 return bm.createNewBranch(ctx, userID, repoFullName, repo, baseBranch) 71 } 72 73 return state.BranchName, nil 74} 75 76// createNewBranch creates a new branch and saves its state 77func (bm *BranchManager) createNewBranch( 78 ctx context.Context, 79 userID int, 80 repoFullName string, 81 repo *git.Repository, 82 baseBranch string, 83) (string, error) { 84 // Generate branch name with timestamp 85 branchName := fmt.Sprintf("markedit-%d", time.Now().Unix()) 86 87 log.Printf("Creating new branch: %s", branchName) 88 89 // Create branch in git 90 if err := bm.git.CreateBranch(repo, branchName, baseBranch); err != nil { 91 return "", fmt.Errorf("failed to create branch: %w", err) 92 } 93 94 // Save branch state to database 95 state := &database.BranchState{ 96 UserID: userID, 97 RepoFullName: repoFullName, 98 BranchName: branchName, 99 BaseBranch: baseBranch, 100 LastPushAt: time.Now(), 101 HasUncommittedChanges: false, 102 FilePaths: "[]", // Empty JSON array 103 } 104 105 if err := bm.db.SaveBranchState(state); err != nil { 106 return "", fmt.Errorf("failed to save branch state: %w", err) 107 } 108 109 return branchName, nil 110} 111 112// UpdateBranchState updates the branch state after changes 113func (bm *BranchManager) UpdateBranchState( 114 userID int, 115 repoFullName string, 116 branchName string, 117 filePaths []string, 118 hasUncommittedChanges bool, 119) error { 120 // Get existing state 121 state, err := bm.db.GetBranchState(userID, repoFullName) 122 if err != nil { 123 return fmt.Errorf("failed to get branch state: %w", err) 124 } 125 126 if state == nil { 127 return fmt.Errorf("branch state not found") 128 } 129 130 // Update state 131 state.HasUncommittedChanges = hasUncommittedChanges 132 if filePaths != nil { 133 filePathsJSON, err := json.Marshal(filePaths) 134 if err != nil { 135 return fmt.Errorf("failed to marshal file paths: %w", err) 136 } 137 state.FilePaths = string(filePathsJSON) 138 } 139 140 if !hasUncommittedChanges { 141 // Reset last push time if we just committed/pushed 142 state.LastPushAt = time.Now() 143 } 144 145 if err := bm.db.SaveBranchState(state); err != nil { 146 return fmt.Errorf("failed to update branch state: %w", err) 147 } 148 149 return nil 150} 151 152// HandleConflict handles merge conflicts by creating a new branch 153func (bm *BranchManager) HandleConflict( 154 ctx context.Context, 155 userID int, 156 repoFullName string, 157 repo *git.Repository, 158 baseBranch string, 159) (string, error) { 160 log.Printf("Handling conflict for %s, creating new branch", repoFullName) 161 162 // Get current state 163 state, _ := bm.db.GetBranchState(userID, repoFullName) 164 165 // Delete old branch locally if it exists 166 if state != nil { 167 _ = bm.git.DeleteLocalBranch(repo, state.BranchName) 168 } 169 170 // Delete old state 171 _ = bm.db.DeleteBranchState(userID, repoFullName) 172 173 // Create new branch 174 return bm.createNewBranch(ctx, userID, repoFullName, repo, baseBranch) 175}