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) 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 (h *Handler) serveRepoMergeBase(w http.ResponseWriter, r *http.Request) error { v := mux.Vars(r) repo, repoPath, done, err := h.getRepo(r) if err != nil { return err } defer done() if repo, ok := repo.(vcs.Merger); ok { a, b := vcs.CommitID(v["CommitIDA"]), vcs.CommitID(v["CommitIDB"]) mb, err := repo.MergeBase(a, b) if err != nil { return err } var statusCode int if commitIDIsCanon(string(a)) && commitIDIsCanon(string(b)) { setLongCache(w) statusCode = http.StatusMovedPermanently } else { setShortCache(w) statusCode = http.StatusFound } http.Redirect(w, r, h.router.URLToRepoCommit(repoPath, mb).String(), statusCode) return nil } return &httpError{http.StatusNotImplemented, fmt.Errorf("Merger not yet implemented by %T", repo)} }
func TestServeRepoCrossRepoDiff(t *testing.T) { setupHandlerTest() defer teardownHandlerTest() baseRepoPath := "a.b/c" headRepoPath := "x.y/z" mockHeadRepo := vcs_testing.MockRepository{} opt := vcs.DiffOptions{} rm := &mockCrossRepoDiff{ t: t, base: vcs.CommitID(strings.Repeat("a", 40)), headRepo: mockHeadRepo, head: vcs.CommitID(strings.Repeat("b", 40)), opt: opt, diff: &vcs.Diff{Raw: "diff"}, } sm := &mockService{ t: t, open: func(repoPath string) (interface{}, error) { switch repoPath { case baseRepoPath: return rm, nil case headRepoPath: return mockHeadRepo, nil default: panic("unexpected repo clone: " + repoPath) } }, } testHandler.Service = sm resp, err := http.Get(server.URL + testHandler.router.URLToRepoCrossRepoDiff(baseRepoPath, rm.base, headRepoPath, rm.head, &opt).String()) if err != nil && !isIgnoredRedirectErr(err) { t.Fatal(err) } defer resp.Body.Close() if !rm.called { t.Errorf("!called") } var diff *vcs.Diff if err := json.NewDecoder(resp.Body).Decode(&diff); err != nil { t.Fatal(err) } if !reflect.DeepEqual(diff, rm.diff) { t.Errorf("got crossRepoDiff %+v, want %+v", diff, rm.diff) } }
// ResolveRevision returns the revision that the given revision // specifier resolves to, or a non-nil error if there is no such // revision. func (r *Repository) ResolveRevision(spec string) (vcs.CommitID, error) { // TODO: git rev-parse supports a horde of complex syntaxes, it will be a fair bit more work to support all of them. // e.g. "master@{yesterday}", "master~3", and various text/path/tree traversal search. if len(spec) == 40 { commit, err := r.repo.GetCommit(spec) if err == nil { return vcs.CommitID(commit.Id.String()), nil } } ci, err := r.ResolveBranch(spec) if err == nil { return ci, nil } ci, err = r.ResolveTag(spec) if err == nil { return ci, nil } // Do an extra lookup just in case it's a complex syntax we don't support // TODO: Remove fallback usage: ResolveRevision ci, err = r.Repository.ResolveRevision(spec) if err == nil { return ci, nil } return ci, vcs.ErrRevisionNotFound }
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 (r *Repository) Branches(opt vcs.BranchesOptions) ([]*vcs.Branch, error) { if opt.ContainsCommit != "" { return nil, fmt.Errorf("vcs.BranchesOptions.ContainsCommit option not implemented") } 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 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 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 (fs *filesystem) submoduleInfo(path string, e *git.TreeEntry) (*util.FileInfo, error) { // TODO: Cache submodules? subs, err := e.Tree().GetSubmodules() if err != nil { return nil, err } var found *git.Submodule for _, sub := range subs { if sub.Path == path { found = sub break } } if found == nil { return nil, fmt.Errorf("submodule not found: %s", path) } return &util.FileInfo{ Name_: e.Name(), Mode_: vcs.ModeSubmodule, Sys_: vcs.SubmoduleInfo{ URL: found.URL, CommitID: vcs.CommitID(e.Id.String()), }, }, 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.Submodules.Lookup(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 (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 TestServeRepoTreeEntry_File(t *testing.T) { setupHandlerTest() defer teardownHandlerTest() commitID := vcs.CommitID(strings.Repeat("a", 40)) repoPath := "a.b/c" rm := &mockFileSystem{ t: t, at: commitID, fs: mapFS(map[string]string{"myfile": "mydata"}), } sm := &mockServiceForExistingRepo{ t: t, repoPath: repoPath, repo: rm, } testHandler.Service = sm resp, err := http.Get(server.URL + testHandler.router.URLToRepoTreeEntry(repoPath, commitID, "myfile").String()) if err != nil { t.Fatal(err) } defer resp.Body.Close() if got, want := resp.StatusCode, http.StatusOK; got != want { t.Errorf("got status code %d, want %d", got, want) } if !sm.opened { t.Errorf("!opened") } if !rm.called { t.Errorf("!called") } var e *vcsclient.TreeEntry if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { t.Fatal(err) } wantEntry := &vcsclient.TreeEntry{ Name: "myfile", Type: vcsclient.FileEntry, Size: 6, ModTime: pbtypes.NewTimestamp(time.Time{}), Contents: []byte("mydata"), } if !reflect.DeepEqual(e, wantEntry) { t.Errorf("got tree entry %+v, want %+v", e, wantEntry) } // used canonical commit ID, so should be long-cached if cc := resp.Header.Get("cache-control"); cc != longCacheControl { t.Errorf("got cache-control %q, want %q", cc, longCacheControl) } }
// ResolveBranch returns the branch with the given name, or // ErrBranchNotFound if no such branch exists. func (r *Repository) ResolveBranch(name string) (vcs.CommitID, error) { id, err := r.repo.GetCommitIdOfBranch(name) if _, ok := err.(git.RefNotFound); ok { return "", vcs.ErrBranchNotFound } else if err != nil { // Unexpected error return "", err } return vcs.CommitID(id), nil }
func TestServeRepoDiff(t *testing.T) { setupHandlerTest() defer teardownHandlerTest() repoPath := "a.b/c" opt := vcs.DiffOptions{} rm := &mockDiff{ t: t, base: vcs.CommitID(strings.Repeat("a", 40)), head: vcs.CommitID(strings.Repeat("b", 40)), opt: opt, diff: &vcs.Diff{Raw: "diff"}, } sm := &mockServiceForExistingRepo{ t: t, repoPath: repoPath, repo: rm, } testHandler.Service = sm resp, err := http.Get(server.URL + testHandler.router.URLToRepoDiff(repoPath, rm.base, rm.head, &opt).String()) if err != nil && !isIgnoredRedirectErr(err) { t.Fatal(err) } defer resp.Body.Close() if !sm.opened { t.Errorf("!opened") } if !rm.called { t.Errorf("!called") } var diff *vcs.Diff if err := json.NewDecoder(resp.Body).Decode(&diff); err != nil { t.Fatal(err) } if !reflect.DeepEqual(diff, rm.diff) { t.Errorf("got diff %+v, want %+v", diff, rm.diff) } }
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 }
// checkCommitID returns whether the commit ID is canonical (i.e., the // full 40-character commit ID), and an error (if any). func checkCommitID(commitID string) (vcs.CommitID, bool, error) { if commitID == "" { return "", false, &httpError{http.StatusBadRequest, errors.New("CommitID is empty")} } if !isLowercaseHex(commitID) { return "", false, &httpError{http.StatusBadRequest, errors.New("CommitID must be lowercase hex")} } return vcs.CommitID(commitID), commitIDIsCanon(commitID), 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 (h *Handler) serveRepoDiff(w http.ResponseWriter, r *http.Request) error { v := mux.Vars(r) repo, _, done, err := h.getRepo(r) if err != nil { return err } defer done() var opt vcs.DiffOptions if err := schemaDecoder.Decode(&opt, r.URL.Query()); err != nil { return err } if repo, ok := repo.(vcs.Differ); ok { diff, err := repo.Diff(vcs.CommitID(v["Base"]), vcs.CommitID(v["Head"]), &opt) if err != nil { return err } _, baseCanon, err := checkCommitID(v["Base"]) if err != nil { return err } _, headCanon, err := checkCommitID(v["Head"]) if err != nil { return err } if baseCanon && headCanon { setLongCache(w) } else { setShortCache(w) } return writeJSON(w, diff) } return &httpError{http.StatusNotImplemented, fmt.Errorf("Diff not yet implemented for %T", repo)} }
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) 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) 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) Branches(opt vcs.BranchesOptions) ([]*vcs.Branch, error) { if opt.ContainsCommit != "" { return nil, fmt.Errorf("vcs.BranchesOptions.ContainsCommit option not implemented") } 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) Branches(opt vcs.BranchesOptions) ([]*vcs.Branch, error) { r.editLock.RLock() defer r.editLock.RUnlock() f := make(branchFilter) if opt.MergedInto != "" { b, err := r.branches("--merged", opt.MergedInto) if err != nil { return nil, err } f.add(b) } if opt.ContainsCommit != "" { b, err := r.branches("--contains=" + opt.ContainsCommit) if err != nil { return nil, err } f.add(b) } refs, err := r.showRef("--heads") if err != nil { return nil, err } var branches []*vcs.Branch for _, ref := range refs { name := strings.TrimPrefix(ref[1], "refs/heads/") id := vcs.CommitID(ref[0]) if !f.allows(name) { continue } branch := &vcs.Branch{Name: name, Head: id} if opt.IncludeCommit { branch.Commit, err = r.getCommit(id) if err != nil { return nil, err } } if opt.BehindAheadBranch != "" { branch.Counts, err = r.branchesBehindAhead(name, opt.BehindAheadBranch) if err != nil { return nil, err } } branches = append(branches, branch) } return branches, 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 }
// 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 }
// Convert a git.Commit to a vcs.Commit func (r *Repository) vcsCommit(commit *git.Commit) *vcs.Commit { var committer *vcs.Signature if commit.Committer != nil { committer = &vcs.Signature{ Name: commit.Committer.Name, Email: commit.Committer.Email, Date: pbtypes.NewTimestamp(commit.Committer.When), } } n := commit.ParentCount() parentIds := commit.ParentIds() parents := make([]vcs.CommitID, 0, len(parentIds)) for _, id := range parentIds { parents = append(parents, vcs.CommitID(id.String())) } if n == 0 { // Required to make reflect.DeepEqual tests pass. :/ parents = nil } var author vcs.Signature if commit.Author != nil { author.Name = commit.Author.Name author.Email = commit.Author.Email author.Date = pbtypes.NewTimestamp(commit.Author.When) } return &vcs.Commit{ ID: vcs.CommitID(commit.Id.String()), Author: author, Committer: committer, Message: strings.TrimSuffix(commit.Message(), "\n"), Parents: parents, } }
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 }
// Tags returns a list of all tags in the repository. func (r *Repository) Tags() ([]*vcs.Tag, error) { names, err := r.repo.GetTags() if err != nil { return nil, err } tags := make([]*vcs.Tag, 0, len(names)) for _, name := range names { id, err := r.ResolveTag(name) if err != nil { return nil, err } tags = append(tags, &vcs.Tag{Name: name, CommitID: vcs.CommitID(id)}) } return tags, nil }