Skip to content

Commit

Permalink
merge-file: support merge-file command
Browse files Browse the repository at this point in the history
  • Loading branch information
fcharlie committed Dec 18, 2024
1 parent c5b66ec commit 5af0cb5
Show file tree
Hide file tree
Showing 17 changed files with 373 additions and 115 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ bin/*
local/*
.DS_Store
*.gop1
*.tomlp1
*.rej
/out/
/.vscode/
Expand Down
1 change: 1 addition & 0 deletions cmd/zeta/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type App struct {
MergeBase command.MergeBase `cmd:"merge-base" help:"Find optimal common ancestors for merge"`
LsFiles command.LsFiles `cmd:"ls-files" help:"Show information about files in the index and the working tree"`
HashObject command.HashObject `cmd:"hash-object" help:"Compute hash or create object"`
MergeFile command.MergeFile `cmd:"merge-file" help:"Run a three-way file merge"`
Version command.Version `cmd:"version" help:"Display version information"`
Debug bool `name:"debug" help:"Enable debug mode; analyze timing"`
}
Expand Down
40 changes: 19 additions & 21 deletions modules/diferenco/diferenco.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,40 +38,38 @@ const (
Patience
)

func (a Algorithm) String() string {
switch a {
case Unspecified:
return "Unspecified"
case Histogram:
return "Histogram"
case Myers:
return "Myers"
case Minimal:
return "Minimal"
case ONP:
return "O(NP)"
case Patience:
return "Patience"
}
return "Unknown"
}

var (
ErrUnsupportedAlgorithm = errors.New("unsupport algorithm")
)

var (
diffAlgorithms = map[string]Algorithm{
algorithmValueMap = map[string]Algorithm{
"histogram": Histogram,
"onp": ONP,
"myers": Myers,
"patience": Patience,
"minimal": Minimal,
}
algorithmNameMap = map[Algorithm]string{
Unspecified: "unspecified",
Histogram: "histogram",
ONP: "onp",
Myers: "myers",
Minimal: "minimal",
Patience: "patience",
}
)

func ParseAlgorithm(s string) (Algorithm, error) {
if a, ok := diffAlgorithms[strings.ToLower(s)]; ok {
func (a Algorithm) String() string {
n, ok := algorithmNameMap[a]
if ok {
return n
}
return "unspecified"
}

func AlgorithmFromName(s string) (Algorithm, error) {
if a, ok := algorithmValueMap[strings.ToLower(s)]; ok {
return a, nil
}
return Unspecified, fmt.Errorf("unsupport algoritm '%s' %w", s, ErrUnsupportedAlgorithm)
Expand Down
16 changes: 13 additions & 3 deletions modules/diferenco/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ func ParseConflictStyle(s string) int {

type MergeOptions struct {
TextO, TextA, TextB string
RO, R1, R2 io.Reader // when if set
LabelO, LabelA, LabelB string
A Algorithm
Style int // Conflict Style
Expand Down Expand Up @@ -399,9 +400,18 @@ func Merge(ctx context.Context, opts *MergeOptions) (string, bool, error) {
default:
}
s := NewSink(NEWLINE_RAW)
slicesO := s.SplitLines(opts.TextO)
slicesA := s.SplitLines(opts.TextA)
slicesB := s.SplitLines(opts.TextB)
slicesO, err := s.parseLines(opts.RO, opts.TextO)
if err != nil {
return "", false, err
}
slicesA, err := s.parseLines(opts.R1, opts.TextA)
if err != nil {
return "", false, err
}
slicesB, err := s.parseLines(opts.R2, opts.TextB)
if err != nil {
return "", false, err
}
regions, err := Diff3Merge(ctx, slicesO, slicesA, slicesB, opts.A, true)
if err != nil {
return "", false, err
Expand Down
63 changes: 35 additions & 28 deletions modules/zeta/object/text.go → modules/diferenco/text.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
// Copyright ©️ Ant Group. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package object
package diferenco

import (
"bytes"
Expand All @@ -23,43 +20,41 @@ import (
// #define MAX_XDIFF_SIZE (1024UL * 1024 * 1023)

const (
// MAX_DIFF_SIZE 100MiB
MAX_DIFF_SIZE = 100 * 1024 * 1024
MAX_DIFF_SIZE = 100 << 20 // MAX_DIFF_SIZE 100MiB
BINARY = "binary"
sniffLen = 8000
UTF8 = "UTF-8"
sniffLen = 8000
)

var (
ErrNotTextContent = errors.New("not a text content")
ErrNonTextContent = errors.New("non-text content")
)

func textCharset(s string) string {
func checkCharset(s string) string {
if _, charset, ok := strings.Cut(s, ";"); ok {
return strings.TrimPrefix(strings.TrimSpace(charset), "charset=")
}
return "UTF-8"
return UTF8
}

func resolveCharset(payload []byte) string {
func detectCharset(payload []byte) string {
result := mime.DetectAny(payload)
for p := result; p != nil; p = p.Parent() {
if p.Is("text/plain") {
return textCharset(p.String())
return checkCharset(p.String())
}
}
return BINARY
}

// readText: Read all text content: automatically detect text encoding and convert to UTF-8, binary will return ErrNotTextContent
func readText(r io.Reader) (string, string, error) {
func readUnifiedText(r io.Reader) (string, string, error) {
sniffBytes, err := streamio.ReadMax(r, sniffLen)
if err != nil {
return "", "", err
}
charset := resolveCharset(sniffBytes)
charset := detectCharset(sniffBytes)
if charset == BINARY {
return "", "", ErrNotTextContent
return "", "", ErrNonTextContent
}
reader := io.MultiReader(bytes.NewReader(sniffBytes), r)
if strings.EqualFold(charset, UTF8) {
Expand All @@ -75,50 +70,62 @@ func readText(r io.Reader) (string, string, error) {
}
buf, err := chardet.DecodeFromCharset(b.Bytes(), charset)
if err != nil {
return "", "", ErrNotTextContent
return "", "", ErrNonTextContent
}
if len(buf) == 0 {
return "", "", nil
}
return unsafe.String(unsafe.SliceData(buf), len(buf)), charset, nil
}

func readTextUTF8(r io.Reader) (string, error) {
func readRawText(r io.Reader, size int) (string, error) {
var b bytes.Buffer
if _, err := b.ReadFrom(io.LimitReader(r, sniffLen)); err != nil {
return "", err
}
if bytes.IndexByte(b.Bytes(), 0) != -1 {
return "", ErrNotTextContent
return "", ErrNonTextContent
}
b.Grow(size)
if _, err := b.ReadFrom(r); err != nil {
return "", err
}
return b.String(), nil
content := b.Bytes()
return unsafe.String(unsafe.SliceData(content), len(content)), nil
}

// GetUnifiedText: Read all text content.
func GetUnifiedText(r io.Reader, size int64, codecvt bool) (string, string, error) {
func ReadUnifiedText(r io.Reader, size int64, textConv bool) (content string, charset string, err error) {
if size > MAX_DIFF_SIZE {
return "", "", ErrNotTextContent
return "", "", ErrNonTextContent
}
if codecvt {
return readText(r)
if textConv {
return readUnifiedText(r)
}
s, err := readTextUTF8(r)
return s, UTF8, err
content, err = readRawText(r, int(size))
return content, UTF8, err
}

func NewUnifiedReader(r io.Reader) (io.Reader, error) {
sniffBytes, err := streamio.ReadMax(r, sniffLen)
if err != nil {
return nil, err
}
charset := resolveCharset(sniffBytes)
charset := detectCharset(sniffBytes)
reader := io.MultiReader(bytes.NewReader(sniffBytes), r)
// binary or UTF-8 not need convert
if charset == BINARY || strings.EqualFold(charset, UTF8) {
return reader, nil
}
return chardet.NewReader(reader, charset), nil
}

func NewTextReader(r io.Reader) (io.Reader, error) {
sniffBytes, err := streamio.ReadMax(r, sniffLen)
if err != nil {
return nil, err
}
if bytes.IndexByte(sniffBytes, 0) != -1 {
return nil, ErrNonTextContent
}
return io.MultiReader(bytes.NewReader(sniffBytes), r), nil
}
6 changes: 5 additions & 1 deletion modules/zeta/object/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func (f *File) OriginReader(ctx context.Context) (io.ReadCloser, int64, error) {
return &readCloser{Reader: br.Contents, Closer: br}, br.Size, nil
}

const (
sniffLen = 8000
)

func (f *File) Reader(ctx context.Context) (io.ReadCloser, bool, error) {
if f.b == nil {
return nil, false, io.ErrUnexpectedEOF
Expand Down Expand Up @@ -89,7 +93,7 @@ func (f *File) UnifiedText(ctx context.Context, codecvt bool) (content string, e
return "", err
}
defer r.Close()
content, _, err = GetUnifiedText(r, f.Size, codecvt)
content, _, err = diferenco.ReadUnifiedText(r, f.Size, codecvt)
return content, err
}

Expand Down
10 changes: 5 additions & 5 deletions modules/zeta/object/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type PatchOptions struct {
}

func sizeOverflow(f *File) bool {
return f != nil && f.Size > MAX_DIFF_SIZE
return f != nil && f.Size > diferenco.MAX_DIFF_SIZE
}

func fileStatName(from, to *File) string {
Expand Down Expand Up @@ -64,14 +64,14 @@ func fileStatWithContext(ctx context.Context, opts *PatchOptions, c *Change) (*F
return s, nil
}
fromContent, err := from.UnifiedText(ctx, opts.Textconv)
if plumbing.IsNoSuchObject(err) || err == ErrNotTextContent {
if plumbing.IsNoSuchObject(err) || err == diferenco.ErrNonTextContent {
return s, nil
}
if err != nil {
return nil, err
}
toContent, err := to.UnifiedText(ctx, opts.Textconv)
if plumbing.IsNoSuchObject(err) || err == ErrNotTextContent {
if plumbing.IsNoSuchObject(err) || err == diferenco.ErrNonTextContent {
return s, nil
}
if err != nil {
Expand Down Expand Up @@ -122,14 +122,14 @@ func filePatchWithContext(ctx context.Context, opts *PatchOptions, c *Change) (*
return &diferenco.Unified{From: from.asFile(), To: to.asFile(), IsBinary: true}, nil
}
fromContent, err := from.UnifiedText(ctx, opts.Textconv)
if plumbing.IsNoSuchObject(err) || err == ErrNotTextContent {
if plumbing.IsNoSuchObject(err) || err == diferenco.ErrNonTextContent {
return &diferenco.Unified{From: from.asFile(), To: to.asFile(), IsBinary: true}, nil
}
if err != nil {
return nil, err
}
toContent, err := to.UnifiedText(ctx, opts.Textconv)
if plumbing.IsNoSuchObject(err) || err == ErrNotTextContent {
if plumbing.IsNoSuchObject(err) || err == diferenco.ErrNonTextContent {
return &diferenco.Unified{From: from.asFile(), To: to.asFile(), IsBinary: true}, nil
}
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/command/command_diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (c *Diff) Passthrough(paths []string) {

func (c *Diff) checkAlgorithm() (diferenco.Algorithm, error) {
if len(c.DiffAlgorithm) != 0 {
return diferenco.ParseAlgorithm(c.DiffAlgorithm)
return diferenco.AlgorithmFromName(c.DiffAlgorithm)
}
if c.Histogram {
return diferenco.Histogram, nil
Expand Down
Loading

0 comments on commit 5af0cb5

Please sign in to comment.