diff --git a/modules/git/command.go b/modules/git/command.go
index 9a65279a8c..a42d859f55 100644
--- a/modules/git/command.go
+++ b/modules/git/command.go
@@ -40,7 +40,7 @@ const DefaultLocale = "C"
 
 // Command represents a command with its subcommands or arguments.
 type Command struct {
-	name             string
+	prog             string
 	args             []string
 	parentContext    context.Context
 	desc             string
@@ -49,10 +49,28 @@ type Command struct {
 }
 
 func (c *Command) String() string {
-	if len(c.args) == 0 {
-		return c.name
+	return c.toString(false)
+}
+
+func (c *Command) toString(sanitizing bool) string {
+	// WARNING: this function is for debugging purposes only. It's much better than old code (which only joins args with space),
+	// It's impossible to make a simple and 100% correct implementation of argument quoting for different platforms.
+	debugQuote := func(s string) string {
+		if strings.ContainsAny(s, " `'\"\t\r\n") {
+			return fmt.Sprintf("%q", s)
+		}
+		return s
 	}
-	return fmt.Sprintf("%s %s", c.name, strings.Join(c.args, " "))
+	a := make([]string, 0, len(c.args)+1)
+	a = append(a, debugQuote(c.prog))
+	for _, arg := range c.args {
+		if sanitizing && (strings.Contains(arg, "://") && strings.Contains(arg, "@")) {
+			a = append(a, debugQuote(util.SanitizeCredentialURLs(arg)))
+		} else {
+			a = append(a, debugQuote(arg))
+		}
+	}
+	return strings.Join(a, " ")
 }
 
 // NewCommand creates and returns a new Git Command based on given command and arguments.
@@ -67,7 +85,7 @@ func NewCommand(ctx context.Context, args ...internal.CmdArg) *Command {
 		cargs = append(cargs, string(arg))
 	}
 	return &Command{
-		name:             GitExecutable,
+		prog:             GitExecutable,
 		args:             cargs,
 		parentContext:    ctx,
 		globalArgsLength: len(globalCommandArgs),
@@ -82,7 +100,7 @@ func NewCommandContextNoGlobals(ctx context.Context, args ...internal.CmdArg) *C
 		cargs = append(cargs, string(arg))
 	}
 	return &Command{
-		name:          GitExecutable,
+		prog:          GitExecutable,
 		args:          cargs,
 		parentContext: ctx,
 	}
@@ -250,28 +268,18 @@ func (c *Command) Run(opts *RunOpts) error {
 	}
 
 	if len(opts.Dir) == 0 {
-		log.Debug("%s", c)
+		log.Debug("git.Command.Run: %s", c)
 	} else {
-		log.Debug("%s: %v", opts.Dir, c)
+		log.Debug("git.Command.RunDir(%s): %s", opts.Dir, c)
 	}
 
 	desc := c.desc
 	if desc == "" {
-		args := c.args[c.globalArgsLength:]
-		var argSensitiveURLIndexes []int
-		for i, arg := range c.args {
-			if strings.Contains(arg, "://") && strings.Contains(arg, "@") {
-				argSensitiveURLIndexes = append(argSensitiveURLIndexes, i)
-			}
+		if opts.Dir == "" {
+			desc = fmt.Sprintf("git: %s", c.toString(true))
+		} else {
+			desc = fmt.Sprintf("git(dir:%s): %s", opts.Dir, c.toString(true))
 		}
-		if len(argSensitiveURLIndexes) > 0 {
-			args = make([]string, len(c.args))
-			copy(args, c.args)
-			for _, urlArgIndex := range argSensitiveURLIndexes {
-				args[urlArgIndex] = util.SanitizeCredentialURLs(args[urlArgIndex])
-			}
-		}
-		desc = fmt.Sprintf("%s %s [repo_path: %s]", c.name, strings.Join(args, " "), opts.Dir)
 	}
 
 	var ctx context.Context
@@ -285,7 +293,7 @@ func (c *Command) Run(opts *RunOpts) error {
 	}
 	defer finished()
 
-	cmd := exec.CommandContext(ctx, c.name, c.args...)
+	cmd := exec.CommandContext(ctx, c.prog, c.args...)
 	if opts.Env == nil {
 		cmd.Env = os.Environ()
 	} else {
diff --git a/modules/git/command_test.go b/modules/git/command_test.go
index 4e5f991d31..9a6228c9ad 100644
--- a/modules/git/command_test.go
+++ b/modules/git/command_test.go
@@ -52,3 +52,11 @@ func TestGitArgument(t *testing.T) {
 	assert.True(t, isSafeArgumentValue("x"))
 	assert.False(t, isSafeArgumentValue("-x"))
 }
+
+func TestCommandString(t *testing.T) {
+	cmd := NewCommandContextNoGlobals(context.Background(), "a", "-m msg", "it's a test", `say "hello"`)
+	assert.EqualValues(t, cmd.prog+` a "-m msg" "it's a test" "say \"hello\""`, cmd.String())
+
+	cmd = NewCommandContextNoGlobals(context.Background(), "url: https://a:b@c/")
+	assert.EqualValues(t, cmd.prog+` "url: https://sanitized-credential@c/"`, cmd.toString(true))
+}
diff --git a/modules/git/repo.go b/modules/git/repo.go
index 233f7f20cf..d29ec40ae2 100644
--- a/modules/git/repo.go
+++ b/modules/git/repo.go
@@ -209,49 +209,22 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
 	} else {
 		cmd.SetDescription(fmt.Sprintf("push branch %s to %s (force: %t, mirror: %t)", opts.Branch, opts.Remote, opts.Force, opts.Mirror))
 	}
-	var outbuf, errbuf strings.Builder
 
-	if opts.Timeout == 0 {
-		opts.Timeout = -1
-	}
-
-	err := cmd.Run(&RunOpts{
-		Env:     opts.Env,
-		Timeout: opts.Timeout,
-		Dir:     repoPath,
-		Stdout:  &outbuf,
-		Stderr:  &errbuf,
-	})
+	stdout, stderr, err := cmd.RunStdString(&RunOpts{Env: opts.Env, Timeout: opts.Timeout, Dir: repoPath})
 	if err != nil {
-		if strings.Contains(errbuf.String(), "non-fast-forward") {
-			return &ErrPushOutOfDate{
-				StdOut: outbuf.String(),
-				StdErr: errbuf.String(),
-				Err:    err,
-			}
-		} else if strings.Contains(errbuf.String(), "! [remote rejected]") {
-			err := &ErrPushRejected{
-				StdOut: outbuf.String(),
-				StdErr: errbuf.String(),
-				Err:    err,
-			}
+		if strings.Contains(stderr, "non-fast-forward") {
+			return &ErrPushOutOfDate{StdOut: stdout, StdErr: stderr, Err: err}
+		} else if strings.Contains(stderr, "! [remote rejected]") {
+			err := &ErrPushRejected{StdOut: stdout, StdErr: stderr, Err: err}
 			err.GenerateMessage()
 			return err
-		} else if strings.Contains(errbuf.String(), "matches more than one") {
-			err := &ErrMoreThanOne{
-				StdOut: outbuf.String(),
-				StdErr: errbuf.String(),
-				Err:    err,
-			}
-			return err
+		} else if strings.Contains(stderr, "matches more than one") {
+			return &ErrMoreThanOne{StdOut: stdout, StdErr: stderr, Err: err}
 		}
+		return fmt.Errorf("push failed: %w - %s\n%s", err, stderr, stdout)
 	}
 
-	if errbuf.Len() > 0 && err != nil {
-		return fmt.Errorf("%w - %s", err, errbuf.String())
-	}
-
-	return err
+	return nil
 }
 
 // GetLatestCommitTime returns time for latest commit in repository (across all branches)