diff --git a/integrations/api_helper_for_declarative_test.go b/integrations/api_helper_for_declarative_test.go
index f1d57e717c..913fce1577 100644
--- a/integrations/api_helper_for_declarative_test.go
+++ b/integrations/api_helper_for_declarative_test.go
@@ -9,6 +9,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"net/http"
+	"net/url"
 	"testing"
 	"time"
 
@@ -71,6 +72,23 @@ func doAPICreateRepository(ctx APITestContext, empty bool, callback ...func(*tes
 	}
 }
 
+func doAPIEditRepository(ctx APITestContext, editRepoOption *api.EditRepoOption, callback ...func(*testing.T, api.Repository)) func(*testing.T) {
+	return func(t *testing.T) {
+		req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), ctx.Token), editRepoOption)
+		if ctx.ExpectedCode != 0 {
+			ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
+			return
+		}
+		resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
+
+		var repository api.Repository
+		DecodeJSON(t, resp, &repository)
+		if len(callback) > 0 {
+			callback[0](t, repository)
+		}
+	}
+}
+
 func doAPIAddCollaborator(ctx APITestContext, username string, mode models.AccessMode) func(*testing.T) {
 	return func(t *testing.T) {
 		permission := "read"
@@ -256,6 +274,23 @@ func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64)
 	}
 }
 
+func doAPIManuallyMergePullRequest(ctx APITestContext, owner, repo, commitID string, index int64) func(*testing.T) {
+	return func(t *testing.T) {
+		urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s",
+			owner, repo, index, ctx.Token)
+		req := NewRequestWithJSON(t, http.MethodPost, urlStr, &auth.MergePullRequestForm{
+			Do:            string(models.MergeStyleManuallyMerged),
+			MergeCommitID: commitID,
+		})
+
+		if ctx.ExpectedCode != 0 {
+			ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
+			return
+		}
+		ctx.Session.MakeRequest(t, req, 200)
+	}
+}
+
 func doAPIGetBranch(ctx APITestContext, branch string, callback ...func(*testing.T, api.Branch)) func(*testing.T) {
 	return func(t *testing.T) {
 		req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/branches/%s?token=%s", ctx.Username, ctx.Reponame, branch, ctx.Token)
diff --git a/integrations/git_test.go b/integrations/git_test.go
index c3c1126829..705bd08c11 100644
--- a/integrations/git_test.go
+++ b/integrations/git_test.go
@@ -69,6 +69,7 @@ func testGit(t *testing.T, u *url.URL) {
 		mediaTest(t, &httpContext, little, big, littleLFS, bigLFS)
 
 		t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath))
+		t.Run("CreatePRAndSetManuallyMerged", doCreatePRAndSetManuallyMerged(httpContext, httpContext, dstPath, "master", "test-manually-merge"))
 		t.Run("MergeFork", func(t *testing.T) {
 			defer PrintCurrentTest(t)()
 			t.Run("CreatePRAndMerge", doMergeFork(httpContext, forkedUserCtx, "master", httpContext.Username+":master"))
@@ -468,6 +469,35 @@ func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) fun
 	}
 }
 
+func doCreatePRAndSetManuallyMerged(ctx, baseCtx APITestContext, dstPath, baseBranch, headBranch string) func(t *testing.T) {
+	return func(t *testing.T) {
+		defer PrintCurrentTest(t)()
+		var (
+			pr           api.PullRequest
+			err          error
+			lastCommitID string
+		)
+
+		trueBool := true
+		falseBool := false
+
+		t.Run("AllowSetManuallyMergedAndSwitchOffAutodetectManualMerge", doAPIEditRepository(baseCtx, &api.EditRepoOption{
+			HasPullRequests:       &trueBool,
+			AllowManualMerge:      &trueBool,
+			AutodetectManualMerge: &falseBool,
+		}))
+
+		t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch))
+		t.Run("PushToHeadBranch", doGitPushTestRepository(dstPath, "origin", headBranch))
+		t.Run("CreateEmptyPullRequest", func(t *testing.T) {
+			pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t)
+			assert.NoError(t, err)
+		})
+		lastCommitID = pr.Base.Sha
+		t.Run("ManuallyMergePR", doAPIManuallyMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, lastCommitID, pr.Index))
+	}
+}
+
 func doEnsureCanSeePull(ctx APITestContext, pr api.PullRequest) func(t *testing.T) {
 	return func(t *testing.T) {
 		req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index))
diff --git a/integrations/pull_status_test.go b/integrations/pull_status_test.go
index fc4c7ca33d..7c6f3d44c5 100644
--- a/integrations/pull_status_test.go
+++ b/integrations/pull_status_test.go
@@ -115,6 +115,6 @@ func TestPullCreate_EmptyChangesWithCommits(t *testing.T) {
 		doc := NewHTMLParser(t, resp.Body)
 
 		text := strings.TrimSpace(doc.doc.Find(".merge-section").Text())
-		assert.Contains(t, text, "This pull request can be merged automatically.")
+		assert.Contains(t, text, "This branch is equal with the target branch.")
 	})
 }
