~gitpocket/gitpocket

4
682e30afb31ec06fa286a00dc80767711b10d5b2
mainfeed-discovery
gitpocket/backup.go -rw-r--r--2.1 KiB ViewLogBlameView rawPermalink 682e30afb3 feat(backup): bundle changed repositories 21 days ago
 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
package main

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

func backup(root, dir string, keep int) error {
	if strings.HasSuffix(dir, ".db") {
		return mustStore(root).Backup(dir)
	}
	if err := os.MkdirAll(filepath.Join(dir, "repos"), 0o750); err != nil {
		return err
	}
	snapshot := filepath.Join(dir, "gitpocket-"+time.Now().UTC().Format("2006-01-02")+".db")
	os.Remove(snapshot)
	if err := mustStore(root).Backup(snapshot); err != nil {
		return err
	}
	old, _ := filepath.Glob(filepath.Join(dir, "gitpocket-*.db"))
	for i := 0; i+keep < len(old); i++ {
		os.Remove(old[i])
	}
	var failed []string
	repos, _ := filepath.Glob(filepath.Join(root, "repos", "*", "*.git"))
	live := map[string]bool{}
	for _, repo := range repos {
		rel, _ := filepath.Rel(filepath.Join(root, "repos"), repo)
		out := filepath.Join(dir, "repos", strings.TrimSuffix(rel, ".git")+".bundle")
		live[out] = true
		if info, err := os.Stat(out); err == nil && !repoUpdated(repo).After(info.ModTime()) {
			continue
		}
		if err := bundle(repo, out); err != nil {
			failed = append(failed, rel+": "+err.Error())
		}
	}
	stale, _ := filepath.Glob(filepath.Join(dir, "repos", "*", "*.bundle"))
	for _, b := range stale {
		if !live[b] {
			os.Remove(b)
		}
	}
	if len(failed) > 0 {
		return fmt.Errorf("could not bundle %s", strings.Join(failed, "; "))
	}
	return nil
}

func bundle(repo, out string) error {
	g := Git{Dir: repo}
	if _, err := g.run(nil, "rev-parse", "--verify", "--quiet", "HEAD"); err != nil {
		if refs, _ := g.run(nil, "for-each-ref", "--count=1"); len(refs) == 0 {
			return nil
		}
	}
	if err := os.MkdirAll(filepath.Dir(out), 0o750); err != nil {
		return err
	}
	tmp := out + ".tmp"
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
	defer cancel()
	cmd := exec.CommandContext(ctx, "git", "-C", repo, "bundle", "create", "--quiet", tmp, "--all")
	cmd.Env = []string{"GIT_CONFIG_NOSYSTEM=1", "HOME=/nonexistent", "PATH=" + os.Getenv("PATH")}
	if out, err := cmd.CombinedOutput(); err != nil {
		os.Remove(tmp)
		return fmt.Errorf("%v: %s", err, strings.TrimSpace(string(out)))
	}
	return os.Rename(tmp, out)
}