Skip to content

*: Add support for initializing SHA256 repositories #707

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/git.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ jobs:
- name: Test
run: make test-coverage

- name: Test SHA256
run: make test-sha256

- name: Build go-git with CGO disabled
run: go build ./...
env:
Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ test:
@echo "running against `git version`"; \
$(GOTEST) -race ./...

TEMP_REPO := $(shell mktemp)
test-sha256:
$(GOCMD) run -tags sha256 _examples/sha256/main.go $(TEMP_REPO)
cd $(TEMP_REPO) && git fsck
rm -rf $(TEMP_REPO)

test-coverage:
@echo "running against `git version`"; \
echo "" > $(COVERAGE_REPORT); \
Expand Down
66 changes: 66 additions & 0 deletions _examples/sha256/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"

"github.com/go-git/go-git/v5"
. "github.com/go-git/go-git/v5/_examples"
"github.com/go-git/go-git/v5/plumbing/format/config"
"github.com/go-git/go-git/v5/plumbing/object"
)

// This example requires building with the sha256 tag for it to work:
// go run -tags sha256 main.go /tmp/repository

// Basic example of how to initialise a repository using sha256 as the hashing algorithmn.
func main() {
CheckArgs("<directory>")
directory := os.Args[1]

os.RemoveAll(directory)

// Init a new repository using the ObjectFormat SHA256.
r, err := git.PlainInitWithOptions(directory, &git.PlainInitOptions{ObjectFormat: config.SHA256})
CheckIfError(err)

w, err := r.Worktree()
CheckIfError(err)

// ... we need a file to commit so let's create a new file inside of the
// worktree of the project using the go standard library.
Info("echo \"hello world!\" > example-git-file")
filename := filepath.Join(directory, "example-git-file")
err = ioutil.WriteFile(filename, []byte("hello world!"), 0644)
CheckIfError(err)

// Adds the new file to the staging area.
Info("git add example-git-file")
_, err = w.Add("example-git-file")
CheckIfError(err)

// Commits the current staging area to the repository, with the new file
// just created. We should provide the object.Signature of Author of the
// commit Since version 5.0.1, we can omit the Author signature, being read
// from the git config files.
Info("git commit -m \"example go-git commit\"")
commit, err := w.Commit("example go-git commit", &git.CommitOptions{
Author: &object.Signature{
Name: "John Doe",
Email: "john@doe.org",
When: time.Now(),
},
})

CheckIfError(err)

// Prints the current HEAD to verify that all worked well.
Info("git show -s")
obj, err := r.CommitObject(commit)
CheckIfError(err)

fmt.Println(obj)
}
73 changes: 51 additions & 22 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ type Config struct {
// CommentChar is the character indicating the start of a
// comment for commands like commit and tag
CommentChar string
// RepositoryFormatVersion identifies the repository format and layout version.
RepositoryFormatVersion format.RepositoryFormatVersion
}

User struct {
Expand Down Expand Up @@ -96,6 +98,17 @@ type Config struct {
DefaultBranch string
}

Extensions struct {
// ObjectFormat specifies the hash algorithm to use. The
// acceptable values are sha1 and sha256. If not specified,
// sha1 is assumed. It is an error to specify this key unless
// core.repositoryFormatVersion is 1.
//
// This setting must not be changed after repository initialization
// (e.g. clone or init).
ObjectFormat format.ObjectFormat
}

// Remotes list of repository remotes, the key of the map is the name
// of the remote, should equal to RemoteConfig.Name.
Remotes map[string]*RemoteConfig
Expand Down Expand Up @@ -226,28 +239,31 @@ func (c *Config) Validate() error {
}

const (
remoteSection = "remote"
submoduleSection = "submodule"
branchSection = "branch"
coreSection = "core"
packSection = "pack"
userSection = "user"
authorSection = "author"
committerSection = "committer"
initSection = "init"
urlSection = "url"
fetchKey = "fetch"
urlKey = "url"
bareKey = "bare"
worktreeKey = "worktree"
commentCharKey = "commentChar"
windowKey = "window"
mergeKey = "merge"
rebaseKey = "rebase"
nameKey = "name"
emailKey = "email"
descriptionKey = "description"
defaultBranchKey = "defaultBranch"
remoteSection = "remote"
submoduleSection = "submodule"
branchSection = "branch"
coreSection = "core"
packSection = "pack"
userSection = "user"
authorSection = "author"
committerSection = "committer"
initSection = "init"
urlSection = "url"
extensionsSection = "extensions"
fetchKey = "fetch"
urlKey = "url"
bareKey = "bare"
worktreeKey = "worktree"
commentCharKey = "commentChar"
windowKey = "window"
mergeKey = "merge"
rebaseKey = "rebase"
nameKey = "name"
emailKey = "email"
descriptionKey = "description"
defaultBranchKey = "defaultBranch"
repositoryFormatVersionKey = "repositoryformatversion"
objectFormat = "objectformat"

// DefaultPackWindow holds the number of previous objects used to
// generate deltas. The value 10 is the same used by git command.
Expand Down Expand Up @@ -391,6 +407,7 @@ func (c *Config) unmarshalInit() {
// Marshal returns Config encoded as a git-config file.
func (c *Config) Marshal() ([]byte, error) {
c.marshalCore()
c.marshalExtensions()
c.marshalUser()
c.marshalPack()
c.marshalRemotes()
Expand All @@ -410,12 +427,24 @@ func (c *Config) Marshal() ([]byte, error) {
func (c *Config) marshalCore() {
s := c.Raw.Section(coreSection)
s.SetOption(bareKey, fmt.Sprintf("%t", c.Core.IsBare))
if string(c.Core.RepositoryFormatVersion) != "" {
s.SetOption(repositoryFormatVersionKey, string(c.Core.RepositoryFormatVersion))
}

if c.Core.Worktree != "" {
s.SetOption(worktreeKey, c.Core.Worktree)
}
}

func (c *Config) marshalExtensions() {
// Extensions are only supported on Version 1, therefore
// ignore them otherwise.
if c.Core.RepositoryFormatVersion == format.Version_1 {
s := c.Raw.Section(extensionsSection)
s.SetOption(objectFormat, string(c.Extensions.ObjectFormat))
}
}

func (c *Config) marshalUser() {
s := c.Raw.Section(userSection)
if c.User.Name != "" {
Expand Down
8 changes: 8 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
formatcfg "github.com/go-git/go-git/v5/plumbing/format/config"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/protocol/packp/sideband"
"github.com/go-git/go-git/v5/plumbing/transport"
Expand Down Expand Up @@ -672,3 +673,10 @@ type PlainOpenOptions struct {

// Validate validates the fields and sets the default values.
func (o *PlainOpenOptions) Validate() error { return nil }

type PlainInitOptions struct {
ObjectFormat formatcfg.ObjectFormat
}

// Validate validates the fields and sets the default values.
func (o *PlainInitOptions) Validate() error { return nil }
7 changes: 3 additions & 4 deletions plumbing/format/commitgraph/encoder.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package commitgraph

import (
"crypto"
"io"

"github.com/go-git/go-git/v5/plumbing"
Expand All @@ -17,7 +16,7 @@ type Encoder struct {

// NewEncoder returns a new stream encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
h := hash.New(crypto.SHA1)
h := hash.New(hash.CryptoType)
mw := io.MultiWriter(w, h)
return &Encoder{mw, h}
}
Expand All @@ -31,7 +30,7 @@ func (e *Encoder) Encode(idx Index) error {
hashToIndex, fanout, extraEdgesCount := e.prepare(idx, hashes)

chunkSignatures := [][]byte{oidFanoutSignature, oidLookupSignature, commitDataSignature}
chunkSizes := []uint64{4 * 256, uint64(len(hashes)) * 20, uint64(len(hashes)) * 36}
chunkSizes := []uint64{4 * 256, uint64(len(hashes)) * hash.Size, uint64(len(hashes)) * 36}
if extraEdgesCount > 0 {
chunkSignatures = append(chunkSignatures, extraEdgeListSignature)
chunkSizes = append(chunkSizes, uint64(extraEdgesCount)*4)
Expand Down Expand Up @@ -183,6 +182,6 @@ func (e *Encoder) encodeExtraEdges(extraEdges []uint32) (err error) {
}

func (e *Encoder) encodeChecksum() error {
_, err := e.Write(e.hash.Sum(nil)[:20])
_, err := e.Write(e.hash.Sum(nil)[:hash.Size])
return err
}
53 changes: 53 additions & 0 deletions plumbing/format/config/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package config

// RepositoryFormatVersion represents the repository format version,
// as per defined at:
//
// https://git-scm.com/docs/repository-version
type RepositoryFormatVersion string

const (
// Version_0 is the format defined by the initial version of git,
// including but not limited to the format of the repository
// directory, the repository configuration file, and the object
// and ref storage.
//
// Specifying the complete behavior of git is beyond the scope
// of this document.
Version_0 = "0"

// Version_1 is identical to version 0, with the following exceptions:
//
// 1. When reading the core.repositoryformatversion variable, a git
// implementation which supports version 1 MUST also read any
// configuration keys found in the extensions section of the
// configuration file.
//
// 2. If a version-1 repository specifies any extensions.* keys that
// the running git has not implemented, the operation MUST NOT proceed.
// Similarly, if the value of any known key is not understood by the
// implementation, the operation MUST NOT proceed.
//
// Note that if no extensions are specified in the config file, then
// core.repositoryformatversion SHOULD be set to 0 (setting it to 1 provides
// no benefit, and makes the repository incompatible with older
// implementations of git).
Version_1 = "1"

// DefaultRepositoryFormatVersion holds the default repository format version.
DefaultRepositoryFormatVersion = Version_0
)

// ObjectFormat defines the object format.
type ObjectFormat string

const (
// SHA1 represents the object format used for SHA1.
SHA1 ObjectFormat = "sha1"

// SHA256 represents the object format used for SHA256.
SHA256 ObjectFormat = "sha256"

// DefaultObjectFormat holds the default object format.
DefaultObjectFormat = SHA1
)
3 changes: 2 additions & 1 deletion plumbing/format/idxfile/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"io"

"github.com/go-git/go-git/v5/plumbing/hash"
"github.com/go-git/go-git/v5/utils/binary"
)

Expand All @@ -19,7 +20,7 @@ var (

const (
fanout = 256
objectIDLength = 20
objectIDLength = hash.Size
)

// Decoder reads and decodes idx files from an input stream.
Expand Down
7 changes: 3 additions & 4 deletions plumbing/format/idxfile/encoder.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package idxfile

import (
"crypto"
"io"

"github.com/go-git/go-git/v5/plumbing/hash"
Expand All @@ -16,7 +15,7 @@ type Encoder struct {

// NewEncoder returns a new stream encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
h := hash.New(crypto.SHA1)
h := hash.New(hash.CryptoType)
mw := io.MultiWriter(w, h)
return &Encoder{mw, h}
}
Expand Down Expand Up @@ -133,10 +132,10 @@ func (e *Encoder) encodeChecksums(idx *MemoryIndex) (int, error) {
return 0, err
}

copy(idx.IdxChecksum[:], e.hash.Sum(nil)[:20])
copy(idx.IdxChecksum[:], e.hash.Sum(nil)[:hash.Size])
if _, err := e.Write(idx.IdxChecksum[:]); err != nil {
return 0, err
}

return 40, nil
return hash.HexSize, nil
}
5 changes: 3 additions & 2 deletions plumbing/format/idxfile/idxfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
encbin "encoding/binary"

"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/hash"
)

const (
Expand Down Expand Up @@ -53,8 +54,8 @@ type MemoryIndex struct {
Offset32 [][]byte
CRC32 [][]byte
Offset64 []byte
PackfileChecksum [20]byte
IdxChecksum [20]byte
PackfileChecksum [hash.Size]byte
IdxChecksum [hash.Size]byte

offsetHash map[int64]plumbing.Hash
offsetHashIsFull bool
Expand Down
3 changes: 1 addition & 2 deletions plumbing/format/index/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package index
import (
"bufio"
"bytes"
"crypto"
"errors"
"io"
"io/ioutil"
Expand Down Expand Up @@ -49,7 +48,7 @@ type Decoder struct {

// NewDecoder returns a new decoder that reads from r.
func NewDecoder(r io.Reader) *Decoder {
h := hash.New(crypto.SHA1)
h := hash.New(hash.CryptoType)
return &Decoder{
r: io.TeeReader(r, h),
hash: h,
Expand Down
Loading