github.com/rcowham/go-libgitfastimport@v0.1.6/types.go (about) 1 // Copyright (C) 2017-2018, 2020-2021 Luke Shumaker <lukeshu@lukeshu.com> 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as published by 5 // the Free Software Foundation, either version 3 of the License, or 6 // (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <https://www.gnu.org/licenses/>. 15 16 package libfastimport 17 18 import ( 19 "fmt" 20 "strconv" 21 "strings" 22 "time" 23 24 "github.com/pkg/errors" 25 ) 26 27 // Ident is a tuple of a commiter's (or author's) name, email, and a 28 // timestamp with timezone. 29 // 30 // BUG(lukeshu): Ident (and ParseIdent) only supports the 31 // "raw"/"raw-permissive" date format (not "rfc2822" or "now") 32 type Ident struct { 33 Name string 34 Email string 35 Time time.Time 36 } 37 38 func (ut Ident) String() string { 39 if ut.Name == "" { 40 return fmt.Sprintf("<%s> %d %s", 41 ut.Email, 42 ut.Time.Unix(), 43 ut.Time.Format("-0700")) 44 } else { 45 return fmt.Sprintf("%s <%s> %d %s", 46 ut.Name, 47 ut.Email, 48 ut.Time.Unix(), 49 ut.Time.Format("-0700")) 50 } 51 } 52 53 // ParseIdent parses a string containing an Ident. 54 // 55 // The format of this string is 56 // 57 // <name> SP LT <email> GT SP <time> SP <offutc> 58 // 59 // Where <name> may contain a space, but not "<" or ">"; <time> is an 60 // integer number of seconds since the UNIX epoch (UTC); <offutc> is 61 // positive or negative 4-digit offset from UTC (for example, EST 62 // would be "-0500"). 63 func ParseIdent(str string) (Ident, error) { 64 ret := Ident{} 65 lt := strings.IndexAny(str, "<>") 66 if lt < 0 || str[lt] != '<' { 67 return ret, errors.Errorf("Missing < in ident string: %q", str) 68 } 69 if lt > 0 { 70 if str[lt-1] != ' ' { 71 return ret, errors.Errorf("Missing space before < in ident string: %q", str) 72 } 73 ret.Name = str[:lt-1] 74 } 75 gt := lt + 1 + strings.IndexAny(str[lt+1:], "<>") 76 if gt < lt+1 || str[gt] != '>' { 77 return ret, errors.Errorf("Missing > in ident string: %q", str) 78 } 79 if str[gt+1] != ' ' { 80 return ret, errors.Errorf("Missing space after > in ident string: %q", str) 81 } 82 ret.Email = str[lt+1 : gt] 83 84 strWhen := str[gt+2:] 85 sp := strings.IndexByte(strWhen, ' ') 86 if sp < 0 { 87 return ret, errors.Errorf("missing time zone in when: %q", str) 88 } 89 sec, err := strconv.ParseInt(strWhen[:sp], 10, 64) 90 if err != nil { 91 return ret, err 92 } 93 tzt, err := time.Parse("-0700", strWhen[sp+1:]) 94 if err != nil { 95 return ret, err 96 } 97 ret.Time = time.Unix(sec, 0).In(tzt.Location()) 98 99 return ret, nil 100 } 101 102 // Mode is a file mode as seen by git. 103 type Mode uint32 // 18 bits 104 105 const ( 106 ModeFil = Mode(0100644) // A regular file 107 ModeExe = Mode(0100755) // An executable file 108 ModeSym = Mode(0120000) // A symbolic link 109 ModeGit = Mode(0160000) // A nested git repository (e.g. submodule) 110 ModeDir = Mode(0040000) // A directory 111 ) 112 113 func (m Mode) String() string { 114 return fmt.Sprintf("%06o", m) 115 } 116 117 func (m Mode) GoString() string { 118 return fmt.Sprintf("%07o", m) 119 } 120 121 // Path is a string storing a git path. 122 type Path string 123 124 // PathEscape escapes a path in case it contains special characters. 125 func PathEscape(path Path) string { 126 if strings.HasPrefix(string(path), "\"") || strings.ContainsRune(string(path), ' ') || strings.ContainsRune(string(path), '\n') { 127 return "\"" + strings.Replace(strings.Replace(strings.Replace(string(path), "\\", "\\\\", -1), "\"", "\\\"", -1), "\n", "\\n", -1) + "\"" 128 } else { 129 return string(path) 130 } 131 } 132 133 // PathUnescape unescapes a quoted path. 134 func PathUnescape(epath string) Path { 135 if strings.HasPrefix(epath, "\"") { 136 return Path(strings.Replace(strings.Replace(strings.Replace(epath[1:len(epath)-1], "\\n", "\n", -1), "\\\"", "\"", -1), "\\\\", "\\", -1)) 137 } else { 138 return Path(epath) 139 } 140 } 141 142 // String calls PathEscape on the Path. 143 func (p Path) String() string { 144 return PathEscape(p) 145 }