diff --git a/error.go b/error.go index 4fd2a5b8f..1aae5a37a 100644 --- a/error.go +++ b/error.go @@ -40,6 +40,16 @@ func (err ErrNotExist) Error() string { return fmt.Sprintf("object does not exist [id: %s, rel_path: %s]", err.ID, err.RelPath) } +// ErrBadLink entry.FollowLink error +type ErrBadLink struct { + Name string + Message string +} + +func (err ErrBadLink) Error() string { + return fmt.Sprintf("%s: %s", err.Name, err.Message) +} + // ErrUnsupportedVersion error when required git version not matched type ErrUnsupportedVersion struct { Required string diff --git a/tests/repos/repo1_bare/objects/21/6bf54c2f2e2916b830ebe09e8c58a6ed52d86b b/tests/repos/repo1_bare/objects/21/6bf54c2f2e2916b830ebe09e8c58a6ed52d86b new file mode 100644 index 000000000..8c0b1b328 Binary files /dev/null and b/tests/repos/repo1_bare/objects/21/6bf54c2f2e2916b830ebe09e8c58a6ed52d86b differ diff --git a/tests/repos/repo1_bare/objects/50/13716a9da8e66ea21059a84f1b4311424d2b7f b/tests/repos/repo1_bare/objects/50/13716a9da8e66ea21059a84f1b4311424d2b7f new file mode 100644 index 000000000..b96905930 Binary files /dev/null and b/tests/repos/repo1_bare/objects/50/13716a9da8e66ea21059a84f1b4311424d2b7f differ diff --git a/tests/repos/repo1_bare/objects/59/dfb0bb505a601006e31fed53d2e24e44fca9ca b/tests/repos/repo1_bare/objects/59/dfb0bb505a601006e31fed53d2e24e44fca9ca new file mode 100644 index 000000000..16292715b Binary files /dev/null and b/tests/repos/repo1_bare/objects/59/dfb0bb505a601006e31fed53d2e24e44fca9ca differ diff --git a/tests/repos/repo1_bare/objects/62/d735f9efa9cf5b7df6bac9917b80e4779f4315 b/tests/repos/repo1_bare/objects/62/d735f9efa9cf5b7df6bac9917b80e4779f4315 new file mode 100644 index 000000000..15b958a2a Binary files /dev/null and b/tests/repos/repo1_bare/objects/62/d735f9efa9cf5b7df6bac9917b80e4779f4315 differ diff --git a/tests/repos/repo1_bare/objects/64/3a35374408002fcf2f0e8d42d262a1e0e2f80e b/tests/repos/repo1_bare/objects/64/3a35374408002fcf2f0e8d42d262a1e0e2f80e new file mode 100644 index 000000000..eb0ad4757 Binary files /dev/null and b/tests/repos/repo1_bare/objects/64/3a35374408002fcf2f0e8d42d262a1e0e2f80e differ diff --git a/tests/repos/repo1_bare/objects/6f/bd69e9823458e6c4a2fc5c0f6bc022b2f2acd1 b/tests/repos/repo1_bare/objects/6f/bd69e9823458e6c4a2fc5c0f6bc022b2f2acd1 new file mode 100644 index 000000000..5b2cfb21c --- /dev/null +++ b/tests/repos/repo1_bare/objects/6f/bd69e9823458e6c4a2fc5c0f6bc022b2f2acd1 @@ -0,0 +1 @@ +x1n0 E3 -&H&F+աo#d/X: zSiZb1ؔSag@F35];̈'Ɇ%sD5R,w_m೶kCv"?}m#8"#|xDtfET zz/N \ No newline at end of file diff --git a/tests/repos/repo1_bare/objects/80/06ff9adbf0cb94da7dad9e537e53817f9fa5c0 b/tests/repos/repo1_bare/objects/80/06ff9adbf0cb94da7dad9e537e53817f9fa5c0 new file mode 100644 index 000000000..808fe6efb Binary files /dev/null and b/tests/repos/repo1_bare/objects/80/06ff9adbf0cb94da7dad9e537e53817f9fa5c0 differ diff --git a/tests/repos/repo1_bare/objects/82/26f571dcc2d2f33a7179d929b10b9c39faa631 b/tests/repos/repo1_bare/objects/82/26f571dcc2d2f33a7179d929b10b9c39faa631 new file mode 100644 index 000000000..6a194fb3a Binary files /dev/null and b/tests/repos/repo1_bare/objects/82/26f571dcc2d2f33a7179d929b10b9c39faa631 differ diff --git a/tests/repos/repo1_bare/objects/98/1ff127cc331753bba28e1377c35934f1ca9b56 b/tests/repos/repo1_bare/objects/98/1ff127cc331753bba28e1377c35934f1ca9b56 new file mode 100644 index 000000000..ae6c93ac8 Binary files /dev/null and b/tests/repos/repo1_bare/objects/98/1ff127cc331753bba28e1377c35934f1ca9b56 differ diff --git a/tests/repos/repo1_bare/objects/b1/4df6442ea5a1b382985a6549b85d435376c351 b/tests/repos/repo1_bare/objects/b1/4df6442ea5a1b382985a6549b85d435376c351 new file mode 100644 index 000000000..02fe24f0a Binary files /dev/null and b/tests/repos/repo1_bare/objects/b1/4df6442ea5a1b382985a6549b85d435376c351 differ diff --git a/tests/repos/repo1_bare/objects/c8/c90111bdc18b3afd2b2906007059e95ac8fdc3 b/tests/repos/repo1_bare/objects/c8/c90111bdc18b3afd2b2906007059e95ac8fdc3 new file mode 100644 index 000000000..57c5d7ce8 Binary files /dev/null and b/tests/repos/repo1_bare/objects/c8/c90111bdc18b3afd2b2906007059e95ac8fdc3 differ diff --git a/tests/repos/repo1_bare/objects/d0/845fe2f85710b50d673dafe98236bf9f2023da b/tests/repos/repo1_bare/objects/d0/845fe2f85710b50d673dafe98236bf9f2023da new file mode 100644 index 000000000..d29ca5af8 Binary files /dev/null and b/tests/repos/repo1_bare/objects/d0/845fe2f85710b50d673dafe98236bf9f2023da differ diff --git a/tests/repos/repo1_bare/refs/heads/master b/tests/repos/repo1_bare/refs/heads/master index 79faa7cff..34850c25d 100644 --- a/tests/repos/repo1_bare/refs/heads/master +++ b/tests/repos/repo1_bare/refs/heads/master @@ -1 +1 @@ -8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2 +6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1 diff --git a/tree_entry.go b/tree_entry.go index 41023010c..210068fad 100644 --- a/tree_entry.go +++ b/tree_entry.go @@ -5,6 +5,7 @@ package git import ( + "io" "sort" "strconv" "strings" @@ -90,6 +91,45 @@ func (te *TreeEntry) Blob() *Blob { } } +// FollowLink returns the entry pointed to by a symlink +func (te *TreeEntry) FollowLink() (*TreeEntry, error) { + if !te.IsLink() { + return nil, ErrBadLink{te.Name(), "not a symlink"} + } + + // read the link + r, err := te.Blob().Data() + if err != nil { + return nil, err + } + buf := make([]byte, te.Size()) + _, err = io.ReadFull(r, buf) + if err != nil { + return nil, err + } + + lnk := string(buf) + t := te.ptree + + // traverse up directories + for ; t != nil && lnk[:3] == "../"; lnk = lnk[3:] { + t = t.ptree + } + + if t == nil { + return nil, ErrBadLink{te.Name(), "points outside of repo"} + } + + target, err := t.GetTreeEntryByPath(lnk) + if err != nil { + if IsErrNotExist(err) { + return nil, ErrBadLink{te.Name(), "broken link"} + } + return nil, err + } + return target, nil +} + // GetSubJumpablePathName return the full path of subdirectory jumpable ( contains only one directory ) func (te *TreeEntry) GetSubJumpablePathName() string { if te.IsSubModule() || !te.IsDir() { diff --git a/tree_entry_test.go b/tree_entry_test.go index 9a79d8e68..4573a7070 100644 --- a/tree_entry_test.go +++ b/tree_entry_test.go @@ -50,3 +50,43 @@ func TestEntriesCustomSort(t *testing.T) { assert.Equal(t, "bcd", entries[6].Name()) assert.Equal(t, "abc", entries[7].Name()) } + +func TestFollowLink(t *testing.T) { + r, err := OpenRepository("tests/repos/repo1_bare") + assert.NoError(t, err) + + commit, err := r.GetCommit("6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1") + assert.NoError(t, err) + + // get the symlink + lnk, err := commit.Tree.GetTreeEntryByPath("foo/bar/link_to_hello") + assert.NoError(t, err) + assert.True(t, lnk.IsLink()) + + // should be able to dereference to target + target, err := lnk.FollowLink() + assert.NoError(t, err) + assert.Equal(t, target.Name(), "hello") + assert.False(t, target.IsLink()) + assert.Equal(t, target.ID.String(), "b14df6442ea5a1b382985a6549b85d435376c351") + + // should error when called on normal file + target, err = commit.Tree.GetTreeEntryByPath("file1.txt") + assert.NoError(t, err) + _, err = target.FollowLink() + assert.Equal(t, err.Error(), "file1.txt: not a symlink") + + // should error for broken links + target, err = commit.Tree.GetTreeEntryByPath("foo/broken_link") + assert.NoError(t, err) + assert.True(t, target.IsLink()) + _, err = target.FollowLink() + assert.Equal(t, err.Error(), "broken_link: broken link") + + // should error for external links + target, err = commit.Tree.GetTreeEntryByPath("foo/outside_repo") + assert.NoError(t, err) + assert.True(t, target.IsLink()) + _, err = target.FollowLink() + assert.Equal(t, err.Error(), "outside_repo: points outside of repo") +}