commands/migrate: teach --above
This commit is contained in:
parent
bea1d63f68
commit
f3e0af6b5d
@ -208,6 +208,7 @@ func getHistoryRewriter(cmd *cobra.Command, db *odb.ObjectDatabase) *githistory.
|
|||||||
func init() {
|
func init() {
|
||||||
info := NewCommand("info", migrateInfoCommand)
|
info := NewCommand("info", migrateInfoCommand)
|
||||||
info.Flags().IntVar(&migrateInfoTopN, "top", 5, "--top=<n>")
|
info.Flags().IntVar(&migrateInfoTopN, "top", 5, "--top=<n>")
|
||||||
|
info.Flags().StringVar(&migrateInfoAboveFmt, "above", "0 B", "--above=<n>")
|
||||||
|
|
||||||
RegisterCommand("migrate", nil, func(cmd *cobra.Command) {
|
RegisterCommand("migrate", nil, func(cmd *cobra.Command) {
|
||||||
// Adding flags directly to cmd.Flags() doesn't apply those
|
// Adding flags directly to cmd.Flags() doesn't apply those
|
||||||
|
@ -8,8 +8,10 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/git-lfs/git-lfs/errors"
|
||||||
"github.com/git-lfs/git-lfs/git/odb"
|
"github.com/git-lfs/git-lfs/git/odb"
|
||||||
"github.com/git-lfs/git-lfs/tools"
|
"github.com/git-lfs/git-lfs/tools"
|
||||||
|
"github.com/git-lfs/git-lfs/tools/humanize"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,15 +19,45 @@ var (
|
|||||||
// migrateInfoTopN is a flag given to the git-lfs-migrate(1) subcommand
|
// migrateInfoTopN is a flag given to the git-lfs-migrate(1) subcommand
|
||||||
// 'info' which specifies how many info entries to show by default.
|
// 'info' which specifies how many info entries to show by default.
|
||||||
migrateInfoTopN int
|
migrateInfoTopN int
|
||||||
|
|
||||||
|
// migrateInfoAboveFmt is a flag given to the git-lfs-migrate(1)
|
||||||
|
// subcommand 'info' specifying a human-readable string threshold of
|
||||||
|
// filesize before entries are counted.
|
||||||
|
migrateInfoAboveFmt string
|
||||||
|
// migrateInfoAbove is the number of bytes parsed from the above
|
||||||
|
// migrateInfoAboveFmt flag.
|
||||||
|
migrateInfoAbove uint64
|
||||||
)
|
)
|
||||||
|
|
||||||
func migrateInfoCommand(cmd *cobra.Command, args []string) {
|
func migrateInfoCommand(cmd *cobra.Command, args []string) {
|
||||||
exts := make(map[string]int64)
|
exts := make(map[string]*MigrateInfoEntry)
|
||||||
|
|
||||||
|
above, err := humanize.ParseBytes(migrateInfoAboveFmt)
|
||||||
|
if err != nil {
|
||||||
|
ExitWithError(errors.Wrap(err, "cannot parse --above=<n>"))
|
||||||
|
}
|
||||||
|
|
||||||
|
migrateInfoAbove = above
|
||||||
|
|
||||||
migrate(cmd, args, func(path string, b *odb.Blob) (*odb.Blob, error) {
|
migrate(cmd, args, func(path string, b *odb.Blob) (*odb.Blob, error) {
|
||||||
ext := fmt.Sprintf("*%s", filepath.Ext(path))
|
ext := fmt.Sprintf("*%s", filepath.Ext(path))
|
||||||
|
|
||||||
if len(ext) > 1 {
|
if len(ext) > 1 {
|
||||||
exts[ext] = exts[ext] + b.Size
|
entry := exts[ext]
|
||||||
|
if entry == nil {
|
||||||
|
entry = &MigrateInfoEntry{Qualifier: ext}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Total++
|
||||||
|
entry.BytesTotal += b.Size
|
||||||
|
|
||||||
|
if b.Size > int64(migrateInfoAbove) {
|
||||||
|
entry.TotalAbove++
|
||||||
|
entry.BytesAbove += b.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(@ttaylorr): needed?
|
||||||
|
exts[ext] = entry
|
||||||
}
|
}
|
||||||
|
|
||||||
return b, nil
|
return b, nil
|
||||||
@ -46,19 +78,19 @@ func migrateInfoCommand(cmd *cobra.Command, args []string) {
|
|||||||
type MigrateInfoEntry struct {
|
type MigrateInfoEntry struct {
|
||||||
// Qualifier is the filepath's extension.
|
// Qualifier is the filepath's extension.
|
||||||
Qualifier string
|
Qualifier string
|
||||||
// Size is the total size in bytes taken by that filepath's extension.
|
|
||||||
Size int64
|
BytesAbove int64
|
||||||
|
TotalAbove int64
|
||||||
|
BytesTotal int64
|
||||||
|
Total int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// MapToEntries creates a set of `*MigrateInfoEntry`'s for a given map of
|
// MapToEntries creates a set of `*MigrateInfoEntry`'s for a given map of
|
||||||
// filepath extensions to file size in bytes.
|
// filepath extensions to file size in bytes.
|
||||||
func MapToEntries(exts map[string]int64) []*MigrateInfoEntry {
|
func MapToEntries(exts map[string]*MigrateInfoEntry) []*MigrateInfoEntry {
|
||||||
entries := make([]*MigrateInfoEntry, 0, len(exts))
|
entries := make([]*MigrateInfoEntry, 0, len(exts))
|
||||||
for qualifier, size := range exts {
|
for _, entry := range exts {
|
||||||
entries = append(entries, &MigrateInfoEntry{
|
entries = append(entries, entry)
|
||||||
Qualifier: qualifier,
|
|
||||||
Size: size,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
@ -73,39 +105,57 @@ func (e EntriesBySize) Len() int { return len(e) }
|
|||||||
|
|
||||||
// Less returns the whether or not the MigrateInfoEntry given at `i` takes up
|
// Less returns the whether or not the MigrateInfoEntry given at `i` takes up
|
||||||
// less total size than the MigrateInfoEntry given at `j`.
|
// less total size than the MigrateInfoEntry given at `j`.
|
||||||
func (e EntriesBySize) Less(i, j int) bool { return e[i].Size < e[j].Size }
|
func (e EntriesBySize) Less(i, j int) bool { return e[i].BytesAbove < e[j].BytesAbove }
|
||||||
|
|
||||||
// Swap swaps the entries given at i, j.
|
// Swap swaps the entries given at i, j.
|
||||||
func (e EntriesBySize) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
func (e EntriesBySize) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||||
|
|
||||||
// LongestQualifier returns the string length of the longest qualifier in the
|
|
||||||
// set.
|
|
||||||
func (e EntriesBySize) LongestQualifier() int {
|
|
||||||
var longest int
|
|
||||||
for _, entry := range e {
|
|
||||||
longest = tools.MaxInt(longest, len(entry.Qualifier))
|
|
||||||
}
|
|
||||||
|
|
||||||
return longest
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print formats the `*MigrateInfoEntry`'s in the set and prints them to the
|
// Print formats the `*MigrateInfoEntry`'s in the set and prints them to the
|
||||||
// given io.Writer, "to", returning "n" the number of bytes written, and any
|
// given io.Writer, "to", returning "n" the number of bytes written, and any
|
||||||
// error, if one occurred.
|
// error, if one occurred.
|
||||||
func (e EntriesBySize) Print(to io.Writer) (int, error) {
|
func (e EntriesBySize) Print(to io.Writer) (int, error) {
|
||||||
output := make([]string, 0, len(e))
|
extensions := make([]string, 0, len(e))
|
||||||
|
|
||||||
longest := e.LongestQualifier()
|
|
||||||
|
|
||||||
for _, entry := range e {
|
for _, entry := range e {
|
||||||
offset := longest - len(entry.Qualifier)
|
extensions = append(extensions, entry.Qualifier)
|
||||||
padding := strings.Repeat(" ", tools.MaxInt(0, offset))
|
}
|
||||||
bytes := humanizeBytes(entry.Size)
|
extensions = tools.Ljust(extensions)
|
||||||
|
|
||||||
line := fmt.Sprintf("%s%s %s", entry.Qualifier, padding, bytes)
|
files := make([]string, 0, len(e))
|
||||||
|
for _, entry := range e {
|
||||||
|
bytes := humanize.FormatBytes(uint64(entry.BytesAbove))
|
||||||
|
above := entry.TotalAbove
|
||||||
|
total := entry.Total
|
||||||
|
|
||||||
|
file := fmt.Sprintf("%s, %d/%d files(s)",
|
||||||
|
bytes, above, total)
|
||||||
|
|
||||||
|
files = append(files, file)
|
||||||
|
}
|
||||||
|
files = tools.Rjust(files)
|
||||||
|
|
||||||
|
percentages := make([]string, 0, len(e))
|
||||||
|
for _, entry := range e {
|
||||||
|
percentAbove := 100 * (float64(entry.TotalAbove) / float64(entry.Total))
|
||||||
|
|
||||||
|
percentage := fmt.Sprintf("%.0f%%", percentAbove)
|
||||||
|
|
||||||
|
percentages = append(percentages, percentage)
|
||||||
|
}
|
||||||
|
percentages = tools.Rjust(percentages)
|
||||||
|
|
||||||
|
output := make([]string, 0, len(e))
|
||||||
|
for i := 0; i < len(e); i++ {
|
||||||
|
extension := extensions[i]
|
||||||
|
fileCount := files[i]
|
||||||
|
percentage := percentages[i]
|
||||||
|
|
||||||
|
line := strings.Join([]string{extension, fileCount, percentage}, "\t")
|
||||||
|
|
||||||
output = append(output, line)
|
output = append(output, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header := fmt.Sprintf("Files above %s:", humanize.FormatBytes(migrateInfoAbove))
|
||||||
|
output = append([]string{header}, output...)
|
||||||
|
|
||||||
return fmt.Fprintln(to, strings.Join(output, "\n"))
|
return fmt.Fprintln(to, strings.Join(output, "\n"))
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,9 @@ begin_test "migrate info (default branch)"
|
|||||||
original_head="$(git rev-parse HEAD)"
|
original_head="$(git rev-parse HEAD)"
|
||||||
|
|
||||||
diff -u <(git lfs migrate info 2>&1) <(cat <<-EOF
|
diff -u <(git lfs migrate info 2>&1) <(cat <<-EOF
|
||||||
*.md 140 B
|
Files above 0 B:
|
||||||
*.txt 120 B
|
*.md 140 B, 1/1 files(s) 100%
|
||||||
|
*.txt 120 B, 1/1 files(s) 100%
|
||||||
EOF)
|
EOF)
|
||||||
|
|
||||||
migrated_head="$(git rev-parse HEAD)"
|
migrated_head="$(git rev-parse HEAD)"
|
||||||
@ -32,8 +33,9 @@ begin_test "migrate info (given branch)"
|
|||||||
original_feature="$(git rev-parse refs/heads/my-feature)"
|
original_feature="$(git rev-parse refs/heads/my-feature)"
|
||||||
|
|
||||||
diff -u <(git lfs migrate info my-feature 2>&1) <(cat <<-EOF
|
diff -u <(git lfs migrate info my-feature 2>&1) <(cat <<-EOF
|
||||||
*.md 170 B
|
Files above 0 B:
|
||||||
*.txt 120 B
|
*.md 170 B, 2/2 files(s) 100%
|
||||||
|
*.txt 120 B, 1/1 files(s) 100%
|
||||||
EOF)
|
EOF)
|
||||||
|
|
||||||
migrated_master="$(git rev-parse refs/heads/master)"
|
migrated_master="$(git rev-parse refs/heads/master)"
|
||||||
@ -53,7 +55,8 @@ begin_test "migrate info (default branch with filter)"
|
|||||||
original_head="$(git rev-parse HEAD)"
|
original_head="$(git rev-parse HEAD)"
|
||||||
|
|
||||||
diff -u <(git lfs migrate info --include "*.md" 2>&1) <(cat <<-EOF
|
diff -u <(git lfs migrate info --include "*.md" 2>&1) <(cat <<-EOF
|
||||||
*.md 140 B
|
Files above 0 B:
|
||||||
|
*.md 140 B, 1/1 files(s) 100%
|
||||||
EOF)
|
EOF)
|
||||||
|
|
||||||
migrated_head="$(git rev-parse HEAD)"
|
migrated_head="$(git rev-parse HEAD)"
|
||||||
@ -72,7 +75,8 @@ begin_test "migrate info (given branch with filter)"
|
|||||||
original_feature="$(git rev-parse refs/heads/my-feature)"
|
original_feature="$(git rev-parse refs/heads/my-feature)"
|
||||||
|
|
||||||
diff -u <(git lfs migrate info --include "*.md" my-feature 2>&1) <(cat <<-EOF
|
diff -u <(git lfs migrate info --include "*.md" my-feature 2>&1) <(cat <<-EOF
|
||||||
*.md 170 B
|
Files above 0 B:
|
||||||
|
*.md 170 B, 2/2 files(s) 100%
|
||||||
EOF)
|
EOF)
|
||||||
|
|
||||||
migrated_master="$(git rev-parse refs/heads/master)"
|
migrated_master="$(git rev-parse refs/heads/master)"
|
||||||
@ -95,8 +99,9 @@ begin_test "migrate info (default branch, exclude remote refs)"
|
|||||||
original_master="$(git rev-parse refs/heads/master)"
|
original_master="$(git rev-parse refs/heads/master)"
|
||||||
|
|
||||||
diff -u <(git lfs migrate info 2>&1) <(cat <<-EOF
|
diff -u <(git lfs migrate info 2>&1) <(cat <<-EOF
|
||||||
*.md 50 B
|
Files above 0 B:
|
||||||
*.txt 30 B
|
*.md 50 B, 1/1 files(s) 100%
|
||||||
|
*.txt 30 B, 1/1 files(s) 100%
|
||||||
EOF)
|
EOF)
|
||||||
|
|
||||||
migrated_remote="$(git rev-parse refs/remotes/origin/master)"
|
migrated_remote="$(git rev-parse refs/remotes/origin/master)"
|
||||||
@ -118,8 +123,9 @@ begin_test "migrate info (given branch, exclude remote refs)"
|
|||||||
original_feature="$(git rev-parse refs/heads/my-feature)"
|
original_feature="$(git rev-parse refs/heads/my-feature)"
|
||||||
|
|
||||||
diff -u <(git lfs migrate info my-feature 2>&1) <(cat <<-EOF
|
diff -u <(git lfs migrate info my-feature 2>&1) <(cat <<-EOF
|
||||||
*.md 52 B
|
Files above 0 B:
|
||||||
*.txt 50 B
|
*.md 52 B, 2/2 files(s) 100%
|
||||||
|
*.txt 50 B, 2/2 files(s) 100%
|
||||||
EOF)
|
EOF)
|
||||||
|
|
||||||
migrated_remote="$(git rev-parse refs/remotes/origin/master)"
|
migrated_remote="$(git rev-parse refs/remotes/origin/master)"
|
||||||
@ -144,8 +150,9 @@ begin_test "migrate info (include/exclude ref)"
|
|||||||
diff -u <(git lfs migrate info \
|
diff -u <(git lfs migrate info \
|
||||||
--include-ref=refs/heads/my-feature \
|
--include-ref=refs/heads/my-feature \
|
||||||
--exclude-ref=refs/heads/master 2>&1) <(cat <<-EOF
|
--exclude-ref=refs/heads/master 2>&1) <(cat <<-EOF
|
||||||
*.md 31 B
|
Files above 0 B:
|
||||||
*.txt 30 B
|
*.md 31 B, 1/1 files(s) 100%
|
||||||
|
*.txt 30 B, 1/1 files(s) 100%
|
||||||
EOF)
|
EOF)
|
||||||
|
|
||||||
migrated_master="$(git rev-parse refs/heads/master)"
|
migrated_master="$(git rev-parse refs/heads/master)"
|
||||||
@ -169,7 +176,8 @@ begin_test "migrate info (include/exclude ref with filter)"
|
|||||||
--include="*.txt" \
|
--include="*.txt" \
|
||||||
--include-ref=refs/heads/my-feature \
|
--include-ref=refs/heads/my-feature \
|
||||||
--exclude-ref=refs/heads/master 2>&1) <(cat <<-EOF
|
--exclude-ref=refs/heads/master 2>&1) <(cat <<-EOF
|
||||||
*.txt 30 B
|
Files above 0 B:
|
||||||
|
*.txt 30 B, 1/1 files(s) 100%
|
||||||
EOF)
|
EOF)
|
||||||
|
|
||||||
migrated_master="$(git rev-parse refs/heads/master)"
|
migrated_master="$(git rev-parse refs/heads/master)"
|
||||||
@ -179,3 +187,43 @@ begin_test "migrate info (include/exclude ref with filter)"
|
|||||||
assert_ref_unmoved "refs/heads/my-feature" "$original_feature" "$migrated_feature"
|
assert_ref_unmoved "refs/heads/my-feature" "$original_feature" "$migrated_feature"
|
||||||
)
|
)
|
||||||
end_test
|
end_test
|
||||||
|
|
||||||
|
begin_test "migrate info (above threshold)"
|
||||||
|
(
|
||||||
|
set -e
|
||||||
|
|
||||||
|
setup_multiple_local_branches
|
||||||
|
|
||||||
|
original_head="$(git rev-parse HEAD)"
|
||||||
|
|
||||||
|
diff -u <(git lfs migrate info --above=130B 2>&1) <(cat <<-EOF
|
||||||
|
Files above 130 B:
|
||||||
|
*.md 140 B, 1/1 files(s) 100%
|
||||||
|
*.txt 0 B, 0/1 files(s) 0%
|
||||||
|
EOF)
|
||||||
|
|
||||||
|
migrated_head="$(git rev-parse HEAD)"
|
||||||
|
|
||||||
|
assert_ref_unmoved "HEAD" "$original_head" "$migrated_head"
|
||||||
|
)
|
||||||
|
end_test
|
||||||
|
|
||||||
|
begin_test "migrate info (above threshold, top)"
|
||||||
|
(
|
||||||
|
set -e
|
||||||
|
|
||||||
|
setup_multiple_local_branches
|
||||||
|
|
||||||
|
original_head="$(git rev-parse HEAD)"
|
||||||
|
|
||||||
|
diff -u <(git lfs migrate info --above=130B --top=1 2>&1) <(cat <<-EOF
|
||||||
|
Files above 130 B:
|
||||||
|
*.md 140 B, 1/1 files(s) 100%
|
||||||
|
EOF)
|
||||||
|
|
||||||
|
migrated_head="$(git rev-parse HEAD)"
|
||||||
|
|
||||||
|
assert_ref_unmoved "HEAD" "$original_head" "$migrated_head"
|
||||||
|
)
|
||||||
|
end_test
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user