mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-15 01:34:26 +02:00
With stacked branches, create fixup commit at the end of the branch it belongs to (#3892)
- **PR Description** When working with stacked branches, and creating a fixup commit for a commit in one of the lower branches of the stack, the fixup was created at the top of the stack and the user needed to move it down to the right branch manually. This is unnecessary extra work; create it at the end of the right branch automatically.
This commit is contained in:
@ -246,16 +246,18 @@ func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
|
|||||||
|
|
||||||
// Takes the hash of some commit, and the hash of a fixup commit that was created
|
// Takes the hash of some commit, and the hash of a fixup commit that was created
|
||||||
// at the end of the branch, then moves the fixup commit down to right after the
|
// at the end of the branch, then moves the fixup commit down to right after the
|
||||||
// original commit, changing its type to "fixup"
|
// original commit, changing its type to "fixup" (only if ChangeToFixup is true)
|
||||||
type MoveFixupCommitDownInstruction struct {
|
type MoveFixupCommitDownInstruction struct {
|
||||||
OriginalHash string
|
OriginalHash string
|
||||||
FixupHash string
|
FixupHash string
|
||||||
|
ChangeToFixup bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMoveFixupCommitDownInstruction(originalHash string, fixupHash string) Instruction {
|
func NewMoveFixupCommitDownInstruction(originalHash string, fixupHash string, changeToFixup bool) Instruction {
|
||||||
return &MoveFixupCommitDownInstruction{
|
return &MoveFixupCommitDownInstruction{
|
||||||
OriginalHash: originalHash,
|
OriginalHash: originalHash,
|
||||||
FixupHash: fixupHash,
|
FixupHash: fixupHash,
|
||||||
|
ChangeToFixup: changeToFixup,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +271,7 @@ func (self *MoveFixupCommitDownInstruction) SerializedInstructions() string {
|
|||||||
|
|
||||||
func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
|
func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
|
||||||
return handleInteractiveRebase(common, func(path string) error {
|
return handleInteractiveRebase(common, func(path string) error {
|
||||||
return utils.MoveFixupCommitDown(path, self.OriginalHash, self.FixupHash, getCommentChar())
|
return utils.MoveFixupCommitDown(path, self.OriginalHash, self.FixupHash, self.ChangeToFixup, getCommentChar())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,6 +284,11 @@ func (self *RebaseCommands) GitRebaseEditTodo(todosFileContent []byte) error {
|
|||||||
return cmdObj.Run()
|
return cmdObj.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *RebaseCommands) getHashOfLastCommitMade() (string, error) {
|
||||||
|
cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
|
||||||
|
return self.cmd.New(cmdArgs).RunWithOutput()
|
||||||
|
}
|
||||||
|
|
||||||
// AmendTo amends the given commit with whatever files are staged
|
// AmendTo amends the given commit with whatever files are staged
|
||||||
func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error {
|
func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error {
|
||||||
commit := commits[commitIndex]
|
commit := commits[commitIndex]
|
||||||
@ -292,9 +297,7 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the hash of the commit we just created
|
fixupHash, err := self.getHashOfLastCommitMade()
|
||||||
cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
|
|
||||||
fixupHash, err := self.cmd.New(cmdArgs).RunWithOutput()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -302,7 +305,20 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
|
|||||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||||
baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1),
|
baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1),
|
||||||
overrideEditor: true,
|
overrideEditor: true,
|
||||||
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash, fixupHash),
|
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash, fixupHash, true),
|
||||||
|
}).Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *RebaseCommands) MoveFixupCommitDown(commits []*models.Commit, targetCommitIndex int) error {
|
||||||
|
fixupHash, err := self.getHashOfLastCommitMade()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||||
|
baseHashOrRoot: getBaseHashOrRoot(commits, targetCommitIndex+1),
|
||||||
|
overrideEditor: true,
|
||||||
|
instruction: daemon.NewMoveFixupCommitDownInstruction(commits[targetCommitIndex].Hash, fixupHash, false),
|
||||||
}).Run()
|
}).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -895,6 +895,10 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := self.moveFixupCommitToOwnerStackedBranch(commit); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
self.context().MoveSelectedLine(1)
|
self.context().MoveSelectedLine(1)
|
||||||
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
|
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
|
||||||
})
|
})
|
||||||
@ -924,6 +928,50 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *LocalCommitsController) moveFixupCommitToOwnerStackedBranch(targetCommit *models.Commit) error {
|
||||||
|
if self.c.Git().Version.IsOlderThan(2, 38, 0) {
|
||||||
|
// Git 2.38.0 introduced the `rebase.updateRefs` config option. Don't
|
||||||
|
// move the commit down with older versions, as it would break the stack.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||||
|
// Can't move commits while rebasing
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetCommit.Status == models.StatusMerged {
|
||||||
|
// Target commit is already on main. It's a bit questionable that we
|
||||||
|
// allow creating a fixup commit for it in the first place, but we
|
||||||
|
// always did, so why restrict that now; however, it doesn't make sense
|
||||||
|
// to move the created fixup commit down in that case.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.c.Git().Config.GetRebaseUpdateRefs() {
|
||||||
|
// If the user has disabled rebase.updateRefs, we don't move the fixup
|
||||||
|
// because this would break the stack of branches (presumably they like
|
||||||
|
// to manage it themselves manually, or something).
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
headOfOwnerBranchIdx := -1
|
||||||
|
for i := self.context().GetSelectedLineIdx(); i > 0; i-- {
|
||||||
|
if lo.SomeBy(self.c.Model().Branches, func(b *models.Branch) bool {
|
||||||
|
return b.CommitHash == self.c.Model().Commits[i].Hash
|
||||||
|
}) {
|
||||||
|
headOfOwnerBranchIdx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if headOfOwnerBranchIdx == -1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.c.Git().Rebase.MoveFixupCommitDown(self.c.Model().Commits, headOfOwnerBranchIdx)
|
||||||
|
}
|
||||||
|
|
||||||
func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, includeFileChanges bool) error {
|
func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, includeFileChanges bool) error {
|
||||||
commitMessage, err := self.c.Git().Commit.GetCommitMessage(commit.Hash)
|
commitMessage, err := self.c.Git().Commit.GetCommitMessage(commit.Hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -947,6 +995,10 @@ func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, inc
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := self.moveFixupCommitToOwnerStackedBranch(commit); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
self.context().MoveSelectedLine(1)
|
self.context().MoveSelectedLine(1)
|
||||||
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
|
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
|
||||||
})
|
})
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
package commit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
var CreateFixupCommitInBranchStack = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
|
Description: "Create a fixup commit in a stack of branches, verify that it is created at the end of the branch it belongs to",
|
||||||
|
ExtraCmdArgs: []string{},
|
||||||
|
Skip: false,
|
||||||
|
GitVersion: AtLeast("2.38.0"),
|
||||||
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
|
SetupRepo: func(shell *Shell) {
|
||||||
|
shell.NewBranch("branch1")
|
||||||
|
shell.EmptyCommit("branch1 commit 1")
|
||||||
|
shell.EmptyCommit("branch1 commit 2")
|
||||||
|
shell.EmptyCommit("branch1 commit 3")
|
||||||
|
shell.NewBranch("branch2")
|
||||||
|
shell.EmptyCommit("branch2 commit 1")
|
||||||
|
shell.EmptyCommit("branch2 commit 2")
|
||||||
|
shell.CreateFileAndAdd("fixup-file", "fixup content")
|
||||||
|
|
||||||
|
shell.SetConfig("rebase.updateRefs", "true")
|
||||||
|
},
|
||||||
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
|
t.Views().Commits().
|
||||||
|
Focus().
|
||||||
|
Lines(
|
||||||
|
Contains("CI ◯ branch2 commit 2"),
|
||||||
|
Contains("CI ◯ branch2 commit 1"),
|
||||||
|
Contains("CI ◯ * branch1 commit 3"),
|
||||||
|
Contains("CI ◯ branch1 commit 2"),
|
||||||
|
Contains("CI ◯ branch1 commit 1"),
|
||||||
|
).
|
||||||
|
NavigateToLine(Contains("branch1 commit 2")).
|
||||||
|
Press(keys.Commits.CreateFixupCommit).
|
||||||
|
Tap(func() {
|
||||||
|
t.ExpectPopup().Menu().
|
||||||
|
Title(Equals("Create fixup commit")).
|
||||||
|
Select(Contains("fixup! commit")).
|
||||||
|
Confirm()
|
||||||
|
}).
|
||||||
|
Lines(
|
||||||
|
Contains("CI ◯ branch2 commit 2"),
|
||||||
|
Contains("CI ◯ branch2 commit 1"),
|
||||||
|
Contains("CI ◯ * fixup! branch1 commit 2"),
|
||||||
|
Contains("CI ◯ branch1 commit 3"),
|
||||||
|
Contains("CI ◯ branch1 commit 2"),
|
||||||
|
Contains("CI ◯ branch1 commit 1"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
@ -87,6 +87,7 @@ var tests = []*components.IntegrationTest{
|
|||||||
commit.CommitWithNonMatchingBranchName,
|
commit.CommitWithNonMatchingBranchName,
|
||||||
commit.CommitWithPrefix,
|
commit.CommitWithPrefix,
|
||||||
commit.CreateAmendCommit,
|
commit.CreateAmendCommit,
|
||||||
|
commit.CreateFixupCommitInBranchStack,
|
||||||
commit.CreateTag,
|
commit.CreateTag,
|
||||||
commit.DiscardOldFileChanges,
|
commit.DiscardOldFileChanges,
|
||||||
commit.FindBaseCommitForFixup,
|
commit.FindBaseCommitForFixup,
|
||||||
|
@ -221,13 +221,13 @@ func moveTodosUp(todos []todo.Todo, todosToMove []Todo) ([]todo.Todo, error) {
|
|||||||
return todos, nil
|
return todos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func MoveFixupCommitDown(fileName string, originalHash string, fixupHash string, commentChar byte) error {
|
func MoveFixupCommitDown(fileName string, originalHash string, fixupHash string, changeToFixup bool, commentChar byte) error {
|
||||||
todos, err := ReadRebaseTodoFile(fileName, commentChar)
|
todos, err := ReadRebaseTodoFile(fileName, commentChar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
newTodos, err := moveFixupCommitDown(todos, originalHash, fixupHash)
|
newTodos, err := moveFixupCommitDown(todos, originalHash, fixupHash, changeToFixup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -235,7 +235,7 @@ func MoveFixupCommitDown(fileName string, originalHash string, fixupHash string,
|
|||||||
return WriteRebaseTodoFile(fileName, newTodos, commentChar)
|
return WriteRebaseTodoFile(fileName, newTodos, commentChar)
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveFixupCommitDown(todos []todo.Todo, originalHash string, fixupHash string) ([]todo.Todo, error) {
|
func moveFixupCommitDown(todos []todo.Todo, originalHash string, fixupHash string, changeToFixup bool) ([]todo.Todo, error) {
|
||||||
isOriginal := func(t todo.Todo) bool {
|
isOriginal := func(t todo.Todo) bool {
|
||||||
return (t.Command == todo.Pick || t.Command == todo.Merge) && equalHash(t.Commit, originalHash)
|
return (t.Command == todo.Pick || t.Command == todo.Merge) && equalHash(t.Commit, originalHash)
|
||||||
}
|
}
|
||||||
@ -259,7 +259,9 @@ func moveFixupCommitDown(todos []todo.Todo, originalHash string, fixupHash strin
|
|||||||
|
|
||||||
newTodos := MoveElement(todos, fixupIndex, originalIndex+1)
|
newTodos := MoveElement(todos, fixupIndex, originalIndex+1)
|
||||||
|
|
||||||
newTodos[originalIndex+1].Command = todo.Fixup
|
if changeToFixup {
|
||||||
|
newTodos[originalIndex+1].Command = todo.Fixup
|
||||||
|
}
|
||||||
|
|
||||||
return newTodos, nil
|
return newTodos, nil
|
||||||
}
|
}
|
||||||
|
@ -266,23 +266,40 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
|
|||||||
todos []todo.Todo
|
todos []todo.Todo
|
||||||
originalHash string
|
originalHash string
|
||||||
fixupHash string
|
fixupHash string
|
||||||
|
changeToFixup bool
|
||||||
expectedTodos []todo.Todo
|
expectedTodos []todo.Todo
|
||||||
expectedErr error
|
expectedErr error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "fixup commit is the last commit",
|
name: "fixup commit is the last commit (change to fixup)",
|
||||||
todos: []todo.Todo{
|
todos: []todo.Todo{
|
||||||
{Command: todo.Pick, Commit: "original"},
|
{Command: todo.Pick, Commit: "original"},
|
||||||
{Command: todo.Pick, Commit: "fixup"},
|
{Command: todo.Pick, Commit: "fixup"},
|
||||||
},
|
},
|
||||||
originalHash: "original",
|
originalHash: "original",
|
||||||
fixupHash: "fixup",
|
fixupHash: "fixup",
|
||||||
|
changeToFixup: true,
|
||||||
expectedTodos: []todo.Todo{
|
expectedTodos: []todo.Todo{
|
||||||
{Command: todo.Pick, Commit: "original"},
|
{Command: todo.Pick, Commit: "original"},
|
||||||
{Command: todo.Fixup, Commit: "fixup"},
|
{Command: todo.Fixup, Commit: "fixup"},
|
||||||
},
|
},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "fixup commit is the last commit (don't change to fixup)",
|
||||||
|
todos: []todo.Todo{
|
||||||
|
{Command: todo.Pick, Commit: "original"},
|
||||||
|
{Command: todo.Pick, Commit: "fixup"},
|
||||||
|
},
|
||||||
|
originalHash: "original",
|
||||||
|
fixupHash: "fixup",
|
||||||
|
changeToFixup: false,
|
||||||
|
expectedTodos: []todo.Todo{
|
||||||
|
{Command: todo.Pick, Commit: "original"},
|
||||||
|
{Command: todo.Pick, Commit: "fixup"},
|
||||||
|
},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "fixup commit is separated from original commit",
|
name: "fixup commit is separated from original commit",
|
||||||
todos: []todo.Todo{
|
todos: []todo.Todo{
|
||||||
@ -290,8 +307,9 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
|
|||||||
{Command: todo.Pick, Commit: "other"},
|
{Command: todo.Pick, Commit: "other"},
|
||||||
{Command: todo.Pick, Commit: "fixup"},
|
{Command: todo.Pick, Commit: "fixup"},
|
||||||
},
|
},
|
||||||
originalHash: "original",
|
originalHash: "original",
|
||||||
fixupHash: "fixup",
|
fixupHash: "fixup",
|
||||||
|
changeToFixup: true,
|
||||||
expectedTodos: []todo.Todo{
|
expectedTodos: []todo.Todo{
|
||||||
{Command: todo.Pick, Commit: "original"},
|
{Command: todo.Pick, Commit: "original"},
|
||||||
{Command: todo.Fixup, Commit: "fixup"},
|
{Command: todo.Fixup, Commit: "fixup"},
|
||||||
@ -306,8 +324,9 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
|
|||||||
{Command: todo.Pick, Commit: "other"},
|
{Command: todo.Pick, Commit: "other"},
|
||||||
{Command: todo.Pick, Commit: "fixup"},
|
{Command: todo.Pick, Commit: "fixup"},
|
||||||
},
|
},
|
||||||
originalHash: "original",
|
originalHash: "original",
|
||||||
fixupHash: "fixup",
|
fixupHash: "fixup",
|
||||||
|
changeToFixup: true,
|
||||||
expectedTodos: []todo.Todo{
|
expectedTodos: []todo.Todo{
|
||||||
{Command: todo.Merge, Commit: "original"},
|
{Command: todo.Merge, Commit: "original"},
|
||||||
{Command: todo.Fixup, Commit: "fixup"},
|
{Command: todo.Fixup, Commit: "fixup"},
|
||||||
@ -324,6 +343,7 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
|
|||||||
},
|
},
|
||||||
originalHash: "original",
|
originalHash: "original",
|
||||||
fixupHash: "fixup",
|
fixupHash: "fixup",
|
||||||
|
changeToFixup: true,
|
||||||
expectedTodos: nil,
|
expectedTodos: nil,
|
||||||
expectedErr: errors.New("Expected exactly one original hash, found 2"),
|
expectedErr: errors.New("Expected exactly one original hash, found 2"),
|
||||||
},
|
},
|
||||||
@ -336,6 +356,7 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
|
|||||||
},
|
},
|
||||||
originalHash: "original",
|
originalHash: "original",
|
||||||
fixupHash: "fixup",
|
fixupHash: "fixup",
|
||||||
|
changeToFixup: true,
|
||||||
expectedTodos: nil,
|
expectedTodos: nil,
|
||||||
expectedErr: errors.New("Expected exactly one fixup hash, found 2"),
|
expectedErr: errors.New("Expected exactly one fixup hash, found 2"),
|
||||||
},
|
},
|
||||||
@ -346,6 +367,7 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
|
|||||||
},
|
},
|
||||||
originalHash: "original",
|
originalHash: "original",
|
||||||
fixupHash: "fixup",
|
fixupHash: "fixup",
|
||||||
|
changeToFixup: true,
|
||||||
expectedTodos: nil,
|
expectedTodos: nil,
|
||||||
expectedErr: errors.New("Expected exactly one fixup hash, found 0"),
|
expectedErr: errors.New("Expected exactly one fixup hash, found 0"),
|
||||||
},
|
},
|
||||||
@ -356,6 +378,7 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
|
|||||||
},
|
},
|
||||||
originalHash: "original",
|
originalHash: "original",
|
||||||
fixupHash: "fixup",
|
fixupHash: "fixup",
|
||||||
|
changeToFixup: true,
|
||||||
expectedTodos: nil,
|
expectedTodos: nil,
|
||||||
expectedErr: errors.New("Expected exactly one original hash, found 0"),
|
expectedErr: errors.New("Expected exactly one original hash, found 0"),
|
||||||
},
|
},
|
||||||
@ -363,7 +386,7 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
|
|||||||
|
|
||||||
for _, scenario := range scenarios {
|
for _, scenario := range scenarios {
|
||||||
t.Run(scenario.name, func(t *testing.T) {
|
t.Run(scenario.name, func(t *testing.T) {
|
||||||
actualTodos, actualErr := moveFixupCommitDown(scenario.todos, scenario.originalHash, scenario.fixupHash)
|
actualTodos, actualErr := moveFixupCommitDown(scenario.todos, scenario.originalHash, scenario.fixupHash, scenario.changeToFixup)
|
||||||
|
|
||||||
if scenario.expectedErr == nil {
|
if scenario.expectedErr == nil {
|
||||||
assert.NoError(t, actualErr)
|
assert.NoError(t, actualErr)
|
||||||
|
Reference in New Issue
Block a user