github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/commands/command_migrate_info.go (about) 1 package commands 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "path/filepath" 8 "sort" 9 "strings" 10 11 "github.com/git-lfs/git-lfs/errors" 12 "github.com/git-lfs/git-lfs/git/githistory" 13 "github.com/git-lfs/git-lfs/git/odb" 14 "github.com/git-lfs/git-lfs/tasklog" 15 "github.com/git-lfs/git-lfs/tools" 16 "github.com/git-lfs/git-lfs/tools/humanize" 17 "github.com/spf13/cobra" 18 ) 19 20 var ( 21 // migrateInfoTopN is a flag given to the git-lfs-migrate(1) subcommand 22 // 'info' which specifies how many info entries to show by default. 23 migrateInfoTopN int 24 25 // migrateInfoAboveFmt is a flag given to the git-lfs-migrate(1) 26 // subcommand 'info' specifying a human-readable string threshold of 27 // filesize before entries are counted. 28 migrateInfoAboveFmt string 29 // migrateInfoAbove is the number of bytes parsed from the above 30 // migrateInfoAboveFmt flag. 31 migrateInfoAbove uint64 32 33 // migrateInfoUnitFmt is a flag given to the git-lfs-migrate(1) 34 // subcommand 'info' specifying a human-readable string of units with 35 // which to display the number of bytes. 36 migrateInfoUnitFmt string 37 // migrateInfoUnit is the number of bytes in the unit given as 38 // migrateInfoUnitFmt. 39 migrateInfoUnit uint64 40 ) 41 42 func migrateInfoCommand(cmd *cobra.Command, args []string) { 43 l := tasklog.NewLogger(os.Stderr) 44 45 db, err := getObjectDatabase() 46 if err != nil { 47 ExitWithError(err) 48 } 49 defer db.Close() 50 51 rewriter := getHistoryRewriter(cmd, db, l) 52 53 exts := make(map[string]*MigrateInfoEntry) 54 55 above, err := humanize.ParseBytes(migrateInfoAboveFmt) 56 if err != nil { 57 ExitWithError(errors.Wrap(err, "cannot parse --above=<n>")) 58 } 59 60 if u := cmd.Flag("unit"); u.Changed { 61 unit, err := humanize.ParseByteUnit(u.Value.String()) 62 if err != nil { 63 ExitWithError(errors.Wrap(err, "cannot parse --unit=<unit>")) 64 } 65 66 migrateInfoUnit = unit 67 } 68 69 migrateInfoAbove = above 70 71 migrate(args, rewriter, l, &githistory.RewriteOptions{ 72 BlobFn: func(path string, b *odb.Blob) (*odb.Blob, error) { 73 ext := fmt.Sprintf("*%s", filepath.Ext(path)) 74 75 if len(ext) > 1 { 76 entry := exts[ext] 77 if entry == nil { 78 entry = &MigrateInfoEntry{Qualifier: ext} 79 } 80 81 entry.Total++ 82 entry.BytesTotal += b.Size 83 84 if b.Size > int64(migrateInfoAbove) { 85 entry.TotalAbove++ 86 entry.BytesAbove += b.Size 87 } 88 89 exts[ext] = entry 90 } 91 92 return b, nil 93 }, 94 }) 95 l.Close() 96 97 entries := EntriesBySize(MapToEntries(exts)) 98 entries = removeEmptyEntries(entries) 99 sort.Sort(sort.Reverse(entries)) 100 101 migrateInfoTopN = tools.ClampInt(migrateInfoTopN, len(entries), 0) 102 103 entries = entries[:tools.MaxInt(0, migrateInfoTopN)] 104 105 entries.Print(os.Stdout) 106 } 107 108 // MigrateInfoEntry represents a tuple of filetype to bytes and entry count 109 // above and below a threshold. 110 type MigrateInfoEntry struct { 111 // Qualifier is the filepath's extension. 112 Qualifier string 113 114 // BytesAbove is total size of all files above a given threshold. 115 BytesAbove int64 116 // TotalAbove is the count of all files above a given size threshold. 117 TotalAbove int64 118 // BytesTotal is the number of bytes of all files 119 BytesTotal int64 120 // Total is the count of all files. 121 Total int64 122 } 123 124 // MapToEntries creates a set of `*MigrateInfoEntry`'s for a given map of 125 // filepath extensions to file size in bytes. 126 func MapToEntries(exts map[string]*MigrateInfoEntry) []*MigrateInfoEntry { 127 entries := make([]*MigrateInfoEntry, 0, len(exts)) 128 for _, entry := range exts { 129 entries = append(entries, entry) 130 } 131 132 return entries 133 } 134 135 // removeEmptyEntries removes `*MigrateInfoEntry`'s for which no matching file 136 // is above the given threshold "--above". 137 func removeEmptyEntries(entries []*MigrateInfoEntry) []*MigrateInfoEntry { 138 nz := make([]*MigrateInfoEntry, 0, len(entries)) 139 for _, e := range entries { 140 if e.TotalAbove > 0 { 141 nz = append(nz, e) 142 } 143 } 144 145 return nz 146 } 147 148 // EntriesBySize is an implementation of sort.Interface that sorts a set of 149 // `*MigrateInfoEntry`'s 150 type EntriesBySize []*MigrateInfoEntry 151 152 // Len returns the total length of the set of `*MigrateInfoEntry`'s. 153 func (e EntriesBySize) Len() int { return len(e) } 154 155 // Less returns the whether or not the MigrateInfoEntry given at `i` takes up 156 // less total size than the MigrateInfoEntry given at `j`. 157 func (e EntriesBySize) Less(i, j int) bool { return e[i].BytesAbove < e[j].BytesAbove } 158 159 // Swap swaps the entries given at i, j. 160 func (e EntriesBySize) Swap(i, j int) { e[i], e[j] = e[j], e[i] } 161 162 // Print formats the `*MigrateInfoEntry`'s in the set and prints them to the 163 // given io.Writer, "to", returning "n" the number of bytes written, and any 164 // error, if one occurred. 165 func (e EntriesBySize) Print(to io.Writer) (int, error) { 166 if len(e) == 0 { 167 return 0, nil 168 } 169 170 extensions := make([]string, 0, len(e)) 171 sizes := make([]string, 0, len(e)) 172 stats := make([]string, 0, len(e)) 173 percentages := make([]string, 0, len(e)) 174 175 for _, entry := range e { 176 bytesAbove := uint64(entry.BytesAbove) 177 above := entry.TotalAbove 178 total := entry.Total 179 percentAbove := 100 * (float64(above) / float64(total)) 180 181 var size string 182 if migrateInfoUnit > 0 { 183 size = humanize.FormatBytesUnit(bytesAbove, migrateInfoUnit) 184 } else { 185 size = humanize.FormatBytes(bytesAbove) 186 } 187 188 stat := fmt.Sprintf("%d/%d files(s)", 189 above, total) 190 191 percentage := fmt.Sprintf("%.0f%%", percentAbove) 192 193 extensions = append(extensions, entry.Qualifier) 194 sizes = append(sizes, size) 195 stats = append(stats, stat) 196 percentages = append(percentages, percentage) 197 } 198 199 extensions = tools.Ljust(extensions) 200 sizes = tools.Ljust(sizes) 201 stats = tools.Rjust(stats) 202 percentages = tools.Rjust(percentages) 203 204 output := make([]string, 0, len(e)) 205 for i := 0; i < len(e); i++ { 206 extension := extensions[i] 207 size := sizes[i] 208 stat := stats[i] 209 percentage := percentages[i] 210 211 line := strings.Join([]string{extension, size, stat, percentage}, "\t") 212 213 output = append(output, line) 214 } 215 216 return fmt.Fprintln(to, strings.Join(output, "\n")) 217 }