summaryrefslogtreecommitdiff
path: root/fi-prune-empty2/main.go
blob: f09af648d5d4738bbbf1725fcf4c8db0198847cc (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
122
123
124
125
126
// Copyright 2019-2021  Luke Shumaker <lukeshu@parabola.nu>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// Command fi-prune-empty is a filter that prunes a fast-import
// stream, removing empty commits and empty merges.
package main

import (
	"fmt"
	"os"

	"git.lukeshu.com/go/libfastimport"
	"github.com/spf13/pflag"

	"git.parabola.nu/~lukeshu/fastimport-go-utils/fiutil"
)

type Args struct {
	help   bool
	srcdir string

	pruneEmpty       PruneArg
	pruneFastForward PruneArg
	existingReplace  ExistingReplaceArg
	newReplace       NewReplaceArg
}

func main() {
	var args Args
	argparser := pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError)
	argparser.SortFlags = false

	argparser.BoolVarP(&args.help, "help", "h", false, "Show this message")
	argparser.StringVar(&args.srcdir, "srcdir", ".", "Path to the source git repository; this is required for '--prune-empty=auto' and '--prune-fastforward=auto'")

	args.pruneEmpty = PruneAuto
	argparser.Var(&args.pruneEmpty, "prune-empty", "Whether to prune empty commits; 'auto' means only prune commits which have become empty but are not empty in the --srcdir repo with GIT_NO_REPLACE_OBJECTS")

	args.pruneFastForward = PruneAuto
	argparser.Var(&args.pruneFastForward, "prune-fastforward", "Whether to prune merge commits that could be fast-forward merges; 'auto' means only prune merges which become could-be-fast-forward but are not could-be-fast-forward in the --srcdir repo with GIT_NO_REPLACE_OBJECTS")

	args.existingReplace = ExistingReplaceDeleteOrUpdate
	argparser.Var(&args.existingReplace, "existing-replace", "What to do with existing 'refs/replace/*' refs")

	args.newReplace = NewReplaceAdd
	argparser.Var(&args.newReplace, "new-replace", "Whether to create new 'refs/replace/*' refs")

	err := argparser.Parse(os.Args[1:])
	if args.help {
		fmt.Printf("Usage: git fast-export --full-tree | %s [OPTIONS] | git fast-import\n", os.Args[0])
		fmt.Println("A filter that prunes a fast-import stream, removing empty commits and empty merges")
		fmt.Println()
		fmt.Println("Requirements for the input stream:")
		fmt.Println(" - Every commit must have a 'mark' (this is the default from `git fast-export`)")
		fmt.Println(" - Every commit must have an 'original-oid' (eg: `git fast-export --show-original-ids`)")
		fmt.Println("   if any of (note that several of these are the default):")
		fmt.Println("     --prune-empty=auto")
		fmt.Println("     --prune-fastforward=auto")
		fmt.Println("     --existing-replace=keep-or-update")
		fmt.Println("     --new-replace=add")
		fmt.Println()
		fmt.Println("OPTIONS:")
		argparser.PrintDefaults()
		os.Exit(0)
	}
	if err != nil {
		fiutil.ErrUsage(err.Error())
	}
	if err := fiutil.NArgsExact(argparser, 0); err != nil {
		fiutil.ErrUsage(err.Error())
	}

	frontend := libfastimport.NewFrontend(os.Stdin, os.Stdin, nil)
	backend := libfastimport.NewBackend(os.Stdout, os.Stdout, nil)

	filter := NewHandler(backend, NewDriver(args, backend))
	if err := fiutil.RunHandler(frontend, filter); err != nil {
		fmt.Fprintf(os.Stderr, "%s: error: %v\n", os.Args[0], err)
		os.Exit(1)
	}
}

type driver struct {
	*Pruner
	*Replacer
}

func (d driver) ProcessCommit(commit Commit) (*Commit, error) {
	d.Replacer.ProcessCommit(commit)
	return d.Pruner.ProcessCommit(commit)
}

func (d driver) GotMark(mark Mark, hash Hash) bool {
	d.Replacer.GotMark(mark, hash)
	return d.Pruner.GotMark(mark, hash)
}

func NewDriver(args Args, backend *libfastimport.Backend) Driver {
	pruner := NewPruner(
		args.pruneEmpty,
		args.pruneFastForward,
		args.srcdir,
		backend)
	replacer := NewReplacer(
		args.existingReplace,
		args.newReplace,
		pruner,
		backend)

	return driver{
		Pruner:   pruner,
		Replacer: replacer,
	}
}