// ReadAllHeaders reads the file headers and extended headers (if any) // from a file unified diff. It does not read hunks, and the returned // FileDiff's Hunks field is nil. To read the hunks, call the // (*FileDiffReader).HunksReader() method to get a HunksReader and // read hunks from that. func (r *FileDiffReader) ReadAllHeaders() (*FileDiff, error) { var err error fd := &FileDiff{} fd.Extended, err = r.ReadExtendedHeaders() if err != nil { return nil, err } var origTime, newTime *time.Time fd.OrigName, fd.NewName, origTime, newTime, err = r.ReadFileHeaders() if err != nil { return nil, err } if origTime != nil { ts := pbtypes.NewTimestamp(*origTime) fd.OrigTime = &ts } if newTime != nil { ts := pbtypes.NewTimestamp(*newTime) fd.NewTime = &ts } return fd, 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 (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 newTreeEntry(fi os.FileInfo) *TreeEntry { e := &TreeEntry{ Name: fi.Name(), Size: fi.Size(), ModTime: pbtypes.NewTimestamp(fi.ModTime()), } if fi.Mode().IsDir() { e.Type = DirEntry } else if fi.Mode().IsRegular() { e.Type = FileEntry } else if fi.Mode()&os.ModeSymlink != 0 { e.Type = SymlinkEntry } return e }
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) 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 (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 }
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 }