package common import ( "errors" "fmt" "io" ) const ( GitStatus_Untracked = 0 GitStatus_Modified = 1 GitStatus_Ignored = 2 GitStatus_Unmerged = 3 // States[0..3] -- Stage1, Stage2, Stage3 of merge objects GitStatus_Renamed = 4 // orig name in States[0] ) type GitStatusData struct { Path string Status int States [3]string /* A 4 character field describing the submodule state. "N..." when the entry is not a submodule. "S" when the entry is a submodule. is "C" if the commit changed; otherwise ".". is "M" if it has tracked changes; otherwise ".". is "U" if there are untracked changes; otherwise ".". */ SubmoduleChanges string } func parseGit_HexString(data io.ByteReader) (string, error) { str := make([]byte, 0, 32) for { c, err := data.ReadByte() if err != nil { return "", err } switch { case c == 0 || c == ' ': return string(str), nil case c >= 'a' && c <= 'f': case c >= 'A' && c <= 'F': case c >= '0' && c <= '9': default: return "", errors.New("Invalid character in hex string:" + string(c)) } str = append(str, c) } } func parseGit_String(data io.ByteReader) (string, error) { str := make([]byte, 0, 100) for { c, err := data.ReadByte() if err != nil { return "", errors.New("Unexpected EOF. Expected NUL string term") } if c == 0 || c == ' ' { return string(str), nil } str = append(str, c) } } func parseGit_StringWithSpace(data io.ByteReader) (string, error) { str := make([]byte, 0, 100) for { c, err := data.ReadByte() if err != nil { return "", errors.New("Unexpected EOF. Expected NUL string term") } if c == 0 { return string(str), nil } str = append(str, c) } } func skipGitStatusEntry(data io.ByteReader, skipSpaceLen int) error { for skipSpaceLen > 0 { c, err := data.ReadByte() if err != nil { return err } if c == ' ' { skipSpaceLen-- } } return nil } func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) { ret := GitStatusData{} statusType, err := data.ReadByte() if err != nil { return nil, nil } switch statusType { case '1': var err error if err = skipGitStatusEntry(data, 8); err != nil { return nil, err } ret.Status = GitStatus_Modified ret.Path, err = parseGit_StringWithSpace(data) if err != nil { return nil, err } case '2': var err error if err = skipGitStatusEntry(data, 9); err != nil { return nil, err } ret.Status = GitStatus_Renamed ret.Path, err = parseGit_StringWithSpace(data) if err != nil { return nil, err } ret.States[0], err = parseGit_StringWithSpace(data) if err != nil { return nil, err } case '?': var err error if err = skipGitStatusEntry(data, 1); err != nil { return nil, err } ret.Status = GitStatus_Untracked ret.Path, err = parseGit_StringWithSpace(data) if err != nil { return nil, err } case '!': var err error if err = skipGitStatusEntry(data, 1); err != nil { return nil, err } ret.Status = GitStatus_Ignored ret.Path, err = parseGit_StringWithSpace(data) if err != nil { return nil, err } case 'u': var err error if err = skipGitStatusEntry(data, 2); err != nil { return nil, err } if ret.SubmoduleChanges, err = parseGit_String(data); err != nil { return nil, err } if err = skipGitStatusEntry(data, 4); err != nil { return nil, err } if ret.States[0], err = parseGit_HexString(data); err != nil { return nil, err } if ret.States[1], err = parseGit_HexString(data); err != nil { return nil, err } if ret.States[2], err = parseGit_HexString(data); err != nil { return nil, err } ret.Status = GitStatus_Unmerged ret.Path, err = parseGit_StringWithSpace(data) if err != nil { return nil, err } default: return nil, errors.New("Invalid status type" + string(statusType)) } return &ret, nil } func parseGitStatusData(data io.ByteReader) (Data, error) { ret := make([]GitStatusData, 0, 10) for { data, err := parseSingleStatusEntry(data) if err != nil { return nil, err } else if data == nil { break } ret = append(ret, *data) } return ret, nil } type Data interface{} type CommitStatus int const ( Add CommitStatus = iota Rm Copy Modify Rename TypeChange Unmerged Unknown ) type GitDiffRawData struct { SrcMode, DstMode string SrcCommit, DstCommit string Status CommitStatus Src, Dst string } func parseGit_DiffIndexStatus(data io.ByteReader, d *GitDiffRawData) error { b, err := data.ReadByte() if err != nil { return err } switch b { case 'A': d.Status = Add case 'C': d.Status = Copy case 'D': d.Status = Rm case 'M': d.Status = Modify case 'R': d.Status = Rename case 'T': d.Status = TypeChange case 'U': d.Status = Unmerged case 'X': return fmt.Errorf("Unexpected unknown change type. This is a git bug") } _, err = parseGit_StringWithSpace(data) if err != nil { return err } return nil } func parseSingleGitDiffIndexRawData(data io.ByteReader) (*GitDiffRawData, error) { var ret GitDiffRawData b, err := data.ReadByte() if err != nil { return nil, err } if b != ':' { return nil, fmt.Errorf("Expected ':' but got '%s'", string(b)) } if ret.SrcMode, err = parseGit_String(data); err != nil { return nil, err } if ret.DstMode, err = parseGit_String(data); err != nil { return nil, err } if ret.Src, err = parseGit_String(data); err != nil { return nil, err } if ret.Dst, err = parseGit_String(data); err != nil { return nil, err } if err = parseGit_DiffIndexStatus(data, &ret); err != nil { return nil, err } ret.Dst = ret.Src switch ret.Status { case Copy, Rename: if ret.Src, err = parseGit_StringWithSpace(data); err != nil { return nil, err } } return &ret, nil } func parseGitDiffIndexRawData(data io.ByteReader) (Data, error) { ret := make([]GitDiffRawData, 0, 10) for { data, err := parseSingleGitDiffIndexRawData(data) if err != nil { return nil, err } else if data == nil { break } ret = append(ret, *data) } return ret, nil }