A deployable markdown editor that connects with your self hosted files and lets you edit in a beautiful interface
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}