func (r *Repository) makeCommit(rec *hg_revlog.Rec) (*vcs.Commit, error) { fb := hg_revlog.NewFileBuilder() ce, err := hg_changelog.BuildEntry(rec, fb) if err != nil { return nil, err } addr, err := mail.ParseAddress(ce.Committer) if err != nil { // This occurs when the commit author specifier is // malformed. Fall back to just using the whole committer // string as the name. addr = &mail.Address{ Name: ce.Committer, Address: "", } } var parents []vcs.CommitID if !rec.IsStartOfBranch() { if p := rec.Parent(); p != nil { parents = append(parents, vcs.CommitID(hex.EncodeToString(rec.Parent().Id()))) } if rec.Parent2Present() { parents = append(parents, vcs.CommitID(hex.EncodeToString(rec.Parent2().Id()))) } } return &vcs.Commit{ ID: vcs.CommitID(ce.Id), Author: vcs.Signature{addr.Name, addr.Address, pbtypes.NewTimestamp(ce.Date)}, Message: ce.Comment, Parents: parents, }, nil }
func (r *Repository) getParents(revSpec vcs.CommitID) ([]vcs.CommitID, error) { var parents []vcs.CommitID cmd := exec.Command("hg", "parents", "-r", string(revSpec), "--template", `{node}\x00{author|person}\x00{author|email}\x00{date|rfc3339date}\x00{desc}\x00{p1node}\x00{p2node}\x00`) cmd.Dir = r.Dir out, err := cmd.CombinedOutput() if err != nil { return nil, fmt.Errorf("exec `hg parents` failed: %s. Output was:\n\n%s", err, out) } const partsPerCommit = 7 // number of \x00-separated fields per commit allParts := bytes.Split(out, []byte{'\x00'}) numCommits := len(allParts) / partsPerCommit for i := 0; i < numCommits; i++ { parts := allParts[partsPerCommit*i : partsPerCommit*(i+1)] if p1 := parts[0]; len(p1) > 0 && !bytes.Equal(p1, hgNullParentNodeID) { parents = append(parents, vcs.CommitID(p1)) } if p2 := parts[5]; len(p2) > 0 && !bytes.Equal(p2, hgNullParentNodeID) { parents = append(parents, vcs.CommitID(p2)) } if p3 := parts[6]; len(p3) > 0 && !bytes.Equal(p3, hgNullParentNodeID) { parents = append(parents, vcs.CommitID(p3)) } } return parents, nil }
func (r *Repository) Branches(_ vcs.BranchesOptions) ([]*vcs.Branch, error) { r.editLock.RLock() defer r.editLock.RUnlock() refs, err := r.u.NewReferenceIterator() if err != nil { return nil, err } var bs []*vcs.Branch for { ref, err := refs.Next() if isErrIterOver(err) { break } if err != nil { return nil, err } if ref.IsBranch() { bs = append(bs, &vcs.Branch{Name: ref.Shorthand(), Head: vcs.CommitID(ref.Target().String())}) } } sort.Sort(vcs.Branches(bs)) return bs, nil }
func (r *Repository) Tags() ([]*vcs.Tag, error) { r.editLock.RLock() defer r.editLock.RUnlock() refs, err := r.u.NewReferenceIterator() if err != nil { return nil, err } var ts []*vcs.Tag for { ref, err := refs.Next() if isErrIterOver(err) { break } if err != nil { return nil, err } if ref.IsTag() { ts = append(ts, &vcs.Tag{Name: ref.Shorthand(), CommitID: vcs.CommitID(ref.Target().String())}) } } sort.Sort(vcs.Tags(ts)) return ts, nil }
func (fs *gitFSLibGit2) makeFileInfo(path string, e *git2go.TreeEntry) (*util.FileInfo, error) { switch e.Type { case git2go.ObjectBlob: return fs.fileInfo(e) case git2go.ObjectTree: return fs.dirInfo(e), nil case git2go.ObjectCommit: submod, err := fs.repo.LookupSubmodule(path) if err != nil { return nil, err } // TODO(sqs): add (*Submodule).Free to git2go and defer submod.Free() // below when that method has been added. // // defer submod.Free() return &util.FileInfo{ Name_: e.Name, Mode_: vcs.ModeSubmodule, Sys_: vcs.SubmoduleInfo{ URL: submod.Url(), CommitID: vcs.CommitID(e.Id.String()), }, }, nil } return nil, fmt.Errorf("unexpected object type %v while making file info (expected blob, tree, or commit)", e.Type) }
func TestRepository_ResolveRevision(t *testing.T) { setup() defer teardown() repoPath := "a.b/c" repo_, _ := vcsclient.Repository(repoPath) repo := repo_.(*repository) want := vcs.CommitID("abcd") var called bool mux.HandleFunc(urlPath(t, RouteRepoRevision, repo, map[string]string{"RepoPath": repoPath, "RevSpec": "myrevspec"}), func(w http.ResponseWriter, r *http.Request) { called = true testMethod(t, r, "GET") http.Redirect(w, r, urlPath(t, RouteRepoCommit, repo, map[string]string{"CommitID": "abcd"}), http.StatusFound) }) commitID, err := repo.ResolveRevision("myrevspec") if err != nil { t.Errorf("Repository.ResolveRevision returned error: %v", err) } if !called { t.Fatal("!called") } if commitID != want { t.Errorf("Repository.ResolveRevision returned %+v, want %+v", commitID, want) } }
func (r *Repository) Branches(opt vcs.BranchesOptions) ([]*vcs.Branch, error) { r.editLock.RLock() defer r.editLock.RUnlock() refs, err := r.showRef("--heads") if err != nil { return nil, err } branches := make([]*vcs.Branch, len(refs)) for i, ref := range refs { branches[i] = &vcs.Branch{ Name: strings.TrimPrefix(ref[1], "refs/heads/"), Head: vcs.CommitID(ref[0]), } } // Fetch behind/ahead counts for each branch. if opt.BehindAheadBranch != "" { for i, branch := range branches { behind, ahead, err := r.branchCounts(branch.Name, opt.BehindAheadBranch) if err != nil { return nil, err } branches[i].Counts = &vcs.BehindAhead{Behind: uint32(behind), Ahead: uint32(ahead)} } } return branches, nil }
func TestRepository_CrossRepoMergeBase(t *testing.T) { setup() defer teardown() repoPath := "a.b/c" repo_, _ := vcsclient.Repository(repoPath) repo := repo_.(*repository) want := vcs.CommitID("abcd") var called bool mux.HandleFunc(urlPath(t, RouteRepoCrossRepoMergeBase, repo, map[string]string{"RepoPath": repoPath, "CommitIDA": "a", "BRepoPath": "x.com/y", "CommitIDB": "b"}), func(w http.ResponseWriter, r *http.Request) { called = true testMethod(t, r, "GET") http.Redirect(w, r, urlPath(t, RouteRepoCommit, repo, map[string]string{"CommitID": "abcd"}), http.StatusFound) }) bRepoPath := "x.com/y" bRepo, _ := vcsclient.Repository(bRepoPath) commitID, err := repo.CrossRepoMergeBase("a", bRepo, "b") if err != nil { t.Errorf("Repository.CrossRepoMergeBase returned error: %v", err) } if !called { t.Fatal("!called") } if commitID != want { t.Errorf("Repository.CrossRepoMergeBase returned %+v, want %+v", commitID, want) } }
func (r *Repository) makeCommit(c *git2go.Commit) *vcs.Commit { var parents []vcs.CommitID if pc := c.ParentCount(); pc > 0 { parents = make([]vcs.CommitID, pc) for i := 0; i < int(pc); i++ { parents[i] = vcs.CommitID(c.ParentId(uint(i)).String()) } } au, cm := c.Author(), c.Committer() return &vcs.Commit{ ID: vcs.CommitID(c.Id().String()), Author: vcs.Signature{au.Name, au.Email, pbtypes.NewTimestamp(au.When)}, Committer: &vcs.Signature{cm.Name, cm.Email, pbtypes.NewTimestamp(cm.When)}, Message: strings.TrimSuffix(c.Message(), "\n"), Parents: parents, } }
func (r *Repository) Tags() ([]*vcs.Tag, error) { ts := make([]*vcs.Tag, len(r.allTags.IdByName)) i := 0 for name, id := range r.allTags.IdByName { ts[i] = &vcs.Tag{Name: name, CommitID: vcs.CommitID(id)} i++ } sort.Sort(vcs.Tags(ts)) return ts, nil }
func (r *Repository) Branches(_ vcs.BranchesOptions) ([]*vcs.Branch, error) { bs := make([]*vcs.Branch, len(r.branchHeads.IdByName)) i := 0 for name, id := range r.branchHeads.IdByName { bs[i] = &vcs.Branch{Name: name, Head: vcs.CommitID(id)} i++ } sort.Sort(vcs.Branches(bs)) return bs, nil }
func (r *Repository) MergeBase(a, b vcs.CommitID) (vcs.CommitID, error) { r.editLock.RLock() defer r.editLock.RUnlock() cmd := exec.Command("git", "merge-base", "--", string(a), string(b)) cmd.Dir = r.Dir out, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("exec %v failed: %s. Output was:\n\n%s", cmd.Args, err, out) } return vcs.CommitID(bytes.TrimSpace(out)), nil }
func (r *Repository) ResolveRevision(spec string) (vcs.CommitID, error) { cmd := exec.Command("hg", "identify", "--debug", "-i", "--rev="+spec) cmd.Dir = r.Dir out, err := cmd.CombinedOutput() if err != nil { out = bytes.TrimSpace(out) if isUnknownRevisionError(string(out), spec) { return "", vcs.ErrRevisionNotFound } return "", fmt.Errorf("exec `hg identify` failed: %s. Output was:\n\n%s", err, out) } return vcs.CommitID(bytes.TrimSpace(out)), nil }
func (r *Repository) ResolveBranch(name string) (vcs.CommitID, error) { r.editLock.RLock() defer r.editLock.RUnlock() b, err := r.u.LookupBranch(name, git2go.BranchLocal) if err != nil { if err.Error() == fmt.Sprintf("Cannot locate local branch '%s'", name) { return "", vcs.ErrBranchNotFound } return "", err } return vcs.CommitID(b.Target().String()), nil }
func (r *repository) parseCommitIDInURL(urlStr string) (vcs.CommitID, error) { url, err := url.Parse(urlStr) if err != nil { return "", err } var info muxpkg.RouteMatch match := (*muxpkg.Router)(router).Match(&http.Request{Method: "GET", URL: url}, &info) if !match || info.Vars["CommitID"] == "" { return "", errors.New("failed to determine CommitID from URL") } return vcs.CommitID(info.Vars["CommitID"]), nil }
func (r *Repository) ResolveRevision(spec string) (vcs.CommitID, error) { r.editLock.RLock() defer r.editLock.RUnlock() o, err := r.u.RevparseSingle(spec) if err != nil { if err.Error() == fmt.Sprintf("Revspec '%s' not found.", spec) { return "", vcs.ErrRevisionNotFound } return "", err } defer o.Free() return vcs.CommitID(o.Id().String()), nil }
func (r *Repository) Tags() ([]*vcs.Tag, error) { refs, err := r.execAndParseCols("tags") if err != nil { return nil, err } tags := make([]*vcs.Tag, len(refs)) for i, ref := range refs { tags[i] = &vcs.Tag{ Name: ref[1], CommitID: vcs.CommitID(ref[0]), } } return tags, nil }
func (r *Repository) Branches(_ vcs.BranchesOptions) ([]*vcs.Branch, error) { refs, err := r.execAndParseCols("branches") if err != nil { return nil, err } branches := make([]*vcs.Branch, len(refs)) for i, ref := range refs { branches[i] = &vcs.Branch{ Name: ref[1], Head: vcs.CommitID(ref[0]), } } return branches, 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 }
func (r *Repository) ResolveRevision(spec string) (vcs.CommitID, error) { if id, err := r.ResolveBranch(spec); err == nil { return id, nil } if id, err := r.ResolveTag(spec); err == nil { return id, nil } rec, err := r.parseRevisionSpec(spec).Lookup(r.cl) if err != nil { if err == hg_revlog.ErrRevNotFound || err == hex.ErrLength { return "", vcs.ErrRevisionNotFound } return "", err } return vcs.CommitID(hex.EncodeToString(rec.Id())), nil }
func (r *Repository) Tags() ([]*vcs.Tag, error) { r.editLock.RLock() defer r.editLock.RUnlock() refs, err := r.showRef("--tags") if err != nil { return nil, err } tags := make([]*vcs.Tag, len(refs)) for i, ref := range refs { tags[i] = &vcs.Tag{ Name: strings.TrimPrefix(ref[1], "refs/tags/"), CommitID: vcs.CommitID(ref[0]), } } return tags, nil }
func (r *Repository) ResolveRevision(spec string) (vcs.CommitID, error) { r.editLock.RLock() defer r.editLock.RUnlock() if err := checkSpecArgSafety(spec); err != nil { return "", err } cmd := exec.Command("git", "rev-parse", spec+"^{commit}") cmd.Dir = r.Dir stdout, stderr, err := dividedOutput(cmd) if err != nil { if bytes.Contains(stderr, []byte("unknown revision")) { return "", vcs.ErrRevisionNotFound } return "", fmt.Errorf("exec `git rev-parse` failed: %s. Stderr was:\n\n%s", err, stderr) } return vcs.CommitID(bytes.TrimSpace(stdout)), nil }
func (r *Repository) ResolveTag(name string) (vcs.CommitID, error) { r.editLock.RLock() defer r.editLock.RUnlock() // TODO(sqs): slow way to iterate through tags because git_tag_lookup is not // in git2go yet refs, err := r.u.NewReferenceIterator() if err != nil { return "", err } for { ref, err := refs.Next() if err != nil { break } if ref.IsTag() && ref.Shorthand() == name { return vcs.CommitID(ref.Target().String()), nil } } return "", vcs.ErrTagNotFound }
func (r *Repository) BlameFile(path string, opt *vcs.BlameOptions) ([]*vcs.Hunk, error) { r.editLock.RLock() defer r.editLock.RUnlock() if opt == nil { opt = &vcs.BlameOptions{} } if opt.OldestCommit != "" { return nil, fmt.Errorf("OldestCommit not implemented") } if err := checkSpecArgSafety(string(opt.NewestCommit)); err != nil { return nil, err } if err := checkSpecArgSafety(string(opt.OldestCommit)); err != nil { return nil, err } args := []string{"blame", "-w", "--porcelain"} if opt.StartLine != 0 || opt.EndLine != 0 { args = append(args, fmt.Sprintf("-L%d,%d", opt.StartLine, opt.EndLine)) } args = append(args, string(opt.NewestCommit), "--", path) cmd := exec.Command("git", args...) cmd.Dir = r.Dir out, err := cmd.CombinedOutput() if err != nil { return nil, fmt.Errorf("exec `git blame` failed: %s. Output was:\n\n%s", err, out) } if len(out) < 1 { // go 1.8.5 changed the behavior of `git blame` on empty files. // previously, it returned a boundary commit. now, it returns nothing. // TODO(sqs) TODO(beyang): make `git blame` return the boundary commit // on an empty file somehow, or come up with some other workaround. st, err := os.Stat(filepath.Join(r.Dir, path)) if err == nil && st.Size() == 0 { return nil, nil } return nil, fmt.Errorf("Expected git output of length at least 1") } commits := make(map[string]vcs.Commit) hunks := make([]*vcs.Hunk, 0) remainingLines := strings.Split(string(out[:len(out)-1]), "\n") byteOffset := 0 for len(remainingLines) > 0 { // Consume hunk hunkHeader := strings.Split(remainingLines[0], " ") if len(hunkHeader) != 4 { fmt.Printf("Remaining lines: %+v, %d, '%s'\n", remainingLines, len(remainingLines), remainingLines[0]) return nil, fmt.Errorf("Expected at least 4 parts to hunkHeader, but got: '%s'", hunkHeader) } commitID := hunkHeader[0] lineNoCur, _ := strconv.Atoi(hunkHeader[2]) nLines, _ := strconv.Atoi(hunkHeader[3]) hunk := &vcs.Hunk{ CommitID: vcs.CommitID(commitID), StartLine: int(lineNoCur), EndLine: int(lineNoCur + nLines), StartByte: byteOffset, } if _, in := commits[commitID]; in { // Already seen commit byteOffset += len(remainingLines[1]) remainingLines = remainingLines[2:] } else { // New commit author := strings.Join(strings.Split(remainingLines[1], " ")[1:], " ") email := strings.Join(strings.Split(remainingLines[2], " ")[1:], " ") if len(email) >= 2 && email[0] == '<' && email[len(email)-1] == '>' { email = email[1 : len(email)-1] } authorTime, err := strconv.ParseInt(strings.Join(strings.Split(remainingLines[3], " ")[1:], " "), 10, 64) if err != nil { return nil, fmt.Errorf("Failed to parse author-time %q", remainingLines[3]) } summary := strings.Join(strings.Split(remainingLines[9], " ")[1:], " ") commit := vcs.Commit{ ID: vcs.CommitID(commitID), Message: summary, Author: vcs.Signature{ Name: author, Email: email, Date: pbtypes.NewTimestamp(time.Unix(authorTime, 0).In(time.UTC)), }, } if len(remainingLines) >= 13 && strings.HasPrefix(remainingLines[10], "previous ") { byteOffset += len(remainingLines[12]) remainingLines = remainingLines[13:] } else if len(remainingLines) >= 13 && remainingLines[10] == "boundary" { byteOffset += len(remainingLines[12]) remainingLines = remainingLines[13:] } else if len(remainingLines) >= 12 { byteOffset += len(remainingLines[11]) remainingLines = remainingLines[12:] } else if len(remainingLines) == 11 { // Empty file remainingLines = remainingLines[11:] } else { return nil, fmt.Errorf("Unexpected number of remaining lines (%d):\n%s", len(remainingLines), " "+strings.Join(remainingLines, "\n ")) } commits[commitID] = commit } if commit, present := commits[commitID]; present { // Should always be present, but check just to avoid // panicking in case of a (somewhat likely) bug in our // git-blame parser above. hunk.CommitID = commit.ID hunk.Author = commit.Author } // Consume remaining lines in hunk for i := 1; i < nLines; i++ { byteOffset += len(remainingLines[1]) remainingLines = remainingLines[2:] } hunk.EndByte = byteOffset hunks = append(hunks, hunk) } return hunks, nil }
func (r *Repository) commitLog(opt vcs.CommitsOptions) ([]*vcs.Commit, uint, error) { args := []string{"log", `--format=format:%H%x00%aN%x00%aE%x00%at%x00%cN%x00%cE%x00%ct%x00%B%x00%P%x00`} if opt.N != 0 { args = append(args, "-n", strconv.FormatUint(uint64(opt.N), 10)) } if opt.Skip != 0 { args = append(args, "--skip="+strconv.FormatUint(uint64(opt.Skip), 10)) } // Range rng := string(opt.Head) if opt.Base != "" { rng += "..." + string(opt.Base) } args = append(args, rng) cmd := exec.Command("git", args...) cmd.Dir = r.Dir out, err := cmd.CombinedOutput() if err != nil { out = bytes.TrimSpace(out) if isBadObjectErr(string(out), string(opt.Head)) { return nil, 0, vcs.ErrCommitNotFound } return nil, 0, fmt.Errorf("exec `git log` failed: %s. Output was:\n\n%s", err, out) } const partsPerCommit = 9 // number of \x00-separated fields per commit allParts := bytes.Split(out, []byte{'\x00'}) numCommits := len(allParts) / partsPerCommit commits := make([]*vcs.Commit, numCommits) for i := 0; i < numCommits; i++ { parts := allParts[partsPerCommit*i : partsPerCommit*(i+1)] // log outputs are newline separated, so all but the 1st commit ID part // has an erroneous leading newline. parts[0] = bytes.TrimPrefix(parts[0], []byte{'\n'}) authorTime, err := strconv.ParseInt(string(parts[3]), 10, 64) if err != nil { return nil, 0, fmt.Errorf("parsing git commit author time: %s", err) } committerTime, err := strconv.ParseInt(string(parts[6]), 10, 64) if err != nil { return nil, 0, fmt.Errorf("parsing git commit committer time: %s", err) } var parents []vcs.CommitID if parentPart := parts[8]; len(parentPart) > 0 { parentIDs := bytes.Split(parentPart, []byte{' '}) parents = make([]vcs.CommitID, len(parentIDs)) for i, id := range parentIDs { parents[i] = vcs.CommitID(id) } } commits[i] = &vcs.Commit{ ID: vcs.CommitID(parts[0]), Author: vcs.Signature{string(parts[1]), string(parts[2]), pbtypes.NewTimestamp(time.Unix(authorTime, 0))}, Committer: &vcs.Signature{string(parts[4]), string(parts[5]), pbtypes.NewTimestamp(time.Unix(committerTime, 0))}, Message: string(bytes.TrimSuffix(parts[7], []byte{'\n'})), Parents: parents, } } // Count commits. cmd = exec.Command("git", "rev-list", "--count", rng) cmd.Dir = r.Dir out, err = cmd.CombinedOutput() if err != nil { return nil, 0, fmt.Errorf("exec `git rev-list --count` failed: %s. Output was:\n\n%s", err, out) } out = bytes.TrimSpace(out) total, err := strconv.ParseUint(string(out), 10, 64) if err != nil { return nil, 0, err } return commits, uint(total), nil }
func (r *Repository) ResolveBranch(name string) (vcs.CommitID, error) { if id, ok := r.branchHeads.IdByName[name]; ok { return vcs.CommitID(id), nil } return "", vcs.ErrBranchNotFound }
func (r *Repository) commitLog(revSpec string, n uint) ([]*vcs.Commit, uint, error) { args := []string{"log", `--template={node}\x00{author|person}\x00{author|email}\x00{date|rfc3339date}\x00{desc}\x00{p1node}\x00{p2node}\x00`} if n != 0 { args = append(args, "--limit", strconv.FormatUint(uint64(n), 10)) } args = append(args, "--rev="+revSpec+":0") cmd := exec.Command("hg", args...) cmd.Dir = r.Dir out, err := cmd.CombinedOutput() if err != nil { out = bytes.TrimSpace(out) if isUnknownRevisionError(string(out), revSpec) { return nil, 0, vcs.ErrCommitNotFound } return nil, 0, fmt.Errorf("exec `hg log` failed: %s. Output was:\n\n%s", err, out) } const partsPerCommit = 7 // number of \x00-separated fields per commit allParts := bytes.Split(out, []byte{'\x00'}) numCommits := len(allParts) / partsPerCommit commits := make([]*vcs.Commit, numCommits) for i := 0; i < numCommits; i++ { parts := allParts[partsPerCommit*i : partsPerCommit*(i+1)] id := vcs.CommitID(parts[0]) authorTime, err := time.Parse(time.RFC3339, string(parts[3])) if err != nil { log.Println(err) //return nil, 0, err } parents, err := r.getParents(id) if err != nil { return nil, 0, fmt.Errorf("r.GetParents failed: %s. Output was:\n\n%s", err, out) } commits[i] = &vcs.Commit{ ID: id, Author: vcs.Signature{string(parts[1]), string(parts[2]), pbtypes.NewTimestamp(authorTime)}, Message: string(parts[4]), Parents: parents, } } // Count. cmd = exec.Command("hg", "id", "--num", "--rev="+revSpec) cmd.Dir = r.Dir out, err = cmd.CombinedOutput() if err != nil { return nil, 0, fmt.Errorf("exec `hg id --num` failed: %s. Output was:\n\n%s", err, out) } out = bytes.TrimSpace(out) total, err := strconv.ParseUint(string(out), 10, 64) if err != nil { return nil, 0, err } total++ // sequence number is 1 less than total number of commits return commits, uint(total), 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 (fs *gitFSCmd) lsTree(path string) ([]os.FileInfo, error) { fs.repoEditLock.RLock() defer fs.repoEditLock.RUnlock() // Don't call filepath.Clean(path) because ReadDir needs to pass // path with a trailing slash. if err := checkSpecArgSafety(path); err != nil { return nil, err } cmd := exec.Command("git", "ls-tree", "-z", "--full-name", "--long", string(fs.at), "--", path) cmd.Dir = fs.dir out, err := cmd.CombinedOutput() if err != nil { if bytes.Contains(out, []byte("exists on disk, but not in")) { return nil, &os.PathError{Op: "ls-tree", Path: path, Err: os.ErrNotExist} } return nil, fmt.Errorf("exec `git ls-files` failed: %s. Output was:\n\n%s", err, out) } if len(out) == 0 { return nil, os.ErrNotExist } lines := bytes.Split(out, []byte{'\x00'}) fis := make([]os.FileInfo, len(lines)-1) for i, line := range lines { if i == len(lines)-1 { // last entry is empty continue } // Format of `git ls-tree --long` is: // "MODE TYPE COMMITID SIZE NAME" // For example: // "100644 blob cfea37f3df073e40c52b61efcd8f94af750346c7 73 mydir/myfile" parts := bytes.SplitN(line, []byte(" "), 4) if len(parts) != 4 { return nil, fmt.Errorf("invalid `git ls-tree --long` output: %q", out) } typ := string(parts[1]) oid := parts[2] if len(oid) != 40 { return nil, fmt.Errorf("invalid `git ls-tree --long` oid output: %q", oid) } rest := bytes.TrimLeft(parts[3], " ") restParts := bytes.SplitN(rest, []byte{'\t'}, 2) if len(restParts) != 2 { return nil, fmt.Errorf("invalid `git ls-tree --long` size and/or name: %q", rest) } sizeB := restParts[0] var size int64 if len(sizeB) != 0 && sizeB[0] != '-' { size, err = strconv.ParseInt(string(sizeB), 10, 64) if err != nil { return nil, err } } name := string(restParts[1]) var sys interface{} mode, err := strconv.ParseInt(string(parts[0]), 8, 32) if err != nil { return nil, err } switch typ { case "blob": const gitModeSymlink = 020000 if mode&gitModeSymlink != 0 { // Dereference symlink. b, err := fs.readFileBytes(name) if err != nil { return nil, err } mode = int64(os.ModeSymlink) sys = vcs.SymlinkInfo{Dest: string(b)} } else { // Regular file. mode = mode | 0644 } case "commit": mode = mode | vcs.ModeSubmodule cmd := exec.Command("git", "config", "--get", "submodule."+name+".url") cmd.Dir = fs.dir out, err := cmd.CombinedOutput() if err != nil { return nil, fmt.Errorf("invalid `git config` output: %q", out) } sys = vcs.SubmoduleInfo{ URL: string(bytes.TrimSpace(out)), CommitID: vcs.CommitID(oid), } case "tree": mode = mode | int64(os.ModeDir) } mtime, err := fs.getModTimeFromGitLog(name) if err != nil { return nil, err } fis[i] = &util.FileInfo{ Name_: filepath.Base(name), Mode_: os.FileMode(mode), Size_: size, ModTime_: mtime, Sys_: sys, } } util.SortFileInfosByName(fis) return fis, nil }
func (r *Repository) BlameFile(path string, opt *vcs.BlameOptions) ([]*vcs.Hunk, error) { if opt == nil { opt = &vcs.BlameOptions{} } // TODO(sqs): implement OldestCommit cmd := exec.Command("python", "-", r.Dir, string(opt.NewestCommit), path) cmd.Dir = r.Dir cmd.Stdin = strings.NewReader(hgRepoAnnotatePy) stdout, err := cmd.StdoutPipe() if err != nil { return nil, err } stderr, err := cmd.StderrPipe() if err != nil { return nil, err } in := bufio.NewReader(stdout) if err := cmd.Start(); err != nil { return nil, err } var data struct { Commits map[string]struct { Author struct{ Name, Email string } AuthorDate time.Time } Hunks map[string][]struct { CommitID string StartLine, EndLine int StartByte, EndByte int } } jsonErr := json.NewDecoder(in).Decode(&data) errOut, _ := ioutil.ReadAll(stderr) if jsonErr != nil { cmd.Wait() return nil, fmt.Errorf("%s (stderr: %s)", jsonErr, errOut) } if err := cmd.Wait(); err != nil { return nil, fmt.Errorf("%s (stderr: %s)", err, errOut) } hunks := make([]*vcs.Hunk, len(data.Hunks[path])) for i, hunk := range data.Hunks[path] { c := data.Commits[hunk.CommitID] hunks[i] = &vcs.Hunk{ StartLine: hunk.StartLine, EndLine: hunk.EndLine, StartByte: hunk.StartByte, EndByte: hunk.EndByte, CommitID: vcs.CommitID(hunk.CommitID), Author: vcs.Signature{ Name: c.Author.Name, Email: c.Author.Email, Date: pbtypes.NewTimestamp(c.AuthorDate.In(time.UTC)), }, } } return hunks, nil }