diff --git a/models/pull.go b/models/pull.go
index 0eba65db4f..7dacf6a8d7 100644
--- a/models/pull.go
+++ b/models/pull.go
@@ -35,6 +35,7 @@ const (
 	PullRequestStatusMergeable
 	PullRequestStatusManuallyMerged
 	PullRequestStatusError
+	PullRequestStatusEmpty
 )
 
 // PullRequest represents relation between pull request and repositories.
@@ -332,6 +333,11 @@ func (pr *PullRequest) CanAutoMerge() bool {
 	return pr.Status == PullRequestStatusMergeable
 }
 
+// IsEmpty returns true if this pull request is empty.
+func (pr *PullRequest) IsEmpty() bool {
+	return pr.Status == PullRequestStatusEmpty
+}
+
 // MergeStyle represents the approach to merge commits into base branch.
 type MergeStyle string
 
@@ -344,6 +350,8 @@ const (
 	MergeStyleRebaseMerge MergeStyle = "rebase-merge"
 	// MergeStyleSquash squash commits into single commit before merging
 	MergeStyleSquash MergeStyle = "squash"
+	// MergeStyleManuallyMerged pr has been merged manually, just mark it as merged directly
+	MergeStyleManuallyMerged MergeStyle = "manually-merged"
 )
 
 // SetMerged sets a pull request to merged and closes the corresponding issue
diff --git a/models/repo_unit.go b/models/repo_unit.go
index 3ef3904833..0feddfe2ea 100644
--- a/models/repo_unit.go
+++ b/models/repo_unit.go
@@ -101,6 +101,8 @@ type PullRequestsConfig struct {
 	AllowRebase               bool
 	AllowRebaseMerge          bool
 	AllowSquash               bool
+	AllowManualMerge          bool
+	AutodetectManualMerge     bool
 }
 
 // FromDB fills up a PullRequestsConfig from serialized format.
@@ -120,7 +122,8 @@ func (cfg *PullRequestsConfig) IsMergeStyleAllowed(mergeStyle MergeStyle) bool {
 	return mergeStyle == MergeStyleMerge && cfg.AllowMerge ||
 		mergeStyle == MergeStyleRebase && cfg.AllowRebase ||
 		mergeStyle == MergeStyleRebaseMerge && cfg.AllowRebaseMerge ||
-		mergeStyle == MergeStyleSquash && cfg.AllowSquash
+		mergeStyle == MergeStyleSquash && cfg.AllowSquash ||
+		mergeStyle == MergeStyleManuallyMerged && cfg.AllowManualMerge
 }
 
 // AllowedMergeStyleCount returns the total count of allowed merge styles for the PullRequestsConfig
