summaryrefslogtreecommitdiff
path: root/libgo/go/patch/git.go
blob: 6516097260a3956c8554bad8db7ec4a4530a9b7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package patch

import (
	"bytes"
	"compress/zlib"
	"crypto/sha1"
	"encoding/git85"
	"fmt"
	"io"
	"os"
)

func gitSHA1(data []byte) []byte {
	if len(data) == 0 {
		// special case: 0 length is all zeros sum
		return make([]byte, 20)
	}
	h := sha1.New()
	fmt.Fprintf(h, "blob %d\x00", len(data))
	h.Write(data)
	return h.Sum()
}

// BUG(rsc): The Git binary delta format is not implemented, only Git binary literals.

// GitBinaryLiteral represents a Git binary literal diff.
type GitBinaryLiteral struct {
	OldSHA1 []byte // if non-empty, the SHA1 hash of the original
	New     []byte // the new contents
}

// Apply implements the Diff interface's Apply method.
func (d *GitBinaryLiteral) Apply(old []byte) ([]byte, os.Error) {
	if sum := gitSHA1(old); !bytes.HasPrefix(sum, d.OldSHA1) {
		return nil, ErrPatchFailure
	}
	return d.New, nil
}

func unhex(c byte) uint8 {
	switch {
	case '0' <= c && c <= '9':
		return c - '0'
	case 'a' <= c && c <= 'f':
		return c - 'a' + 10
	case 'A' <= c && c <= 'F':
		return c - 'A' + 10
	}
	return 255
}

func getHex(s []byte) (data []byte, rest []byte) {
	n := 0
	for n < len(s) && unhex(s[n]) != 255 {
		n++
	}
	n &^= 1 // Only take an even number of hex digits.
	data = make([]byte, n/2)
	for i := range data {
		data[i] = unhex(s[2*i])<<4 | unhex(s[2*i+1])
	}
	rest = s[n:]
	return
}

// ParseGitBinary parses raw as a Git binary patch.
func ParseGitBinary(raw []byte) (Diff, os.Error) {
	var oldSHA1, newSHA1 []byte
	var sawBinary bool

	for {
		var first []byte
		first, raw, _ = getLine(raw, 1)
		first = bytes.TrimSpace(first)
		if s, ok := skip(first, "index "); ok {
			oldSHA1, s = getHex(s)
			if s, ok = skip(s, ".."); !ok {
				continue
			}
			newSHA1, s = getHex(s)
			continue
		}
		if _, ok := skip(first, "GIT binary patch"); ok {
			sawBinary = true
			continue
		}
		if n, _, ok := atoi(first, "literal ", 10); ok && sawBinary {
			data := make([]byte, n)
			d := git85.NewDecoder(bytes.NewBuffer(raw))
			z, err := zlib.NewReader(d)
			if err != nil {
				return nil, err
			}
			defer z.Close()
			if _, err = io.ReadFull(z, data); err != nil {
				if err == os.EOF {
					err = io.ErrUnexpectedEOF
				}
				return nil, err
			}
			var buf [1]byte
			m, err := z.Read(buf[0:])
			if m != 0 || err != os.EOF {
				return nil, os.NewError("Git binary literal longer than expected")
			}

			if sum := gitSHA1(data); !bytes.HasPrefix(sum, newSHA1) {
				return nil, os.NewError("Git binary literal SHA1 mismatch")
			}
			return &GitBinaryLiteral{oldSHA1, data}, nil
		}
		if !sawBinary {
			return nil, os.NewError("unexpected Git patch header: " + string(first))
		}
	}
	panic("unreachable")
}