If merging a ProjectGit with submodules, * removed submodules are to be removed always * modified/added submodules are to be present * submodules modified in base project and PR should conflict
297 lines
6.0 KiB
Go
297 lines
6.0 KiB
Go
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
|
|
|
|
/*
|
|
<sub> A 4 character field describing the submodule state.
|
|
"N..." when the entry is not a submodule.
|
|
"S<c><m><u>" when the entry is a submodule.
|
|
<c> is "C" if the commit changed; otherwise ".".
|
|
<m> is "M" if it has tracked changes; otherwise ".".
|
|
<u> 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
|
|
}
|