mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Able to set timeout for process monitor
This commit is contained in:
		
							
								
								
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							@@ -17,7 +17,7 @@ import (
 | 
				
			|||||||
	"github.com/gogits/gogs/modules/setting"
 | 
						"github.com/gogits/gogs/modules/setting"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const APP_VER = "0.4.5.0704 Alpha"
 | 
					const APP_VER = "0.4.5.0706 Alpha"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
	runtime.GOMAXPROCS(runtime.NumCPU())
 | 
						runtime.GOMAXPROCS(runtime.NumCPU())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@ import (
 | 
				
			|||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"os/exec"
 | 
						"os/exec"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gogits/git"
 | 
						"github.com/gogits/git"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -170,10 +171,6 @@ func ParsePatch(pid int64, cmd *exec.Cmd, reader io.Reader) (*Diff, error) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// In case process became zombie.
 | 
					 | 
				
			||||||
	if err := process.Kill(pid); err != nil {
 | 
					 | 
				
			||||||
		log.Error("git_diff.ParsePatch(Kill): %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return diff, nil
 | 
						return diff, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -201,10 +198,30 @@ func GetDiff(repoPath, commitid string) (*Diff, error) {
 | 
				
			|||||||
	cmd.Stdout = wr
 | 
						cmd.Stdout = wr
 | 
				
			||||||
	cmd.Stdin = os.Stdin
 | 
						cmd.Stdin = os.Stdin
 | 
				
			||||||
	cmd.Stderr = os.Stderr
 | 
						cmd.Stderr = os.Stderr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						done := make(chan error)
 | 
				
			||||||
	go func() {
 | 
						go func() {
 | 
				
			||||||
		cmd.Run()
 | 
							cmd.Start()
 | 
				
			||||||
 | 
							done <- cmd.Wait()
 | 
				
			||||||
		wr.Close()
 | 
							wr.Close()
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
	defer rd.Close()
 | 
						defer rd.Close()
 | 
				
			||||||
	return ParsePatch(process.Add(fmt.Sprintf("GetDiff(%s)", repoPath), cmd), cmd, rd)
 | 
					
 | 
				
			||||||
 | 
						desc := fmt.Sprintf("GetDiff(%s)", repoPath)
 | 
				
			||||||
 | 
						pid := process.Add(desc, cmd)
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							// In case process became zombie.
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-time.After(5 * time.Minute):
 | 
				
			||||||
 | 
								if errKill := process.Kill(pid); errKill != nil {
 | 
				
			||||||
 | 
									log.Error("git_diff.ParsePatch(Kill): %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								<-done
 | 
				
			||||||
 | 
								// return "", ErrExecTimeout.Error(), ErrExecTimeout
 | 
				
			||||||
 | 
							case err = <-done:
 | 
				
			||||||
 | 
								process.Remove(pid)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ParsePatch(pid, cmd, rd)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -89,7 +89,7 @@ func NewRepoContext() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Check if server has basic git setting.
 | 
						// Check if server has basic git setting.
 | 
				
			||||||
	stdout, stderr, err := process.Exec("NewRepoContext(get setting)", "git", "config", "--get", "user.name")
 | 
						stdout, stderr, err := process.Exec("NewRepoContext(get setting)", "git", "config", "--get", "user.name")
 | 
				
			||||||
	if strings.Contains(stderr, "fatal:") {
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatal("repo.NewRepoContext(fail to get git user.name): %s", stderr)
 | 
							log.Fatal("repo.NewRepoContext(fail to get git user.name): %s", stderr)
 | 
				
			||||||
	} else if err != nil || len(strings.TrimSpace(stdout)) == 0 {
 | 
						} else if err != nil || len(strings.TrimSpace(stdout)) == 0 {
 | 
				
			||||||
		if _, stderr, err = process.Exec("NewRepoContext(set email)", "git", "config", "--global", "user.email", "gogitservice@gmail.com"); err != nil {
 | 
							if _, stderr, err = process.Exec("NewRepoContext(set email)", "git", "config", "--global", "user.email", "gogitservice@gmail.com"); err != nil {
 | 
				
			||||||
@@ -190,8 +190,8 @@ type Mirror struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// MirrorRepository creates a mirror repository from source.
 | 
					// MirrorRepository creates a mirror repository from source.
 | 
				
			||||||
func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error {
 | 
					func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error {
 | 
				
			||||||
	// TODO: need timeout.
 | 
						_, stderr, err := process.ExecTimeout(10*time.Minute,
 | 
				
			||||||
	_, stderr, err := process.Exec(fmt.Sprintf("MirrorRepository: %s/%s", userName, repoName),
 | 
							fmt.Sprintf("MirrorRepository: %s/%s", userName, repoName),
 | 
				
			||||||
		"git", "clone", "--mirror", url, repoPath)
 | 
							"git", "clone", "--mirror", url, repoPath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return errors.New("git clone --mirror: " + stderr)
 | 
							return errors.New("git clone --mirror: " + stderr)
 | 
				
			||||||
@@ -233,9 +233,8 @@ func MirrorUpdate() {
 | 
				
			|||||||
			return nil
 | 
								return nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO: need timeout.
 | 
					 | 
				
			||||||
		repoPath := filepath.Join(setting.RepoRootPath, m.RepoName+".git")
 | 
							repoPath := filepath.Join(setting.RepoRootPath, m.RepoName+".git")
 | 
				
			||||||
		if _, stderr, err := process.ExecDir(
 | 
							if _, stderr, err := process.ExecDir(10*time.Minute,
 | 
				
			||||||
			repoPath, fmt.Sprintf("MirrorUpdate: %s", repoPath),
 | 
								repoPath, fmt.Sprintf("MirrorUpdate: %s", repoPath),
 | 
				
			||||||
			"git", "remote", "update"); err != nil {
 | 
								"git", "remote", "update"); err != nil {
 | 
				
			||||||
			return errors.New("git remote update: " + stderr)
 | 
								return errors.New("git remote update: " + stderr)
 | 
				
			||||||
@@ -272,26 +271,23 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
 | 
				
			|||||||
		return repo, UpdateRepository(repo)
 | 
							return repo, UpdateRepository(repo)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: need timeout.
 | 
					 | 
				
			||||||
	// Clone from local repository.
 | 
						// Clone from local repository.
 | 
				
			||||||
	_, stderr, err := process.Exec(
 | 
						_, stderr, err := process.ExecTimeout(10*time.Minute,
 | 
				
			||||||
		fmt.Sprintf("MigrateRepository(git clone): %s", repoPath),
 | 
							fmt.Sprintf("MigrateRepository(git clone): %s", repoPath),
 | 
				
			||||||
		"git", "clone", repoPath, tmpDir)
 | 
							"git", "clone", repoPath, tmpDir)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return repo, errors.New("git clone: " + stderr)
 | 
							return repo, errors.New("git clone: " + stderr)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: need timeout.
 | 
					 | 
				
			||||||
	// Pull data from source.
 | 
						// Pull data from source.
 | 
				
			||||||
	if _, stderr, err = process.ExecDir(
 | 
						if _, stderr, err = process.ExecDir(3*time.Minute,
 | 
				
			||||||
		tmpDir, fmt.Sprintf("MigrateRepository(git pull): %s", repoPath),
 | 
							tmpDir, fmt.Sprintf("MigrateRepository(git pull): %s", repoPath),
 | 
				
			||||||
		"git", "pull", url); err != nil {
 | 
							"git", "pull", url); err != nil {
 | 
				
			||||||
		return repo, errors.New("git pull: " + stderr)
 | 
							return repo, errors.New("git pull: " + stderr)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: need timeout.
 | 
					 | 
				
			||||||
	// Push data to local repository.
 | 
						// Push data to local repository.
 | 
				
			||||||
	if _, stderr, err = process.ExecDir(
 | 
						if _, stderr, err = process.ExecDir(3*time.Minute,
 | 
				
			||||||
		tmpDir, fmt.Sprintf("MigrateRepository(git push): %s", repoPath),
 | 
							tmpDir, fmt.Sprintf("MigrateRepository(git push): %s", repoPath),
 | 
				
			||||||
		"git", "push", "origin", "master"); err != nil {
 | 
							"git", "push", "origin", "master"); err != nil {
 | 
				
			||||||
		return repo, errors.New("git push: " + stderr)
 | 
							return repo, errors.New("git push: " + stderr)
 | 
				
			||||||
@@ -314,20 +310,20 @@ func extractGitBareZip(repoPath string) error {
 | 
				
			|||||||
// initRepoCommit temporarily changes with work directory.
 | 
					// initRepoCommit temporarily changes with work directory.
 | 
				
			||||||
func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
 | 
					func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
 | 
				
			||||||
	var stderr string
 | 
						var stderr string
 | 
				
			||||||
	if _, stderr, err = process.ExecDir(
 | 
						if _, stderr, err = process.ExecDir(-1,
 | 
				
			||||||
		tmpPath, fmt.Sprintf("initRepoCommit(git add): %s", tmpPath),
 | 
							tmpPath, fmt.Sprintf("initRepoCommit(git add): %s", tmpPath),
 | 
				
			||||||
		"git", "add", "--all"); err != nil {
 | 
							"git", "add", "--all"); err != nil {
 | 
				
			||||||
		return errors.New("git add: " + stderr)
 | 
							return errors.New("git add: " + stderr)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, stderr, err = process.ExecDir(
 | 
						if _, stderr, err = process.ExecDir(-1,
 | 
				
			||||||
		tmpPath, fmt.Sprintf("initRepoCommit(git commit): %s", tmpPath),
 | 
							tmpPath, fmt.Sprintf("initRepoCommit(git commit): %s", tmpPath),
 | 
				
			||||||
		"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
 | 
							"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
 | 
				
			||||||
		"-m", "Init commit"); err != nil {
 | 
							"-m", "Init commit"); err != nil {
 | 
				
			||||||
		return errors.New("git commit: " + stderr)
 | 
							return errors.New("git commit: " + stderr)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, stderr, err = process.ExecDir(
 | 
						if _, stderr, err = process.ExecDir(-1,
 | 
				
			||||||
		tmpPath, fmt.Sprintf("initRepoCommit(git push): %s", tmpPath),
 | 
							tmpPath, fmt.Sprintf("initRepoCommit(git push): %s", tmpPath),
 | 
				
			||||||
		"git", "push", "origin", "master"); err != nil {
 | 
							"git", "push", "origin", "master"); err != nil {
 | 
				
			||||||
		return errors.New("git push: " + stderr)
 | 
							return errors.New("git push: " + stderr)
 | 
				
			||||||
@@ -583,7 +579,7 @@ func CreateRepository(u *User, name, desc, lang, license string, private, mirror
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, stderr, err := process.ExecDir(
 | 
						_, stderr, err := process.ExecDir(-1,
 | 
				
			||||||
		repoPath, fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath),
 | 
							repoPath, fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath),
 | 
				
			||||||
		"git", "update-server-info")
 | 
							"git", "update-server-info")
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -82,7 +82,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
 | 
				
			|||||||
		ctx.Repo.Owner = user
 | 
							ctx.Repo.Owner = user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Organization owner team members are true owners as well.
 | 
							// Organization owner team members are true owners as well.
 | 
				
			||||||
		if ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgOwner(ctx.User.Id) {
 | 
							if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgOwner(ctx.User.Id) {
 | 
				
			||||||
			ctx.Repo.IsTrueOwner = true
 | 
								ctx.Repo.IsTrueOwner = true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ package process
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"os/exec"
 | 
						"os/exec"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@@ -13,6 +14,16 @@ import (
 | 
				
			|||||||
	"github.com/gogits/gogs/modules/log"
 | 
						"github.com/gogits/gogs/modules/log"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						ErrExecTimeout = errors.New("Process execution timeout")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Common timeout.
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						// NOTE: could be custom in config file for default.
 | 
				
			||||||
 | 
						DEFAULT = 60 * time.Second
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Process represents a working process inherit from Gogs.
 | 
					// Process represents a working process inherit from Gogs.
 | 
				
			||||||
type Process struct {
 | 
					type Process struct {
 | 
				
			||||||
	Pid         int64 // Process ID, not system one.
 | 
						Pid         int64 // Process ID, not system one.
 | 
				
			||||||
@@ -40,7 +51,12 @@ func Add(desc string, cmd *exec.Cmd) int64 {
 | 
				
			|||||||
	return pid
 | 
						return pid
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ExecDir(dir, desc, cmdName string, args ...string) (string, string, error) {
 | 
					// Exec starts executing a command in given path, it records its process and timeout.
 | 
				
			||||||
 | 
					func ExecDir(timeout time.Duration, dir, desc, cmdName string, args ...string) (string, string, error) {
 | 
				
			||||||
 | 
						if timeout == -1 {
 | 
				
			||||||
 | 
							timeout = DEFAULT
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bufOut := new(bytes.Buffer)
 | 
						bufOut := new(bytes.Buffer)
 | 
				
			||||||
	bufErr := new(bytes.Buffer)
 | 
						bufErr := new(bytes.Buffer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,18 +64,39 @@ func ExecDir(dir, desc, cmdName string, args ...string) (string, string, error)
 | 
				
			|||||||
	cmd.Dir = dir
 | 
						cmd.Dir = dir
 | 
				
			||||||
	cmd.Stdout = bufOut
 | 
						cmd.Stdout = bufOut
 | 
				
			||||||
	cmd.Stderr = bufErr
 | 
						cmd.Stderr = bufErr
 | 
				
			||||||
 | 
						if err := cmd.Start(); err != nil {
 | 
				
			||||||
 | 
							return "", err.Error(), err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pid := Add(desc, cmd)
 | 
						pid := Add(desc, cmd)
 | 
				
			||||||
	err := cmd.Run()
 | 
						done := make(chan error)
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							done <- cmd.Wait()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-time.After(timeout):
 | 
				
			||||||
		if errKill := Kill(pid); errKill != nil {
 | 
							if errKill := Kill(pid); errKill != nil {
 | 
				
			||||||
		log.Error("Exec: %v", pid, desc, errKill)
 | 
								log.Error("Exec(%d:%s): %v", pid, desc, errKill)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							<-done
 | 
				
			||||||
 | 
							return "", ErrExecTimeout.Error(), ErrExecTimeout
 | 
				
			||||||
 | 
						case err = <-done:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Remove(pid)
 | 
				
			||||||
	return bufOut.String(), bufErr.String(), err
 | 
						return bufOut.String(), bufErr.String(), err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Exec starts executing a command and record its process.
 | 
					// Exec starts executing a command, it records its process and timeout.
 | 
				
			||||||
 | 
					func ExecTimeout(timeout time.Duration, desc, cmdName string, args ...string) (string, string, error) {
 | 
				
			||||||
 | 
						return ExecDir(timeout, "", desc, cmdName, args...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Exec starts executing a command, it records its process and has default timeout.
 | 
				
			||||||
func Exec(desc, cmdName string, args ...string) (string, string, error) {
 | 
					func Exec(desc, cmdName string, args ...string) (string, string, error) {
 | 
				
			||||||
	return ExecDir("", desc, cmdName, args...)
 | 
						return ExecDir(-1, "", desc, cmdName, args...)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Remove removes a process from list.
 | 
					// Remove removes a process from list.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +1 @@
 | 
				
			|||||||
0.4.5.0704 Alpha
 | 
					0.4.5.0706 Alpha
 | 
				
			||||||
@@ -6,7 +6,7 @@
 | 
				
			|||||||
            <a class="nav-item pull-left{{if .PageIsHelp}} active{{end}}" target="_blank" href="http://gogs.io/docs">Help</a>
 | 
					            <a class="nav-item pull-left{{if .PageIsHelp}} active{{end}}" target="_blank" href="http://gogs.io/docs">Help</a>
 | 
				
			||||||
            {{if .IsSigned}}
 | 
					            {{if .IsSigned}}
 | 
				
			||||||
            {{if .HasAccess}}
 | 
					            {{if .HasAccess}}
 | 
				
			||||||
            <form class="nav-item pull-left{{if .PageIsNewRepo}} active{{end}}" id="nav-search-form">
 | 
					            <!-- <form class="nav-item pull-left{{if .PageIsNewRepo}} active{{end}}" id="nav-search-form">
 | 
				
			||||||
                <div class="input-group">
 | 
					                <div class="input-group">
 | 
				
			||||||
                    <div class="input-group-btn">
 | 
					                    <div class="input-group-btn">
 | 
				
			||||||
                        <button type="button" class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown">{{if .Repository}}This Repository{{else}}All Repositories{{end}} <span class="caret"></span></button>
 | 
					                        <button type="button" class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown">{{if .Repository}}This Repository{{else}}All Repositories{{end}} <span class="caret"></span></button>
 | 
				
			||||||
@@ -20,7 +20,7 @@
 | 
				
			|||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <input type="search" class="form-control input-sm" name="q" placeholder="search code, commits and issues"/>
 | 
					                    <input type="search" class="form-control input-sm" name="q" placeholder="search code, commits and issues"/>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </form>
 | 
					            </form> -->
 | 
				
			||||||
            {{end}}
 | 
					            {{end}}
 | 
				
			||||||
            <a id="nav-out" class="nav-item navbar-right navbar-btn btn btn-danger" href="/user/logout/"><i class="fa fa-power-off fa-lg"></i></a>
 | 
					            <a id="nav-out" class="nav-item navbar-right navbar-btn btn btn-danger" href="/user/logout/"><i class="fa fa-power-off fa-lg"></i></a>
 | 
				
			||||||
            <a id="nav-avatar" class="nav-item navbar-right{{if .PageIsUserProfile}} active{{end}}" href="{{.SignedUser.HomeLink}}" data-toggle="tooltip" data-placement="bottom" title="{{.SignedUserName}}">
 | 
					            <a id="nav-avatar" class="nav-item navbar-right{{if .PageIsUserProfile}} active{{end}}" href="{{.SignedUser.HomeLink}}" data-toggle="tooltip" data-placement="bottom" title="{{.SignedUserName}}">
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user