From 232340f5e3ebe61787021bebea01fd755513c72b Mon Sep 17 00:00:00 2001
From: David Svantesson <davidsvantesson@gmail.com>
Date: Sat, 2 Nov 2019 08:37:05 +0100
Subject: [PATCH] Prevent upload (overwrite) of lfs locked file (#8769)

* Check if file is locked on upload file commit.

* Better user message if file is locked.

* Check lfs lock before creating temporary repository. fix some errors.

* move lines

* Add comment that enabled setting is checked.
---
 models/error.go                 | 17 +++++++++++++++++
 modules/repofiles/upload.go     | 24 +++++++++++++++++-------
 options/locale/locale_en-US.ini |  1 +
 routers/repo/editor.go          |  6 +++++-
 4 files changed, 40 insertions(+), 8 deletions(-)

diff --git a/models/error.go b/models/error.go
index 995617e83b..505df28868 100644
--- a/models/error.go
+++ b/models/error.go
@@ -647,6 +647,23 @@ func (err ErrLFSLockAlreadyExist) Error() string {
 	return fmt.Sprintf("lfs lock already exists [rid: %d, path: %s]", err.RepoID, err.Path)
 }
 
+// ErrLFSFileLocked represents a "LFSFileLocked" kind of error.
+type ErrLFSFileLocked struct {
+	RepoID   int64
+	Path     string
+	UserName string
+}
+
+// IsErrLFSFileLocked checks if an error is a ErrLFSFileLocked.
+func IsErrLFSFileLocked(err error) bool {
+	_, ok := err.(ErrLFSFileLocked)
+	return ok
+}
+
+func (err ErrLFSFileLocked) Error() string {
+	return fmt.Sprintf("File is lfs locked [repo: %d, locked by: %s, path: %s]", err.RepoID, err.UserName, err.Path)
+}
+
 // __________                           .__  __
 // \______   \ ____ ______   ____  _____|__|/  |_  ___________ ___.__.
 //  |       _// __ \\____ \ /  _ \/  ___/  \   __\/  _ \_  __ <   |  |
diff --git a/modules/repofiles/upload.go b/modules/repofiles/upload.go
index a2e7cc927c..eb1379560d 100644
--- a/modules/repofiles/upload.go
+++ b/modules/repofiles/upload.go
@@ -55,6 +55,23 @@ func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRep
 		return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %v", opts.Files, err)
 	}
 
+	names := make([]string, len(uploads))
+	infos := make([]uploadInfo, len(uploads))
+	for i, upload := range uploads {
+		// Check file is not lfs locked, will return nil if lock setting not enabled
+		filepath := path.Join(opts.TreePath, upload.Name)
+		lfsLock, err := repo.GetTreePathLock(filepath)
+		if err != nil {
+			return err
+		}
+		if lfsLock != nil && lfsLock.OwnerID != doer.ID {
+			return models.ErrLFSFileLocked{RepoID: repo.ID, Path: filepath, UserName: lfsLock.Owner.Name}
+		}
+
+		names[i] = upload.Name
+		infos[i] = uploadInfo{upload: upload}
+	}
+
 	t, err := NewTemporaryUploadRepository(repo)
 	if err != nil {
 		return err
@@ -67,13 +84,6 @@ func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRep
 		return err
 	}
 
-	names := make([]string, len(uploads))
-	infos := make([]uploadInfo, len(uploads))
-	for i, upload := range uploads {
-		names[i] = upload.Name
-		infos[i] = uploadInfo{upload: upload}
-	}
-
 	var filename2attribute2info map[string]map[string]string
 	if setting.LFS.StartServer {
 		filename2attribute2info, err = t.CheckAttribute("filter", names...)
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 4d1af69db5..794d78c6f7 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -742,6 +742,7 @@ editor.no_changes_to_show = There are no changes to show.
 editor.fail_to_update_file = Failed to update/create file '%s' with error: %v
 editor.add_subdir = Add a directory…
 editor.unable_to_upload_files = Failed to upload files to '%s' with error: %v
+editor.upload_file_is_locked = File '%s' is locked by %s.
 editor.upload_files_to_dir = Upload files to '%s'
 editor.cannot_commit_to_protected_branch = Cannot commit to protected branch '%s'.
 
diff --git a/routers/repo/editor.go b/routers/repo/editor.go
index d4a7dab074..763429f8cf 100644
--- a/routers/repo/editor.go
+++ b/routers/repo/editor.go
@@ -585,7 +585,11 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
 		Files:        form.Files,
 	}); err != nil {
 		ctx.Data["Err_TreePath"] = true
-		ctx.RenderWithErr(ctx.Tr("repo.editor.unable_to_upload_files", form.TreePath, err), tplUploadFile, &form)
+		if models.IsErrLFSFileLocked(err) {
+			ctx.RenderWithErr(ctx.Tr("repo.editor.upload_file_is_locked", err.(models.ErrLFSFileLocked).Path, err.(models.ErrLFSFileLocked).UserName), tplUploadFile, &form)
+		} else {
+			ctx.RenderWithErr(ctx.Tr("repo.editor.unable_to_upload_files", form.TreePath, err), tplUploadFile, &form)
+		}
 		return
 	}