diff --git a/models/repo.go b/models/repo.go index 010c9fad6..e8b8b0af0 100644 --- a/models/repo.go +++ b/models/repo.go @@ -5,10 +5,12 @@ package models import ( + "bufio" "bytes" "errors" "fmt" "html/template" + "io" "io/ioutil" "os" "os/exec" @@ -16,6 +18,7 @@ import ( "path/filepath" "regexp" "sort" + "strconv" "strings" "time" @@ -194,6 +197,7 @@ type Repository struct { IsFork bool `xorm:"NOT NULL DEFAULT false"` ForkID int64 BaseRepo *Repository `xorm:"-"` + RepoSize int64 `xorm:"NOT NULL DEFAULT 0"` Created time.Time `xorm:"-"` CreatedUnix int64 @@ -427,6 +431,19 @@ func (repo *Repository) IsOwnedBy(userID int64) bool { return repo.OwnerID == userID } +func (repo *Repository) GetRepoSize() int64 { + return repo.RepoSize +} + +func (repo *Repository) ComputeRepoSize() (err error) { + repoInfoSize, err := GitRepoSize(repo.RepoPath()) + if err != nil { + return err + } + repo.RepoSize = repoInfoSize.Size + repoInfoSize.SizePack + return nil +} + // CanBeForked returns true if repository meets the requirements of being forked. func (repo *Repository) CanBeForked() bool { return !repo.IsBare @@ -674,6 +691,11 @@ func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) { } } + err = repo.ComputeRepoSize() + if err != nil { + return repo, fmt.Errorf("ComputeRepoSize: %v", err) + } + if opts.IsMirror { if _, err = x.InsertOne(&Mirror{ RepoID: repo.ID, @@ -1267,6 +1289,11 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err) } } + + err = repo.ComputeRepoSize() + if err != nil { + return fmt.Errorf("ComputeRepoSize: %v", err) + } } return nil @@ -1665,6 +1692,122 @@ func GitGcRepos() error { }) } +// Maybe move this to gogits/git-module +type CountObject struct { + Count int + Size int64 + InPack int64 + Packs int + SizePack int64 + PrunePack int + Garbage int + SizeGarbage int +} + +const STAT_COUNT = "count: " +const STAT_SIZE = "size: " +const STAT_INPACK = "in-pack: " +const STAT_PACKS = "packs: " +const STAT_SIZEPACK = "size-pack: " +const STAT_PRUNEPACKAGE = "prune-package: " +const STAT_GARBAGE = "garbage: " +const STAT_SIZEGARBAGE = "size-garbage: " + +func GitRepoSize(repoPath string) (*CountObject, error) { + var cmd *exec.Cmd + cmd = exec.Command("git", "count-objects", "-v") + cmd.Dir = repoPath + cmd.Stderr = os.Stderr + + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("StdoutPipe: %v", err) + } + if err = cmd.Start(); err != nil { + return nil, fmt.Errorf("Start: %v", err) + } + + repoSize, err := ParseSize(stdout) + if err != nil { + return nil, fmt.Errorf("ParsePatch: %v", err) + } + + if err = cmd.Wait(); err != nil { + return nil, fmt.Errorf("Wait: %v", err) + } + + return repoSize, nil +} + +func ParseSize(reader io.Reader) (*CountObject, error) { + repoSize := &CountObject{} + input := bufio.NewReader(reader) + isEOF := false + + for !isEOF { + line, err := input.ReadString('\n') + if err != nil { + if err == io.EOF { + isEOF = true + continue + } else { + return nil, fmt.Errorf("ReadString: %v", err) + } + } + + if len(line) > 0 && line[len(line)-1] == '\n' { + // Remove line break. + line = line[:len(line)-1] + } + log.Info(line) + switch { + case strings.HasPrefix(line, STAT_COUNT): + repoSize.Count, err = strconv.Atoi(line[7:]) + case strings.HasPrefix(line, STAT_SIZE): + repoSize.Size, err = strconv.ParseInt(line[6:], 10, 64) + case strings.HasPrefix(line, STAT_INPACK): + repoSize.InPack, err = strconv.ParseInt(line[9:], 10, 64) + case strings.HasPrefix(line, STAT_PACKS): + repoSize.Packs, err = strconv.Atoi(line[7:]) + case strings.HasPrefix(line, STAT_SIZEPACK): + repoSize.SizePack, err = strconv.ParseInt(line[11:], 10, 64) + case strings.HasPrefix(line, STAT_PRUNEPACKAGE): + repoSize.PrunePack, err = strconv.Atoi(line[16:]) + case strings.HasPrefix(line, STAT_GARBAGE): + repoSize.Garbage, err = strconv.Atoi(line[9:]) + case strings.HasPrefix(line, STAT_SIZEGARBAGE): + repoSize.SizeGarbage, err = strconv.Atoi(line[14:]) + } + if err != nil { + log.Error(4, "Parsing count-objects failed: %v", err) + return nil, err + } + } + return repoSize, nil +} + +func UpdateRepoSize(repoUserName, repoName string) error { + log.Info("Compute Size of %s - %s", repoUserName, repoName) + + repoPath := RepoPath(repoUserName, repoName) + repoInfoSize, err := GitRepoSize(repoPath) + if err != nil { + return err + } + repoSize := repoInfoSize.Size + repoInfoSize.SizePack + log.Info(strconv.FormatInt(repoSize, 10)) + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { + return nil + } + if _, err = sess.Exec("UPDATE `repository` SET repo_size=? WHERE name=?", repoSize, repoName); err != nil { + return fmt.Errorf("Update repo size failed: %v", err) + } + + return sess.Commit() +} + type repoChecker struct { querySQL, correctSQL string desc string @@ -2032,6 +2175,7 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit DefaultBranch: oldRepo.DefaultBranch, IsPrivate: oldRepo.IsPrivate, IsFork: true, + RepoSize: oldRepo.RepoSize, ForkID: oldRepo.ID, } diff --git a/models/update.go b/models/update.go index 79d62c8f6..23328bc88 100644 --- a/models/update.go +++ b/models/update.go @@ -167,5 +167,11 @@ func PushUpdate(opts PushUpdateOptions) (err error) { }); err != nil { return fmt.Errorf("CommitRepoAction (branch): %v", err) } + + // Compute repo size + if err := UpdateRepoSize(opts.RepoUserName, opts.RepoName); err != nil { + return fmt.Errorf("Update repo size failed: %v", err) + } + return nil } diff --git a/modules/template/template.go b/modules/template/template.go index 3a6eb042b..5cf91ed67 100644 --- a/modules/template/template.go +++ b/modules/template/template.go @@ -75,6 +75,9 @@ func NewFuncMap() []template.FuncMap { "DateFmtShort": func(t time.Time) string { return t.Format("Jan 02, 2006") }, + "SizeFmt": func(s int64) string { + return base.FileSize(s) + }, "List": List, "SubStr": func(str string, start, length int) string { if len(str) == 0 { diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index 4b1d98b94..aa60260bb 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -22,6 +22,7 @@