diff --git a/modules/forms/repo_form.go b/modules/forms/repo_form.go
index 2793acdd5b..ab88aef571 100644
--- a/modules/forms/repo_form.go
+++ b/modules/forms/repo_form.go
@@ -156,6 +156,8 @@ type RepoSettingForm struct {
 	PullsAllowRebase                 bool
 	PullsAllowRebaseMerge            bool
 	PullsAllowSquash                 bool
+	PullsAllowManualMerge            bool
+	EnableAutodetectManualMerge      bool
 	EnableTimetracker                bool
 	AllowOnlyContributorsToTrackTime bool
 	EnableIssueDependencies          bool
@@ -556,11 +558,12 @@ func (f *InitializeLabelsForm) Validate(req *http.Request, errs binding.Errors)
 // swagger:model MergePullRequestOption
 type MergePullRequestForm struct {
 	// required: true
-	// enum: merge,rebase,rebase-merge,squash
-	Do                string `binding:"Required;In(merge,rebase,rebase-merge,squash)"`
+	// enum: merge,rebase,rebase-merge,squash,manually-merged
+	Do                string `binding:"Required;In(merge,rebase,rebase-merge,squash,manually-merged)"`
 	MergeTitleField   string
 	MergeMessageField string
-	ForceMerge        *bool `json:"force_merge,omitempty"`
+	MergeCommitID     string // only used for manually-merged
+	ForceMerge        *bool  `json:"force_merge,omitempty"`
 }
 
 // Validate validates the fields
diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go
index 5bf113ba49..ea0aeeb35d 100644
--- a/modules/git/repo_commit.go
+++ b/modules/git/repo_commit.go
@@ -456,3 +456,12 @@ func (repo *Repository) GetCommitsFromIDs(commitIDs []string) (commits *list.Lis
 
 	return commits
 }
+
+// IsCommitInBranch check if the commit is on the branch
+func (repo *Repository) IsCommitInBranch(commitID, branch string) (r bool, err error) {
+	stdout, err := NewCommand("branch", "--contains", commitID, branch).RunInDir(repo.Path)
+	if err != nil {
+		return false, err
+	}
+	return len(stdout) > 0, err
+}
diff --git a/modules/git/repo_commit_test.go b/modules/git/repo_commit_test.go
index 87dd6763b3..3eedaa6b6e 100644
--- a/modules/git/repo_commit_test.go
+++ b/modules/git/repo_commit_test.go
@@ -63,3 +63,18 @@ func TestGetCommitWithBadCommitID(t *testing.T) {
 	assert.Error(t, err)
 	assert.True(t, IsErrNotExist(err))
 }
+
+func TestIsCommitInBranch(t *testing.T) {
+	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
+	bareRepo1, err := OpenRepository(bareRepo1Path)
+	assert.NoError(t, err)
+	defer bareRepo1.Close()
+
+	result, err := bareRepo1.IsCommitInBranch("2839944139e0de9737a044f78b0e4b40d989a9e3", "branch1")
+	assert.NoError(t, err)
+	assert.Equal(t, true, result)
+
+	result, err = bareRepo1.IsCommitInBranch("2839944139e0de9737a044f78b0e4b40d989a9e3", "branch2")
+	assert.NoError(t, err)
+	assert.Equal(t, false, result)
+}
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index d588813b21..c47700cd00 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -167,6 +167,10 @@ type EditRepoOption struct {
 	AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"`
 	// either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging. `has_pull_requests` must be `true`.
 	AllowSquash *bool `json:"allow_squash_merge,omitempty"`
+	// either `true` to allow mark pr as merged manually, or `false` to prevent it. `has_pull_requests` must be `true`.
+	AllowManualMerge *bool `json:"allow_manual_merge,omitempty"`
+	// either `true` to enable AutodetectManualMerge, or `false` to prevent it. `has_pull_requests` must be `true`, Note: In some special cases, misjudgments can occur.
+	AutodetectManualMerge *bool `json:"autodetect_manual_merge,omitempty"`
 	// set to `true` to archive this repository.
 	Archived *bool `json:"archived,omitempty"`
 	// set to a string like `8h30m0s` to set the mirror interval time
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 0ee8e7ab0c..08b202d192 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1105,6 +1105,7 @@ issues.context.delete = Delete
 issues.no_content = There is no content yet.
 issues.close_issue = Close
 issues.pull_merged_at = `merged commit <a href="%[1]s">%[2]s</a> into <b>%[3]s</b> %[4]s`
+issues.manually_pull_merged_at = `merged commit <a href="%[1]s">%[2]s</a> into <b>%[3]s</b> %[4]s manually`
 issues.close_comment_issue = Comment and Close
 issues.reopen_issue = Reopen
 issues.reopen_comment_issue = Comment and Reopen
@@ -1273,6 +1274,7 @@ pulls.compare_compare = pull from
 pulls.filter_branch = Filter branch
 pulls.no_results = No results found.
 pulls.nothing_to_compare = These branches are equal. There is no need to create a pull request.
+pulls.nothing_to_compare_and_allow_empty_pr = These branches are equal. This PR will be empty.
 pulls.has_pull_request = `A pull request between these branches already exists: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
 pulls.create = Create Pull Request
 pulls.title_desc = wants to merge %[1]d commits from <code>%[2]s</code> into <code id="branch_target">%[3]s</code>
@@ -1285,6 +1287,8 @@ pulls.reopen_to_merge = Please reopen this pull request to perform a merge.
 pulls.cant_reopen_deleted_branch = This pull request cannot be reopened because the branch was deleted.
 pulls.merged = Merged
 pulls.merged_as = The pull request has been merged as <a rel="nofollow" class="ui sha" href="%[1]s"><code>%[2]s</code></a>.
+pulls.manually_merged = Manually merged
+pulls.manually_merged_as = The pull request has been manually merged as <a rel="nofollow" class="ui sha" href="%[1]s"><code>%[2]s</code></a>.
 pulls.is_closed = The pull request has been closed.
 pulls.has_merged = The pull request has been merged.
 pulls.title_wip_desc = `<a href="#">Start the title with <strong>%s</strong></a> to prevent the pull request from being merged accidentally.`
@@ -1292,6 +1296,7 @@ pulls.cannot_merge_work_in_progress = This pull request is marked as a work in p
 pulls.data_broken = This pull request is broken due to missing fork information.
 pulls.files_conflicted = This pull request has changes conflicting with the target branch.
 pulls.is_checking = "Merge conflict checking is in progress. Try again in few moments."
+pulls.is_empty = "This branch is equal with the target branch."
 pulls.required_status_check_failed = Some required checks were not successful.
 pulls.required_status_check_missing = Some required checks are missing.
 pulls.required_status_check_administrator = As an administrator, you may still merge this pull request.
@@ -1312,6 +1317,7 @@ pulls.reject_count_1 = "%d change request"
 pulls.reject_count_n = "%d change requests"
 pulls.waiting_count_1 = "%d waiting review"
 pulls.waiting_count_n = "%d waiting reviews"
+pulls.wrong_commit_id = "commit id must be a commit id on the target branch"
 
 pulls.no_merge_desc = This pull request cannot be merged because all repository merge options are disabled.
 pulls.no_merge_helper = Enable merge options in the repository settings or merge the pull request manually.
@@ -1322,6 +1328,8 @@ pulls.merge_pull_request = Merge Pull Request
 pulls.rebase_merge_pull_request = Rebase and Merge
 pulls.rebase_merge_commit_pull_request = Rebase and Merge (--no-ff)
 pulls.squash_merge_pull_request = Squash and Merge
+pulls.merge_manually = Manually merged
+pulls.merge_commit_id = The merge commit ID
 pulls.require_signed_wont_sign = The branch requires signed commits but this merge will not be signed
 pulls.invalid_merge_option = You cannot use this merge option for this pull request.
 pulls.merge_conflict = Merge Failed: There was a conflict whilst merging. Hint: Try a different strategy
@@ -1545,6 +1553,8 @@ settings.pulls.allow_merge_commits = Enable Commit Merging
 settings.pulls.allow_rebase_merge = Enable Rebasing to Merge Commits
 settings.pulls.allow_rebase_merge_commit = Enable Rebasing with explicit merge commits (--no-ff)
 settings.pulls.allow_squash_commits = Enable Squashing to Merge Commits
+settings.pulls.allow_manual_merge = Enable Mark PR as manually merged
+settings.pulls.enable_autodetect_manual_merge = Enable autodetect manual merge (Note: In some special cases, misjudgments can occur)
 settings.projects_desc = Enable Repository Projects
 settings.admin_settings = Administrator Settings
 settings.admin_enable_health_check = Enable Repository Health Checks (git fsck)
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index 38dac36553..8eda949652 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -769,13 +769,31 @@ func MergePullRequest(ctx *context.APIContext) {
 		return
 	}
 
-	if !pr.CanAutoMerge() {
-		ctx.Error(http.StatusMethodNotAllowed, "PR not in mergeable state", "Please try again later")
+	if pr.HasMerged {
+		ctx.Error(http.StatusMethodNotAllowed, "PR already merged", "")
 		return
 	}
 
-	if pr.HasMerged {
-		ctx.Error(http.StatusMethodNotAllowed, "PR already merged", "")
+	// handle manually-merged mark
+	if models.MergeStyle(form.Do) == models.MergeStyleManuallyMerged {
+		if err = pull_service.MergedManually(pr, ctx.User, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
+			if models.IsErrInvalidMergeStyle(err) {
+				ctx.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", models.MergeStyle(form.Do)))
+				return
+			}
+			if strings.Contains(err.Error(), "Wrong commit ID") {
+				ctx.JSON(http.StatusConflict, err)
+				return
+			}
+			ctx.Error(http.StatusInternalServerError, "Manually-Merged", err)
+			return
+		}
+		ctx.Status(http.StatusOK)
+		return
+	}
+
+	if !pr.CanAutoMerge() {
+		ctx.Error(http.StatusMethodNotAllowed, "PR not in mergeable state", "Please try again later")
 		return
 	}
 
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index b84c5993e4..b12797f83b 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -725,6 +725,8 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
 					AllowRebase:               true,
 					AllowRebaseMerge:          true,
 					AllowSquash:               true,
+					AllowManualMerge:          true,
+					AutodetectManualMerge:     false,
 				}
 			} else {
 				config = unit.PullRequestsConfig()
@@ -745,6 +747,12 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
 			if opts.AllowSquash != nil {
 				config.AllowSquash = *opts.AllowSquash
 			}
+			if opts.AllowManualMerge != nil {
+				config.AllowManualMerge = *opts.AllowManualMerge
+			}
+			if opts.AutodetectManualMerge != nil {
+				config.AutodetectManualMerge = *opts.AutodetectManualMerge
+			}
 
 			units = append(units, models.RepoUnit{
 				RepoID: repo.ID,
diff --git a/routers/repo/compare.go b/routers/repo/compare.go
index 2eef20f5ff..a8f4f8add8 100644
--- a/routers/repo/compare.go
+++ b/routers/repo/compare.go
@@ -429,6 +429,14 @@ func PrepareCompareDiff(
 
 	if headCommitID == compareInfo.MergeBase {
 		ctx.Data["IsNothingToCompare"] = true
+		if unit, err := repo.GetUnit(models.UnitTypePullRequests); err == nil {
+			config := unit.PullRequestsConfig()
+			if !config.AutodetectManualMerge {
+				ctx.Data["AllowEmptyPr"] = !(baseBranch == headBranch && ctx.Repo.Repository.Name == headRepo.Name)
+			} else {
+				ctx.Data["AllowEmptyPr"] = false
+			}
+		}
 		return true
 	}
 
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index a9459a10ed..99df9db183 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -1491,6 +1491,8 @@ func ViewIssue(ctx *context.Context) {
 				ctx.Data["MergeStyle"] = models.MergeStyleRebaseMerge
 			} else if prConfig.AllowSquash {
 				ctx.Data["MergeStyle"] = models.MergeStyleSquash
+			} else if prConfig.AllowManualMerge {
+				ctx.Data["MergeStyle"] = models.MergeStyleManuallyMerged
 			} else {
 				ctx.Data["MergeStyle"] = ""
 			}
@@ -1531,6 +1533,22 @@ func ViewIssue(ctx *context.Context) {
 			pull.HeadRepo != nil &&
 			git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch) &&
 			(!pull.HasMerged || ctx.Data["HeadBranchCommitID"] == ctx.Data["PullHeadCommitID"])
+
+		stillCanManualMerge := func() bool {
+			if pull.HasMerged || issue.IsClosed || !ctx.IsSigned {
+				return false
+			}
+			if pull.CanAutoMerge() || pull.IsWorkInProgress() || pull.IsChecking() {
+				return false
+			}
+			if (ctx.User.IsAdmin || ctx.Repo.IsAdmin()) && prConfig.AllowManualMerge {
+				return true
+			}
+
+			return false
+		}
+
+		ctx.Data["StillCanManualMerge"] = stillCanManualMerge()
 	}
 
 	// Get Dependencies
diff --git a/routers/repo/pull.go b/routers/repo/pull.go
index 15af2a2a3f..2ed47605f8 100644
--- a/routers/repo/pull.go
+++ b/routers/repo/pull.go
@@ -33,6 +33,7 @@ import (
 	"code.gitea.io/gitea/services/gitdiff"
 	pull_service "code.gitea.io/gitea/services/pull"
 	repo_service "code.gitea.io/gitea/services/repository"
+	"github.com/unknwon/com"
 )
 
 const (
@@ -794,15 +795,36 @@ func MergePullRequest(ctx *context.Context) {
 		return
 	}
 
-	if !pr.CanAutoMerge() {
-		ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
-		ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
+	if pr.HasMerged {
+		ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged"))
+		ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
 		return
 	}
 
-	if pr.HasMerged {
-		ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged"))
-		ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
+	// handle manually-merged mark
+	if models.MergeStyle(form.Do) == models.MergeStyleManuallyMerged {
+		if err = pull_service.MergedManually(pr, ctx.User, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
+			if models.IsErrInvalidMergeStyle(err) {
+				ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
+				ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
+				return
+			} else if strings.Contains(err.Error(), "Wrong commit ID") {
+				ctx.Flash.Error(ctx.Tr("repo.pulls.wrong_commit_id"))
+				ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
+				return
+			}
+
+			ctx.ServerError("MergedManually", err)
+			return
+		}
+
+		ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
+		return
+	}
+
+	if !pr.CanAutoMerge() {
+		ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
+		ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
 		return
 	}
 
diff --git a/routers/repo/setting.go b/routers/repo/setting.go
index b35828d7b1..692d65b44c 100644
--- a/routers/repo/setting.go
+++ b/routers/repo/setting.go
@@ -321,6 +321,8 @@ func SettingsPost(ctx *context.Context) {
 					AllowRebase:               form.PullsAllowRebase,
 					AllowRebaseMerge:          form.PullsAllowRebaseMerge,
 					AllowSquash:               form.PullsAllowSquash,
+					AllowManualMerge:          form.PullsAllowManualMerge,
+					AutodetectManualMerge:     form.EnableAutodetectManualMerge,
 				},
 			})
 		} else if !models.UnitTypePullRequests.UnitGlobalDisabled() {
diff --git a/services/pull/check.go b/services/pull/check.go
index 5acee8174b..3ec76de5e8 100644
--- a/services/pull/check.go
+++ b/services/pull/check.go
@@ -116,7 +116,7 @@ func getMergeCommit(pr *models.PullRequest) (*git.Commit, error) {
 	if err != nil {
 		return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %v", err)
 	} else if len(mergeCommit) < 40 {
-		// PR was fast-forwarded, so just use last commit of PR
+		// PR was maybe fast-forwarded, so just use last commit of PR
 		mergeCommit = commitID[:40]
 	}
 
@@ -137,6 +137,21 @@ func getMergeCommit(pr *models.PullRequest) (*git.Commit, error) {
 // manuallyMerged checks if a pull request got manually merged
 // When a pull request got manually merged mark the pull request as merged
 func manuallyMerged(pr *models.PullRequest) bool {
+	if err := pr.LoadBaseRepo(); err != nil {
+		log.Error("PullRequest[%d].LoadBaseRepo: %v", pr.ID, err)
+		return false
+	}
+
+	if unit, err := pr.BaseRepo.GetUnit(models.UnitTypePullRequests); err == nil {
+		config := unit.PullRequestsConfig()
+		if !config.AutodetectManualMerge {
+			return false
+		}
+	} else {
+		log.Error("PullRequest[%d].BaseRepo.GetUnit(models.UnitTypePullRequests): %v", pr.ID, err)
+		return false
+	}
+
 	commit, err := getMergeCommit(pr)
 	if err != nil {
 		log.Error("PullRequest[%d].getMergeCommit: %v", pr.ID, err)
diff --git a/services/pull/merge.go b/services/pull/merge.go
index 9d6eadc524..bbe631f68c 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -615,3 +615,54 @@ func CheckPRReadyToMerge(pr *models.PullRequest, skipProtectedFilesCheck bool) (
 
 	return nil
 }
+
+// MergedManually mark pr as merged manually
+func MergedManually(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repository, commitID string) (err error) {
+	prUnit, err := pr.BaseRepo.GetUnit(models.UnitTypePullRequests)
+	if err != nil {
+		return
+	}
+	prConfig := prUnit.PullRequestsConfig()
+
+	// Check if merge style is correct and allowed
+	if !prConfig.IsMergeStyleAllowed(models.MergeStyleManuallyMerged) {
+		return models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: models.MergeStyleManuallyMerged}
+	}
+
+	if len(commitID) < 40 {
+		return fmt.Errorf("Wrong commit ID")
+	}
+
+	commit, err := baseGitRepo.GetCommit(commitID)
+	if err != nil {
+		if git.IsErrNotExist(err) {
+			return fmt.Errorf("Wrong commit ID")
+		}
+		return
+	}
+
+	ok, err := baseGitRepo.IsCommitInBranch(commitID, pr.BaseBranch)
+	if err != nil {
+		return
+	}
+	if !ok {
+		return fmt.Errorf("Wrong commit ID")
+	}
+
+	pr.MergedCommitID = commitID
+	pr.MergedUnix = timeutil.TimeStamp(commit.Author.When.Unix())
+	pr.Status = models.PullRequestStatusManuallyMerged
+	pr.Merger = doer
+	pr.MergerID = doer.ID
+
+	merged := false
+	if merged, err = pr.SetMerged(); err != nil {
+		return
+	} else if !merged {
+		return fmt.Errorf("SetMerged failed")
+	}
+
+	notification.NotifyMergePullRequest(pr, doer)
+	log.Info("manuallyMerged[%d]: Marked as manually merged into %s/%s by commit id: %s", pr.ID, pr.BaseRepo.Name, pr.BaseBranch, commit.ID.String())
+	return nil
+}
diff --git a/services/pull/patch.go b/services/pull/patch.go
index 2692d848c4..72b459bf2c 100644
--- a/services/pull/patch.go
+++ b/services/pull/patch.go
@@ -80,7 +80,7 @@ func TestPatch(pr *models.PullRequest) error {
 	pr.MergeBase = strings.TrimSpace(pr.MergeBase)
 
 	// 2. Check for conflicts
-	if conflicts, err := checkConflicts(pr, gitRepo, tmpBasePath); err != nil || conflicts {
+	if conflicts, err := checkConflicts(pr, gitRepo, tmpBasePath); err != nil || conflicts || pr.Status == models.PullRequestStatusEmpty {
 		return err
 	}
 
@@ -125,8 +125,9 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath
 	// 1a. if the size of that patch is 0 - there can be no conflicts!
 	if stat.Size() == 0 {
 		log.Debug("PullRequest[%d]: Patch is empty - ignoring", pr.ID)
-		pr.Status = models.PullRequestStatusMergeable
+		pr.Status = models.PullRequestStatusEmpty
 		pr.ConflictedFiles = []string{}
+		pr.ChangedProtectedFiles = []string{}
 		return false, nil
 	}
 
diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl
index 245bdaa542..071a790457 100644
--- a/templates/repo/diff/compare.tmpl
+++ b/templates/repo/diff/compare.tmpl
@@ -83,7 +83,17 @@
 	{{end}}
 
 	{{if .IsNothingToCompare}}
-    	<div class="ui segment">{{.i18n.Tr "repo.pulls.nothing_to_compare"}}</div>
+		{{if and $.IsSigned $.AllowEmptyPr (not .Repository.IsArchived) }}
+			<div class="ui segment">{{.i18n.Tr "repo.pulls.nothing_to_compare_and_allow_empty_pr"}}</div>
+			<div class="ui info message show-form-container">
+				<button class="ui button green show-form">{{.i18n.Tr "repo.pulls.new"}}</button>
+			</div>
+			<div class="pullrequest-form" style="display: none">
+				{{template "repo/issue/new_form" .}}
+			</div>
+		{{else}}
+			<div class="ui segment">{{.i18n.Tr "repo.pulls.nothing_to_compare"}}</div>
+		{{end}}
 	{{else if and .PageIsComparePull (gt .CommitCount 0)}}
 		{{if .HasPullRequest}}
         	<div class="ui segment">
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index cfacde9648..b8d78f5697 100644
--- a/templates/repo/issue/view_content/comments.tmpl
+++ b/templates/repo/issue/view_content/comments.tmpl
@@ -124,7 +124,11 @@
 			<span class="text grey">
 				<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
 				{{$link := printf "%s/commit/%s" $.Repository.HTMLURL $.Issue.PullRequest.MergedCommitID}}
-				{{$.i18n.Tr "repo.issues.pull_merged_at" $link (ShortSha $.Issue.PullRequest.MergedCommitID) ($.BaseTarget|Escape) $createdStr | Str2html}}
+				{{if eq $.Issue.PullRequest.Status 3}}
+					{{$.i18n.Tr "repo.issues.manually_pull_merged_at" $link (ShortSha $.Issue.PullRequest.MergedCommitID) $.BaseTarget $createdStr | Str2html}}
+				{{else}}
+					{{$.i18n.Tr "repo.issues.pull_merged_at" $link (ShortSha $.Issue.PullRequest.MergedCommitID) $.BaseTarget $createdStr | Str2html}}
+				{{end}}
 			</span>
 		</div>
 	{{else if eq .Type 3 5 6}}
diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl
index 9e883c0a93..2175fad067 100644
--- a/templates/repo/issue/view_content/pull.tmpl
+++ b/templates/repo/issue/view_content/pull.tmpl
@@ -118,6 +118,7 @@
 	{{- else if and .EnableStatusCheck (or (not $.LatestCommitStatus) .RequiredStatusCheckState.IsPending .RequiredStatusCheckState.IsWarning)}}yellow
 	{{- else if and .AllowMerge .RequireSigned (not .WillSign)}}red
 	{{- else if .Issue.PullRequest.IsChecking}}yellow
+	{{- else if .Issue.PullRequest.IsEmpty}}grey
 	{{- else if .Issue.PullRequest.CanAutoMerge}}green
 	{{- else}}red{{end}}">{{svg "octicon-git-merge" 32}}</a>
 	<div class="content">
@@ -128,7 +129,11 @@
 				<div class="item text">
 					{{if .Issue.PullRequest.MergedCommitID}}
 						{{$link := printf "%s/commit/%s" $.Repository.HTMLURL .Issue.PullRequest.MergedCommitID}}
-						{{$.i18n.Tr "repo.pulls.merged_as" $link (ShortSha .Issue.PullRequest.MergedCommitID) | Safe}}
+						{{if eq $.Issue.PullRequest.Status 3}}
+							{{$.i18n.Tr "repo.pulls.manually_merged_as" $link (ShortSha .Issue.PullRequest.MergedCommitID) | Safe}}
+						{{else}}
+							{{$.i18n.Tr "repo.pulls.merged_as" $link (ShortSha .Issue.PullRequest.MergedCommitID) | Safe}}
+						{{end}}
 					{{else}}
 						{{$.i18n.Tr "repo.pulls.has_merged"}}
 					{{end}}
@@ -176,6 +181,11 @@
 					<i class="icon icon-octicon">{{svg "octicon-sync"}}</i>
 					{{$.i18n.Tr "repo.pulls.is_checking"}}
 				</div>
+			{{else if .Issue.PullRequest.IsEmpty}}
+				<div class="item text grey">
+					<i class="icon icon-octicon">{{svg "octicon-alert" 16}}</i>
+					{{$.i18n.Tr "repo.pulls.is_empty"}}
+				</div>
 			{{else if .Issue.PullRequest.CanAutoMerge}}
 				{{if .IsBlockedByApprovals}}
 					<div class="item">
@@ -350,6 +360,22 @@
 								</form>
 							</div>
 							{{end}}
+							{{if and $prUnit.PullRequestsConfig.AllowManualMerge $.IsRepoAdmin}}
+								<div class="ui form manually-merged-fields" style="display: none">
+									<form action="{{.Link}}/merge" method="post">
+										{{.CsrfTokenHtml}}
+										<div class="field">
+											<input type="text" name="merge_commit_id"  placeholder="{{$.i18n.Tr "repo.pulls.merge_commit_id"}}">
+										</div>
+										<button class="ui red button" type="submit" name="do" value="manually-merged">
+											{{$.i18n.Tr "repo.pulls.merge_manually"}}
+										</button>
+										<button class="ui button merge-cancel">
+											{{$.i18n.Tr "cancel"}}
+										</button>
+									</form>
+								</div>
+							{{end}}
 							<div class="dib">
 								<div class="ui {{if $notAllOverridableChecksOk}}red{{else}}green{{end}} buttons merge-button">
 									<button class="ui button" data-do="{{.MergeStyle}}">
@@ -367,6 +393,9 @@
 										{{if eq .MergeStyle "squash"}}
 											{{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}}
 										{{end}}
+										{{if eq .MergeStyle "manually-merged"}}
+											{{$.i18n.Tr "repo.pulls.merge_manually"}}
+										{{end}}
 										</span>
 									</button>
 									{{if gt $prUnit.PullRequestsConfig.AllowedMergeStyleCount 1}}
@@ -385,6 +414,9 @@
 												{{if $prUnit.PullRequestsConfig.AllowSquash}}
 												<div class="item{{if eq .MergeStyle "squash"}} active selected{{end}}" data-do="squash">{{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}}</div>
 												{{end}}
+												{{if and $prUnit.PullRequestsConfig.AllowManualMerge $.IsRepoAdmin}}
+												<div class="item{{if eq .MergeStyle "manually-merged"}} active selected{{end}}" data-do="manually-merged">{{$.i18n.Tr "repo.pulls.merge_manually"}}</div>
+												{{end}}
 											</div>
 										</div>
 									{{end}}
@@ -492,6 +524,30 @@
 					{{end}}
 				</div>
 			{{end}}
+
+			{{if $.StillCanManualMerge}}
+				<div class="ui divider"></div>
+				<div class="ui form manually-merged-fields" style="display: none">
+					<form action="{{.Link}}/merge" method="post">
+						{{.CsrfTokenHtml}}
+						<div class="field">
+							<input type="text" name="merge_commit_id"  placeholder="{{$.i18n.Tr "repo.pulls.merge_commit_id"}}">
+						</div>
+						<button class="ui red button" type="submit" name="do" value="manually-merged">
+							{{$.i18n.Tr "repo.pulls.merge_manually"}}
+						</button>
+						<button class="ui button merge-cancel">
+							{{$.i18n.Tr "cancel"}}
+						</button>
+					</form>
+				</div>
+
+				<div class="ui red buttons merge-button">
+					<button class="ui button" data-do="manually-merged">
+						{{$.i18n.Tr "repo.pulls.merge_manually"}}
+					</button>
+				</div>
+			{{end}}
 		</div>
 	</div>
 </div>
diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl
index 2f2787d67c..f6cbb9206c 100644
--- a/templates/repo/issue/view_title.tmpl
+++ b/templates/repo/issue/view_title.tmpl
@@ -20,7 +20,7 @@
 		{{end}}
 	</div>
 	{{if .HasMerged}}
-		<div class="ui purple large label">{{svg "octicon-git-merge"}} {{.i18n.Tr "repo.pulls.merged"}}</div>
+		<div class="ui purple large label">{{svg "octicon-git-merge" 16}} {{if eq .Issue.PullRequest.Status 3}}{{.i18n.Tr "repo.pulls.manually_merged"}}{{else}}{{.i18n.Tr "repo.pulls.merged"}}{{end}}</div>
 	{{else if .Issue.IsClosed}}
 		<div class="ui red large label">{{if .Issue.IsPull}}{{svg "octicon-git-pull-request"}}{{else}}{{svg "octicon-issue-closed"}}{{end}} {{.i18n.Tr "repo.issues.closed_title"}}</div>
 	{{else if .Issue.IsPull}}
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl
index b69f90f9c5..9d87101671 100644
--- a/templates/repo/settings/options.tmpl
+++ b/templates/repo/settings/options.tmpl
@@ -330,6 +330,18 @@
 								<label>{{.i18n.Tr "repo.settings.pulls.allow_squash_commits"}}</label>
 							</div>
 						</div>
+						<div class="field">
+							<div class="ui checkbox">
+								<input name="pulls_allow_manual_merge" type="checkbox" {{if or (not $pullRequestEnabled) ($prUnit.PullRequestsConfig.AllowManualMerge)}}checked{{end}}>
+								<label>{{.i18n.Tr "repo.settings.pulls.allow_manual_merge"}}</label>
+							</div>
+						</div>
+						<div class="field">
+							<div class="ui checkbox">
+								<input name="enable_autodetect_manual_merge" type="checkbox" {{if or (not $pullRequestEnabled) ($prUnit.PullRequestsConfig.AutodetectManualMerge)}}checked{{end}}>
+								<label>{{.i18n.Tr "repo.settings.pulls.enable_autodetect_manual_merge"}}</label>
+							</div>
+						</div>
 					</div>
 				{{end}}
 
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 1b1d9e5c97..930af907ea 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -13586,6 +13586,11 @@
       "description": "EditRepoOption options when editing a repository's properties",
       "type": "object",
       "properties": {
+        "allow_manual_merge": {
+          "description": "either `true` to allow mark pr as merged manually, or `false` to prevent it. `has_pull_requests` must be `true`.",
+          "type": "boolean",
+          "x-go-name": "AllowManualMerge"
+        },
         "allow_merge_commits": {
           "description": "either `true` to allow merging pull requests with a merge commit, or `false` to prevent merging pull requests with merge commits. `has_pull_requests` must be `true`.",
           "type": "boolean",
@@ -13611,6 +13616,11 @@
           "type": "boolean",
           "x-go-name": "Archived"
         },
+        "autodetect_manual_merge": {
+          "description": "either `true` to enable AutodetectManualMerge, or `false` to prevent it. `has_pull_requests` must be `true`, Note: In some special cases, misjudgments can occur.",
+          "type": "boolean",
+          "x-go-name": "AutodetectManualMerge"
+        },
         "default_branch": {
           "description": "sets the default branch for this repository.",
           "type": "string",
@@ -14596,9 +14606,13 @@
             "merge",
             "rebase",
             "rebase-merge",
-            "squash"
+            "squash",
+            "manually-merged"
           ]
         },
+        "MergeCommitID": {
+          "type": "string"
+        },
         "MergeMessageField": {
           "type": "string"
         },