e89e3102edf8ec2168e0ac0eaf5636dc0f0b35e5

Author
Kieran Klukas <kieran@dunkirk.sh>
Committer
Kieran Klukas <kieran@dunkirk.sh>
Date

Message

refactor: move LFS lock handlers into their own file

The LFS handler file had grown past a thousand lines. The lock API is a
separate part of the LFS protocol, so its handlers now live on their
own. The code is moved unchanged.

💘 Generated with Crush

Assisted-by: Crush:claude-opus-5-5

Diff

This diff is truncated to protect this page.

  1diff --git a/pkg/web/git_lfs.go b/pkg/web/git_lfs.go
  2index 19d08ba03b56d0e6b438a49028bc0d45f87d1aea..6230aa7ef627585488b20c0e17e99efb34e30063 100644
  3--- a/pkg/web/git_lfs.go
  4+++ b/pkg/web/git_lfs.go
  5@@ -7,7 +7,6 @@ import (
  6 	"io"
  7 	"io/fs"
  8 	"net/http"
  9-	"net/url"
 10 	"path"
 11 	"path/filepath"
 12 	"strconv"
 13@@ -17,7 +16,6 @@ import (
 14 	"github.com/charmbracelet/soft-serve/pkg/access"
 15 	"github.com/charmbracelet/soft-serve/pkg/config"
 16 	"github.com/charmbracelet/soft-serve/pkg/db"
 17-	"github.com/charmbracelet/soft-serve/pkg/db/models"
 18 	"github.com/charmbracelet/soft-serve/pkg/lfs"
 19 	"github.com/charmbracelet/soft-serve/pkg/proto"
 20 	"github.com/charmbracelet/soft-serve/pkg/storage"
 21@@ -460,512 +458,6 @@ func serviceLfsBasicVerify(w http.ResponseWriter, r *http.Request) {
 22 	}
 23 }
 24 
 25-func serviceLfsLocks(w http.ResponseWriter, r *http.Request) {
 26-	switch r.Method {
 27-	case http.MethodGet:
 28-		serviceLfsLocksGet(w, r)
 29-	case http.MethodPost:
 30-		serviceLfsLocksCreate(w, r)
 31-	default:
 32-		renderMethodNotAllowed(w, r)
 33-	}
 34-}
 35-
 36-// POST: /<repo>.git/info/lfs/objects/locks
 37-func serviceLfsLocksCreate(w http.ResponseWriter, r *http.Request) {
 38-	if !isLfs(r) {
 39-		renderNotAcceptable(w)
 40-		return
 41-	}
 42-
 43-	ctx := r.Context()
 44-	logger := log.FromContext(ctx).WithPrefix("http.lfs-locks")
 45-
 46-	// Checked in withAccess too. Repeated here so a routing or middleware
 47-	// mistake cannot turn into an unauthorized lock.
 48-	if access.FromContext(ctx) < access.ReadWriteAccess {
 49-		askCredentials(w, r)
 50-		renderJSON(w, http.StatusForbidden, lfs.ErrorResponse{
 51-			Message: "write access required",
 52-		})
 53-		return
 54-	}
 55-
 56-	var req lfs.LockCreateRequest
 57-	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 58-		logger.Error("error decoding json", "err", err)
 59-		renderJSON(w, http.StatusBadRequest, lfs.ErrorResponse{
 60-			Message: "invalid request: " + err.Error(),
 61-		})
 62-		return
 63-	}
 64-
 65-	repo, ok := lfsRepository(w, r)
 66-	if !ok {
 67-		return
 68-	}
 69-
 70-	user := proto.UserFromContext(ctx)
 71-	if user == nil {
 72-		logger.Error("error getting user from context")
 73-		renderJSON(w, http.StatusNotFound, lfs.ErrorResponse{
 74-			Message: "user not found",
 75-		})
 76-		return
 77-	}
 78-
 79-	dbx := db.FromContext(ctx)
 80-	datastore := store.FromContext(ctx)
 81-	if err := datastore.CreateLFSLockForUser(ctx, dbx, repo.ID(), user.ID(), req.Path, req.Ref.Name); err != nil {
 82-		err = db.WrapError(err)
 83-		if errors.Is(err, db.ErrDuplicateKey) {
 84-			errResp := lfs.LockResponse{
 85-				ErrorResponse: lfs.ErrorResponse{
 86-					Message: "lock already exists",
 87-				},
 88-			}
 89-			lock, err := datastore.GetLFSLockForUserPath(ctx, dbx, repo.ID(), user.ID(), req.Path)
 90-			if err == nil {
 91-				errResp.Lock = lfs.Lock{
 92-					ID:       strconv.FormatInt(lock.ID, 10),
 93-					Path:     lock.Path,
 94-					LockedAt: lock.CreatedAt,
 95-				}
 96-				lockOwner := lfs.Owner{
 97-					Name: user.Username(),
 98-				}
 99-				if lock.UserID != user.ID() {
100-					owner, err := datastore.GetUserByID(ctx, dbx, lock.UserID)
101-					if err != nil {
102-						logger.Error("error getting lock owner", "err", err)
103-						renderJSON(w, http.StatusInternalServerError, lfs.ErrorResponse{
104-							Message: "internal server error",
105diff --git a/pkg/web/git_lfs_locks.go b/pkg/web/git_lfs_locks.go
106new file mode 100644
107index 0000000000000000000000000000000000000000..c8c41d22939c821b0aade4769fd13b447b702d50
108--- /dev/null
109+++ b/pkg/web/git_lfs_locks.go
110@@ -0,0 +1,525 @@
111+package web
112+
113+import (
114+	"encoding/json"
115+	"errors"
116+	"net/http"
117+	"net/url"
118+	"strconv"
119+	"strings"
120+
121+	"charm.land/log/v2"
122+	"github.com/charmbracelet/soft-serve/pkg/access"
123+	"github.com/charmbracelet/soft-serve/pkg/db"
124+	"github.com/charmbracelet/soft-serve/pkg/db/models"
125+	"github.com/charmbracelet/soft-serve/pkg/lfs"
126+	"github.com/charmbracelet/soft-serve/pkg/proto"
127+	"github.com/charmbracelet/soft-serve/pkg/store"
128+	"github.com/gorilla/mux"
129+)
130+
131+func serviceLfsLocks(w http.ResponseWriter, r *http.Request) {
132+	switch r.Method {
133+	case http.MethodGet:
134+		serviceLfsLocksGet(w, r)
135+	case http.MethodPost:
136+		serviceLfsLocksCreate(w, r)
137+	default:
138+		renderMethodNotAllowed(w, r)
139+	}
140+}
141+
142+// POST: /<repo>.git/info/lfs/objects/locks
143+func serviceLfsLocksCreate(w http.ResponseWriter, r *http.Request) {
144+	if !isLfs(r) {
145+		renderNotAcceptable(w)
146+		return
147+	}
148+
149+	ctx := r.Context()
150+	logger := log.FromContext(ctx).WithPrefix("http.lfs-locks")
151+
152+	// Checked in withAccess too. Repeated here so a routing or middleware
153+	// mistake cannot turn into an unauthorized lock.
154+	if access.FromContext(ctx) < access.ReadWriteAccess {
155+		askCredentials(w, r)
156+		renderJSON(w, http.StatusForbidden, lfs.ErrorResponse{
157+			Message: "write access required",
158+		})
159+		return
160+	}
161+
162+	var req lfs.LockCreateRequest
163+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
164+		logger.Error("error decoding json", "err", err)
165+		renderJSON(w, http.StatusBadRequest, lfs.ErrorResponse{
166+			Message: "invalid request: " + err.Error(),
167+		})
168+		return
169+	}
170+
171+	repo, ok := lfsRepository(w, r)
172+	if !ok {
173+		return
174+	}
175+
176+	user := proto.UserFromContext(ctx)
177+	if user == nil {
178+		logger.Error("error getting user from context")
179+		renderJSON(w, http.StatusNotFound, lfs.ErrorResponse{
180+			Message: "user not found",
181+		})
182+		return
183+	}
184+
185+	dbx := db.FromContext(ctx)
186+	datastore := store.FromContext(ctx)
187+	if err := datastore.CreateLFSLockForUser(ctx, dbx, repo.ID(), user.ID(), req.Path, req.Ref.Name); err != nil {
188+		err = db.WrapError(err)
189+		if errors.Is(err, db.ErrDuplicateKey) {
190+			errResp := lfs.LockResponse{
191+				ErrorResponse: lfs.ErrorResponse{
192+					Message: "lock already exists",
193+				},
194+			}
195+			lock, err := datastore.GetLFSLockForUserPath(ctx, dbx, repo.ID(), user.ID(), req.Path)
196+			if err == nil {
197+				errResp.Lock = lfs.Lock{
198+					ID:       strconv.FormatInt(lock.ID, 10),
199+					Path:     lock.Path,
200+					LockedAt: lock.CreatedAt,
201+				}
202+				lockOwner := lfs.Owner{
203+					Name: user.Username(),
204+				}
205+				if lock.UserID != user.ID() {
206+					owner, err := datastore.GetUserByID(ctx, dbx, lock.UserID)
207+					if err != nil {
208+						logger.Error("error getting lock owner", "err", err)
209+						renderJSON(w, http.StatusInternalServerError, lfs.ErrorResponse{