Commit 3ba6cf73 authored by Kirill Smelkov's avatar Kirill Smelkov

Don't be fooled by strings.Split(..., "\n") result always having empty "" last element

By definition of strings.Split(..., sep) it "slices s into all substrings
separated by sep and returns a slice of the substrings between those
separators". That means that

    string.Split("hello\nworld\n", "\n") -> ["hello", "world", ""])     # NOTE the last ""

when parsing file by lines, it is handy though to do not get last empty
"" after last "\n". #6 shows how we missed to do that filtering-out for
case of empty backup.refs file and errored-out because of that.

To fix let's introduce a helper - splitlines(), which does the job of
filtering-out last empty entry after last separator. By using this
helper everywhere we can hopefully avoid problems while pulling only
empty repositories (#6 case), and also similar ones.

Fixes #6
/reported-by @iv
parent 7535343c
...@@ -459,7 +459,7 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) { ...@@ -459,7 +459,7 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) {
backup_refsv := []string{} // backup.refs content backup_refsv := []string{} // backup.refs content
backup_refs_parents := Sha1Set{} // sha1 for commit parents, obtained from refs backup_refs_parents := Sha1Set{} // sha1 for commit parents, obtained from refs
noncommit_seen := map[Sha1]Sha1{} // {} sha1 -> sha1_ (there are many duplicate tags) noncommit_seen := map[Sha1]Sha1{} // {} sha1 -> sha1_ (there are many duplicate tags)
for _, __ := range strings.Split(backup_refs_dump, "\n") { for _, __ := range splitlines(backup_refs_dump, "\n") {
sha1, type_, ref := Sha1{}, "", "" sha1, type_, ref := Sha1{}, "", ""
_, err := fmt.Sscanf(__, "%s %s %s\n", &sha1, &type_, &ref) _, err := fmt.Sscanf(__, "%s %s %s\n", &sha1, &type_, &ref)
if err != nil { if err != nil {
...@@ -705,7 +705,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec) ...@@ -705,7 +705,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
// read backup refs index // read backup refs index
repotab := map[string]*BackupRepo{} // repo.path -> repo repotab := map[string]*BackupRepo{} // repo.path -> repo
backup_refs := xgit("cat-file", "blob", fmt.Sprintf("%s:backup.refs", HEAD)) backup_refs := xgit("cat-file", "blob", fmt.Sprintf("%s:backup.refs", HEAD))
for _, refentry := range strings.Split(backup_refs, "\n") { for _, refentry := range splitlines(backup_refs, "\n") {
// sha1 prefix+refname (sha1_) // sha1 prefix+refname (sha1_)
badentry := func() { raisef("E: invalid backup.refs entry: %q", refentry) } badentry := func() { raisef("E: invalid backup.refs entry: %q", refentry) }
refentryv := strings.Fields(refentry) refentryv := strings.Fields(refentry)
...@@ -774,10 +774,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec) ...@@ -774,10 +774,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
// files // files
lstree := xgit("ls-tree", "--full-tree", "-r", "-z", "--", HEAD, prefix, RunWith{raw: true}) lstree := xgit("ls-tree", "--full-tree", "-r", "-z", "--", HEAD, prefix, RunWith{raw: true})
repos_seen := StrSet{} // dirs of *.git seen while restoring files repos_seen := StrSet{} // dirs of *.git seen while restoring files
for _, __ := range strings.Split(lstree, "\x00") { for _, __ := range splitlines(lstree, "\x00") {
if __ == "" {
continue // last empty line after last \0
}
mode, type_, sha1, filename, err := parse_lstree_entry(__) mode, type_, sha1, filename, err := parse_lstree_entry(__)
// NOTE // NOTE
// - `ls-tree -r` shows only leaf objects // - `ls-tree -r` shows only leaf objects
......
...@@ -98,6 +98,9 @@ func TestPullRestore(t *testing.T) { ...@@ -98,6 +98,9 @@ func TestPullRestore(t *testing.T) {
} }
// pull from testdata // pull from testdata
my0 := mydir + "/testdata/0"
cmd_pull(gb, []string{my0+":b0"}) // only empty repo in testdata/0
my1 := mydir + "/testdata/1" my1 := mydir + "/testdata/1"
cmd_pull(gb, []string{my1+":b1"}) cmd_pull(gb, []string{my1+":b1"})
......
[core]
repositoryformatversion = 0
filemode = true
bare = true
Unnamed repository; edit this file 'description' to name the repository.
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
...@@ -44,6 +44,17 @@ func String(b []byte) string { ...@@ -44,6 +44,17 @@ func String(b []byte) string {
return s return s
} }
// split string into lines. The last line, if it is empty, is omitted from the result
// (rationale is: string.Split("hello\nworld\n", "\n") -> ["hello", "world", ""])
func splitlines(s, sep string) []string {
sv := strings.Split(s, sep)
l := len(sv)
if l > 0 && sv[l-1] == "" {
sv = sv[:l-1]
}
return sv
}
// split string by sep and expect exactly 2 parts // split string by sep and expect exactly 2 parts
func split2(s, sep string) (s1, s2 string, err error) { func split2(s, sep string) (s1, s2 string, err error) {
parts := strings.Split(s, sep) parts := strings.Split(s, sep)
......
...@@ -33,6 +33,25 @@ func TestStringBytes(t *testing.T) { ...@@ -33,6 +33,25 @@ func TestStringBytes(t *testing.T) {
if !reflect.DeepEqual(b1, b) { t.Error("string -> Bytes not aliased") } if !reflect.DeepEqual(b1, b) { t.Error("string -> Bytes not aliased") }
} }
func TestSplitLines(t *testing.T) {
var tests = []struct { input, sep string; output []string } {
{"", "\n", []string{}},
{"hello", "\n", []string{"hello"}},
{"hello\n", "\n", []string{"hello"}},
{"hello\nworld", "\n", []string{"hello", "world"}},
{"hello\nworld\n", "\n", []string{"hello", "world"}},
{"hello\x00world\x00", "\n", []string{"hello\x00world\x00"}},
{"hello\x00world\x00", "\x00", []string{"hello", "world"}},
}
for _, tt := range tests {
sv := splitlines(tt.input, tt.sep)
if !reflect.DeepEqual(sv, tt.output) {
t.Errorf("splitlines(%q, %q) -> %q ; want %q", tt.input, tt.sep, sv, tt.output)
}
}
}
func TestSplit2(t *testing.T) { func TestSplit2(t *testing.T) {
var tests = []struct { input, s1, s2 string; ok bool } { var tests = []struct { input, s1, s2 string; ok bool } {
{"", "", "", false}, {"", "", "", false},
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment