Commit 3a7b390c authored by Kirill Smelkov's avatar Kirill Smelkov Committed by Kirill Smelkov

xcommit_tree: Teach it to create commit without spawning `git commit-tree ...`

Because spawning separate process per 1 commit is slow.

Libgit2 does not allow to create commits only knowing tree & parentv
sha1s, but we can create commit objects by hand pretty easily - their format is

    tree <sha1>
    parent <parent1-sha1>
    parent <parent2-sha1>
    ...
    author user <email> date +offset
    committer user <email> date +offset
    LF
    message

Time for pulling-in kirr/slapos.git

before: 2.5s
after:  0.9s

NOTE AuthorInfo is changed to inherit from git.Signature (same fields
    and semantic)

NOTE Since libgit2 default ident can fail, and does not look beyond
    user.name and user.email we do backup identity detection
    (user/hostname) - in similar way Git does - ourselves.
parent cc450765
......@@ -192,9 +192,9 @@ func obj_represent_as_commit(g *git.Repository, sha1 Sha1, obj_type string) Sha1
// all commits we do here - we do with fixed name/date, so transformation
// tag->commit is stable wrt git environment and time change
fixed := AuthorInfo{name: "Git backup", email: "git@backup.org", date: "@0 +0000"}
fixed := AuthorInfo{Name: "Git backup", Email: "git@backup.org", When: time.Unix(0, 0).UTC()}
zcommit_tree := func(tree Sha1, parents []Sha1, msg string) Sha1 {
return xcommit_tree2(tree, parents, msg, fixed, fixed)
return xcommit_tree2(g, tree, parents, msg, fixed, fixed)
}
// Tag ~> Commit*
......@@ -358,7 +358,7 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) {
if gerr != nil {
infof("# creating root commit")
// NOTE `git commit` does not work in bare repo - do commit by hand
commit := xcommit_tree(mktree_empty(), []Sha1{}, "Initialize git-backup repository")
commit := xcommit_tree(gb, mktree_empty(), []Sha1{}, "Initialize git-backup repository")
xgit("update-ref", "-m", "git-backup pull init", "HEAD", commit)
}
......@@ -499,7 +499,7 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) {
backup_tree_sha1 := xgitSha1("write-tree")
HEAD := xgitSha1("rev-parse", "HEAD")
commit_sha1 := xcommit_tree(backup_tree_sha1, append([]Sha1{HEAD}, backup_refs_parentv...),
commit_sha1 := xcommit_tree(gb, backup_tree_sha1, append([]Sha1{HEAD}, backup_refs_parentv...),
"Git-backup " + backup_time)
xgit("update-ref", "-m", "git-backup pull", "HEAD", commit_sha1, HEAD)
......
......@@ -17,7 +17,9 @@ import (
"errors"
"fmt"
"os"
"strings"
"os/user"
"sync"
"time"
git "github.com/libgit2/git2go"
)
......@@ -157,43 +159,77 @@ func mktree_empty() Sha1 {
// Reason why not use g.CreateCommit():
// - we don't want to load tree and parent objects - we only have their sha1
// `git commit-tree` -> commit_sha1, raise on error
type AuthorInfo struct {
name string
email string
date string
type AuthorInfo git.Signature
func (ai *AuthorInfo) String() string {
_, toffset := ai.When.Zone()
// offset: Git wants in minutes, .Zone() gives in seconds
return fmt.Sprintf("%s <%s> %d %+05d", ai.Name, ai.Email, ai.When.Unix(), toffset / 60)
}
func xcommit_tree2(tree Sha1, parents []Sha1, msg string, author AuthorInfo, committer AuthorInfo) Sha1 {
argv := []string{"commit-tree", tree.String()}
for _, p := range parents {
argv = append(argv, "-p", p.String())
var (
defaultIdent AuthorInfo // default ident without date
defaultIdentOnce sync.Once
)
func getDefaultIdent(g *git.Repository) AuthorInfo {
sig, err := g.DefaultSignature()
if err == nil {
return AuthorInfo(*sig)
}
// env []string -> {}
env := map[string]string{}
for _, e := range os.Environ() {
i := strings.Index(e, "=")
if i == -1 {
panic(fmt.Errorf("E: env variable format invalid: %q", e))
// libgit2 failed for some reason (i.e. user.name config not set). Let's cook ident ourselves
defaultIdentOnce.Do(func() {
var username, name string
u, _ := user.Current()
if u != nil {
username = u.Username
name = u.Name
} else {
username = "?"
name = "?"
}
k, v := e[:i], e[i+1:]
if _, dup := env[k]; dup {
panic(fmt.Errorf("E: env has duplicate entry for %q", k))
// XXX it is better to get hostname as fqdn
hostname, _ := os.Hostname()
if hostname == "" {
hostname = "?"
}
env[k] = v
defaultIdent.Name = name
defaultIdent.Email = fmt.Sprintf("%s@%s", username, hostname)
})
ident := defaultIdent
ident.When = time.Now()
return ident
}
// `git commit-tree` -> commit_sha1, raise on error
func xcommit_tree2(g *git.Repository, tree Sha1, parents []Sha1, msg string, author AuthorInfo, committer AuthorInfo) Sha1 {
ident := getDefaultIdent(g)
if author.Name == "" { author.Name = ident.Name }
if author.Email == "" { author.Email = ident.Email }
if author.When.IsZero() { author.When = ident.When }
if committer.Name == "" { committer.Name = ident.Name }
if committer.Email == "" { committer.Email = ident.Email }
if committer.When.IsZero() { committer.When = ident.When }
commit := fmt.Sprintf("tree %s\n", tree)
for _, p := range parents {
commit += fmt.Sprintf("parent %s\n", p)
}
commit += fmt.Sprintf("author %s\n", &author)
commit += fmt.Sprintf("committer %s\n", &committer)
commit += fmt.Sprintf("\n%s", msg)
if author.name != "" { env["GIT_AUTHOR_NAME"] = author.name }
if author.email != "" { env["GIT_AUTHOR_EMAIL"] = author.email }
if author.date != "" { env["GIT_AUTHOR_DATE"] = author.date }
if committer.name != "" { env["GIT_COMMITTER_NAME"] = committer.name }
if committer.email != "" { env["GIT_COMMITTER_EMAIL"] = committer.email }
if committer.date != "" { env["GIT_COMMITTER_DATE"] = committer.date }
sha1, err := WriteObject(g, Bytes(commit), git.ObjectCommit)
raiseif(err)
return xgit2Sha1(argv, RunWith{stdin: msg, env: env})
return sha1
}
func xcommit_tree(tree Sha1, parents []Sha1, msg string) Sha1 {
return xcommit_tree2(tree, parents, msg, AuthorInfo{}, AuthorInfo{})
func xcommit_tree(g *git.Repository, tree Sha1, parents []Sha1, msg string) Sha1 {
return xcommit_tree2(g, tree, parents, msg, AuthorInfo{}, AuthorInfo{})
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment