func (r *Repository) Commits(opt vcs.CommitsOptions) ([]*vcs.Commit, uint, error) { r.editLock.RLock() defer r.editLock.RUnlock() walk, err := r.u.Walk() if err != nil { return nil, 0, err } defer walk.Free() walk.Sorting(git2go.SortTime) oid, err := git2go.NewOid(string(opt.Head)) if err != nil { return nil, 0, err } if err := walk.Push(oid); err != nil { if git2go.IsErrorCode(err, git2go.ErrNotFound) { return nil, 0, vcs.ErrCommitNotFound } return nil, 0, err } if opt.Base != "" { baseOID, err := git2go.NewOid(string(opt.Base)) if err != nil { return nil, 0, err } if err := walk.Hide(baseOID); err != nil { if git2go.IsErrorCode(err, git2go.ErrNotFound) { return nil, 0, vcs.ErrCommitNotFound } return nil, 0, err } } var commits []*vcs.Commit total := uint(0) err = walk.Iterate(func(c *git2go.Commit) bool { if total >= opt.Skip && (opt.N == 0 || uint(len(commits)) < opt.N) { commits = append(commits, r.makeCommit(c)) } total++ // If we want total, keep going until the end. if !opt.NoTotal { return true } // Otherwise return once N has been satisfied. return (opt.N == 0 || uint(len(commits)) < opt.N) }) if err != nil { return nil, 0, err } if opt.NoTotal { total = 0 } return commits, total, nil }
func (r *Repository) Commits(opt vcs.CommitsOptions) ([]*vcs.Commit, uint, error) { r.editLock.RLock() defer r.editLock.RUnlock() walk, err := r.u.Walk() if err != nil { return nil, 0, err } defer walk.Free() walk.Sorting(git2go.SortTopological) oid, err := git2go.NewOid(string(opt.Head)) if err != nil { return nil, 0, err } if err := walk.Push(oid); err != nil { if git2go.IsErrorCode(err, git2go.ErrNotFound) { return nil, 0, vcs.ErrCommitNotFound } return nil, 0, err } if opt.Base != "" { baseOID, err := git2go.NewOid(string(opt.Base)) if err != nil { return nil, 0, err } if err := walk.Hide(baseOID); err != nil { if git2go.IsErrorCode(err, git2go.ErrNotFound) { return nil, 0, vcs.ErrCommitNotFound } return nil, 0, err } } var commits []*vcs.Commit total := uint(0) err = walk.Iterate(func(c *git2go.Commit) bool { if total >= opt.Skip && (opt.N == 0 || uint(len(commits)) < opt.N) { commits = append(commits, r.makeCommit(c)) } total++ return total < uint(MaxCommits) }) if err != nil { return nil, 0, err } return commits, total, nil }
// mergeBaseHoldingEditLock performs a merge-base. Callers must hold // the r.editLock (either as a reader or writer). func (r *Repository) mergeBaseHoldingEditLock(a, b vcs.CommitID) (vcs.CommitID, error) { ao, err := git2go.NewOid(string(a)) if err != nil { return "", err } bo, err := git2go.NewOid(string(b)) if err != nil { return "", err } mb, err := r.u.MergeBase(ao, bo) if err != nil { return "", err } return vcs.CommitID(mb.String()), nil }
// getCommit finds and returns the raw git2go Commit. The caller is // responsible for freeing it (c.Free()). func (r *Repository) getCommit(id vcs.CommitID) (*git2go.Commit, error) { oid, err := git2go.NewOid(string(id)) if err != nil { return nil, err } c, err := r.u.LookupCommit(oid) if err != nil { if git2go.IsErrorCode(err, git2go.ErrNotFound) { return nil, vcs.ErrCommitNotFound } return nil, err } return c, nil }
func (r *Repository) BlameFile(path string, opt *vcs.BlameOptions) ([]*vcs.Hunk, error) { r.editLock.RLock() defer r.editLock.RUnlock() gopt := git2go.BlameOptions{} if opt != nil { var err error if opt.NewestCommit != "" { gopt.NewestCommit, err = git2go.NewOid(string(opt.NewestCommit)) if err != nil { return nil, err } } if opt.OldestCommit != "" { gopt.OldestCommit, err = git2go.NewOid(string(opt.OldestCommit)) if err != nil { return nil, err } } gopt.MinLine = uint32(opt.StartLine) gopt.MaxLine = uint32(opt.EndLine) } blame, err := r.u.BlameFile(path, &gopt) if err != nil { return nil, err } defer blame.Free() // Read file contents so we can set hunk byte start and end. fs, err := r.FileSystem(vcs.CommitID(gopt.NewestCommit.String())) if err != nil { return nil, err } b, err := fs.(*gitFSLibGit2).readFileBytes(path) if err != nil { return nil, err } lines := bytes.SplitAfter(b, []byte{'\n'}) byteOffset := 0 hunks := make([]*vcs.Hunk, blame.HunkCount()) for i := 0; i < len(hunks); i++ { hunk, err := blame.HunkByIndex(i) if err != nil { return nil, err } hunkBytes := 0 for j := uint16(0); j < hunk.LinesInHunk; j++ { hunkBytes += len(lines[j]) } endByteOffset := byteOffset + hunkBytes hunks[i] = &vcs.Hunk{ StartLine: int(hunk.FinalStartLineNumber), EndLine: int(hunk.FinalStartLineNumber + hunk.LinesInHunk), StartByte: byteOffset, EndByte: endByteOffset, CommitID: vcs.CommitID(hunk.FinalCommitId.String()), Author: vcs.Signature{ Name: hunk.FinalSignature.Name, Email: hunk.FinalSignature.Email, Date: pbtypes.NewTimestamp(hunk.FinalSignature.When.In(time.UTC)), }, } byteOffset = endByteOffset lines = lines[hunk.LinesInHunk:] } return hunks, nil }
func (input *GraderInput) createArchiveFromGit(archivePath string) (int64, error) { repository, err := git.OpenRepository(input.repositoryPath) if err != nil { return 0, err } defer repository.Free() treeOid, err := git.NewOid(input.Hash()) if err != nil { return 0, err } tree, err := repository.LookupTree(treeOid) if err != nil { return 0, err } defer tree.Free() odb, err := repository.Odb() if err != nil { return 0, err } defer odb.Free() tmpFd, err := os.Create(archivePath) if err != nil { return 0, err } defer tmpFd.Close() gz := gzip.NewWriter(tmpFd) defer gz.Close() archive := tar.NewWriter(gz) defer archive.Close() var walkErr error = nil foundSettings := false var uncompressedSize int64 = 0 tree.Walk(func(parent string, entry *git.TreeEntry) int { entryPath := path.Join(parent, entry.Name) if entryPath == "settings.json" { foundSettings = true } switch entry.Type { case git.ObjectTree: hdr := &tar.Header{ Name: entryPath, Typeflag: tar.TypeDir, Mode: 0755, Size: 0, } if walkErr = archive.WriteHeader(hdr); walkErr != nil { return -1 } case git.ObjectBlob: blob, walkErr := repository.LookupBlob(entry.Id) if walkErr != nil { return -1 } defer blob.Free() hdr := &tar.Header{ Name: entryPath, Typeflag: tar.TypeReg, Mode: 0644, Size: blob.Size(), } uncompressedSize += blob.Size() if walkErr = archive.WriteHeader(hdr); walkErr != nil { return -1 } stream, err := odb.NewReadStream(entry.Id) if err == nil { defer stream.Free() if _, walkErr := io.Copy(archive, stream); walkErr != nil { return -1 } } else { // That particular object cannot be streamed. Allocate the blob in // memory and write it to the archive. if _, walkErr := archive.Write(blob.Contents()); walkErr != nil { return -1 } } } return 0 }) if walkErr != nil { return 0, walkErr } if !foundSettings { return 0, fmt.Errorf( "Could not find `settings.json` in %s:%s", input.repositoryPath, input.Hash(), ) } return uncompressedSize, nil }
func (input *v1CompatGraderInput) createArchiveFromGit( archivePath string, ) (int64, error) { err := input.db.QueryRow( `SELECT extra_wall_time, memory_limit, output_limit, overall_wall_time_limit, time_limit, validator_time_limit, slow, validator FROM Problems WHERE alias = ?;`, input.problemName).Scan( &input.Settings().Limits.ExtraWallTime, &input.Settings().Limits.MemoryLimit, &input.Settings().Limits.OutputLimit, &input.Settings().Limits.OverallWallTimeLimit, &input.Settings().Limits.TimeLimit, &input.Settings().Limits.ValidatorTimeLimit, &input.Settings().Slow, &input.Settings().Validator.Name, ) if err != nil { return 0, err } input.Settings().Limits.MemoryLimit *= 1024 if input.Settings().Validator.Name == "token-numeric" { tolerance := 1e-6 input.Settings().Validator.Tolerance = &tolerance } repository, err := git.OpenRepository(input.repositoryPath) if err != nil { return 0, err } defer repository.Free() treeOid, err := git.NewOid(input.Hash()) if err != nil { return 0, err } tree, err := repository.LookupTree(treeOid) if err != nil { return 0, err } defer tree.Free() odb, err := repository.Odb() if err != nil { return 0, err } defer odb.Free() tmpFd, err := os.Create(archivePath) if err != nil { return 0, err } defer tmpFd.Close() gz := gzip.NewWriter(tmpFd) defer gz.Close() archive := tar.NewWriter(gz) defer archive.Close() // TODO(lhchavez): Support libinteractive. var walkErr error = nil var uncompressedSize int64 = 0 rawCaseWeights := make(map[string]float64) tree.Walk(func(parent string, entry *git.TreeEntry) int { untrimmedPath := path.Join(parent, entry.Name) if untrimmedPath == "testplan" && entry.Type == git.ObjectBlob { blob, walkErr := repository.LookupBlob(entry.Id) if walkErr != nil { return -1 } defer blob.Free() testplanRe := regexp.MustCompile(`^\s*([^# \t]+)\s+([0-9.]+).*$`) for _, line := range strings.Split(string(blob.Contents()), "\n") { m := testplanRe.FindStringSubmatch(line) if m == nil { continue } rawCaseWeights[m[1]], walkErr = strconv.ParseFloat(m[2], 64) if walkErr != nil { return -1 } } } if strings.HasPrefix(untrimmedPath, "validator.") && input.Settings().Validator.Name == "custom" && entry.Type == git.ObjectBlob { lang := filepath.Ext(untrimmedPath) input.Settings().Validator.Lang = &lang blob, walkErr := repository.LookupBlob(entry.Id) if walkErr != nil { return -1 } defer blob.Free() hdr := &tar.Header{ Name: untrimmedPath, Typeflag: tar.TypeReg, Mode: 0644, Size: blob.Size(), } uncompressedSize += blob.Size() if walkErr = archive.WriteHeader(hdr); walkErr != nil { return -1 } if _, walkErr := archive.Write(blob.Contents()); walkErr != nil { return -1 } } if !strings.HasPrefix(untrimmedPath, "cases/") { return 0 } entryPath := strings.TrimPrefix(untrimmedPath, "cases/") if strings.HasPrefix(entryPath, "in/") { caseName := strings.TrimSuffix(strings.TrimPrefix(entryPath, "in/"), ".in") if _, ok := rawCaseWeights[caseName]; !ok { rawCaseWeights[caseName] = 1.0 } } switch entry.Type { case git.ObjectTree: hdr := &tar.Header{ Name: entryPath, Typeflag: tar.TypeDir, Mode: 0755, Size: 0, } if walkErr = archive.WriteHeader(hdr); walkErr != nil { return -1 } case git.ObjectBlob: blob, walkErr := repository.LookupBlob(entry.Id) if walkErr != nil { return -1 } defer blob.Free() hdr := &tar.Header{ Name: entryPath, Typeflag: tar.TypeReg, Mode: 0644, Size: blob.Size(), } uncompressedSize += blob.Size() if walkErr = archive.WriteHeader(hdr); walkErr != nil { return -1 } stream, err := odb.NewReadStream(entry.Id) if err == nil { defer stream.Free() if _, walkErr := io.Copy(archive, stream); walkErr != nil { return -1 } } else { // That particular object cannot be streamed. Allocate the blob in // memory and write it to the archive. if _, walkErr := archive.Write(blob.Contents()); walkErr != nil { return -1 } } } return 0 }) if walkErr != nil { return 0, walkErr } // Generate the group/case settings. cases := make(map[string][]common.CaseSettings) groupWeights := make(map[string]float64) totalWeight := 0.0 for _, weight := range rawCaseWeights { totalWeight += weight } for caseName, weight := range rawCaseWeights { components := strings.SplitN(caseName, ".", 2) groupName := components[0] if _, ok := groupWeights[groupName]; !ok { groupWeights[groupName] = 0 } groupWeights[groupName] += weight / totalWeight if _, ok := cases[groupName]; !ok { cases[groupName] = make([]common.CaseSettings, 0) } cases[groupName] = append(cases[groupName], common.CaseSettings{ Name: caseName, Weight: weight / totalWeight, }) } input.Settings().Cases = make([]common.GroupSettings, 0) for groupName, cases := range cases { sort.Sort(common.ByCaseName(cases)) input.Settings().Cases = append(input.Settings().Cases, common.GroupSettings{ Cases: cases, Name: groupName, Weight: groupWeights[groupName], }) } sort.Sort(common.ByGroupName(input.Settings().Cases)) // Finally, write settings.json. settingsBlob, err := json.MarshalIndent(input.Settings(), "", " ") if err != nil { return 0, err } hdr := &tar.Header{ Name: "settings.json", Typeflag: tar.TypeReg, Mode: 0644, Size: int64(len(settingsBlob)), } uncompressedSize += int64(len(settingsBlob)) if err = archive.WriteHeader(hdr); err != nil { return 0, err } if _, err = archive.Write(settingsBlob); err != nil { return 0, err } return uncompressedSize, nil }
// Run runs each step of the pipeline in sequence, each time passing // the output of step N as input to step N+1. // If an error is encountered, the pipeline is aborted. func (t *Pipeline) Run() (*git.Tree, error) { var in *git.Tree // Call the previous operation before our own // (unless the current operation is Empty or Base, since they would // discard the result anyway) if t.prev != nil && t.op != OpEmpty && t.op != OpBase { prevOut, err := t.prev.Run() if err != nil { return nil, err } in = prevOut } switch t.op { case OpEmpty: { empty, err := emptyTree(t.repo) if err != nil { return nil, err } return lookupTree(t.repo, empty) } case OpBase: { base, ok := t.arg.(*git.Tree) if !ok { return nil, fmt.Errorf("base: invalid argument: %v", t.arg) } return base, nil } case OpAdd: { arg, ok := t.arg.(*addArg) if !ok { return nil, fmt.Errorf("add: invalid argument: %v", t.arg) } var id *git.Oid switch val := arg.val.(type) { case *Pipeline: { if out, err := val.Run(); err != nil { return nil, fmt.Errorf("add: run source: %v", err) } else { id = out.Id() } } case git.Object: { id = val.Id() } case *git.Oid: { id = val } case *DB: { tree, err := val.Tree() // FIXME: distinguish "no such entry" errors (which we can ignore) // from other errors (which we should forward) if err == nil && tree != nil { id = tree.Id() } } default: { return nil, fmt.Errorf("invalid value: %#v", val) } } return treeAdd(t.repo, in, arg.key, id, arg.merge) } case OpDelete: { key, ok := t.arg.(string) if !ok { return nil, fmt.Errorf("delete: invalid argument: %v", t.arg) } return treeDel(t.repo, in, key) } case OpMkdir: { key, ok := t.arg.(string) if !ok { return nil, fmt.Errorf("mkdir: invalid argument: %v", t.arg) } empty, err := emptyTree(t.repo) if err != nil { return nil, err } return treeAdd(t.repo, in, key, empty, true) } case OpSet: { kv, ok := t.arg.([]string) if !ok { return nil, fmt.Errorf("invalid argument") } if len(kv) != 2 { return nil, fmt.Errorf("invalid argument") } // FIXME: libgit2 crashes if value is empty. // Work around this by shelling out to git. var ( id *git.Oid err error ) if kv[1] == "" { out, err := exec.Command("git", "--git-dir", t.repo.Path(), "hash-object", "-w", "--stdin").Output() if err != nil { return nil, fmt.Errorf("git hash-object: %v", err) } id, err = git.NewOid(strings.Trim(string(out), " \t\r\n")) if err != nil { return nil, fmt.Errorf("git newoid %v", err) } } else { id, err = t.repo.CreateBlobFromBuffer([]byte(kv[1])) if err != nil { return nil, err } } return treeAdd(t.repo, in, kv[0], id, true) } case OpScope: { key, ok := t.arg.(string) if !ok { return nil, fmt.Errorf("invalid argument") } return TreeScope(t.repo, in, key) } } return nil, fmt.Errorf("invalid op: %v", t.op) }