Fix GetPage on section/bundle name overlaps
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Thu, 21 May 2020 09:25:00 +0000 (11:25 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sun, 24 May 2020 10:35:45 +0000 (12:35 +0200)
In the internal Radix we stored the directory based nodes without a traling slash, e.g. `/blog`.

The original motivation was probably to make it easy to do prefix searching: Give me all ancestors.

This, however have lead to some ambigouty with overlapping directory names.

This particular problem was, however, not possible to work around in an easy way, so from now we store these as `/blog/`.

Fixes #7301

helpers/path.go
htesting/hqt/checkers.go
hugolib/content_map.go
hugolib/content_map_page.go
hugolib/content_map_test.go
hugolib/page.go
hugolib/page__tree.go
hugolib/pagecollections.go
hugolib/pagecollections_test.go
hugolib/taxonomy_test.go

index 29e1e60718beca45f7f2a8b83dad51034b0c704e..01c45260743e83a9ece5f701c91eab2d7b591fbd 100644 (file)
@@ -665,3 +665,12 @@ func FileContainsAny(filename string, subslices [][]byte, fs afero.Fs) (bool, er
 func Exists(path string, fs afero.Fs) (bool, error) {
        return afero.Exists(fs, path)
 }
+
+// AddTrailingSlash adds a trailing Unix styled slash (/) if not already
+// there.
+func AddTrailingSlash(path string) string {
+       if !strings.HasSuffix(path, "/") {
+               path += "/"
+       }
+       return path
+}
index 6fb65ee47b50a8caa64e5256751f4ef5caa89e86..c12f78034cf246ad90283d081933c1b2641aaf8d 100644 (file)
@@ -77,7 +77,7 @@ func (c *stringChecker) Check(got interface{}, args []interface{}, note func(key
                return nil
        }
 
-       return fmt.Errorf("values are not the same text: %s", htesting.DiffStrings(s1, s2))
+       return fmt.Errorf("values are not the same text: %s", strings.Join(htesting.DiffStrings(s1, s2), " | "))
 }
 
 func normalizeString(s string) string {
index 3c57fffcfb0a4543885514631e63fc24cdfb5991..ddcc7070734ffd2ead5a2d2ba21eaba2e90dbc1c 100644 (file)
@@ -20,6 +20,8 @@ import (
        "strings"
        "sync"
 
+       "github.com/gohugoio/hugo/helpers"
+
        "github.com/gohugoio/hugo/resources/page"
        "github.com/pkg/errors"
 
@@ -31,27 +33,26 @@ import (
 )
 
 // We store the branch nodes in either the `sections` or `taxonomies` tree
-// with their path as a key; Unix style slashes, a leading slash but no
-// trailing slash.
+// with their path as a key; Unix style slashes, a leading and trailing slash.
 //
-// E.g. "/blog" or "/categories/funny"
+// E.g. "/blog/" or "/categories/funny/"
 //
 // Pages that belongs to a section are stored in the `pages` tree below
-// the section name and a branch separator, e.g. "/blog__hb_". A page is
+// the section name and a branch separator, e.g. "/blog/__hb_". A page is
 // given a key using the path below the section and the base filename with no extension
 // with a leaf separator added.
 //
 // For bundled pages (/mybundle/index.md), we use the folder name.
 //
-// An exmple of a full page key would be "/blog__hb_/page1__hl_"
+// An exmple of a full page key would be "/blog/__hb_page1__hl_"
 //
 // Bundled resources are stored in the `resources` having their path prefixed
 // with the bundle they belong to, e.g.
-// "/blog__hb_/bundle__hl_data.json".
+// "/blog/__hb_bundle__hl_data.json".
 //
 // The weighted taxonomy entries extracted from page front matter are stored in
 // the `taxonomyEntries` tree below /plural/term/page-key, e.g.
-// "/categories/funny/blog__hb_/bundle__hl_".
+// "/categories/funny/blog/__hb_bundle__hl_".
 const (
        cmBranchSeparator = "__hb_"
        cmLeafSeparator   = "__hl_"
@@ -105,7 +106,7 @@ func newContentMap(cfg contentMapConfig) *contentMap {
                                                addToReverseMap(mountKey, n, m)
                                        }
                                }
-                               k := strings.TrimSuffix(path.Base(s), cmLeafSeparator)
+                               k := strings.TrimPrefix(strings.TrimSuffix(path.Base(s), cmLeafSeparator), cmBranchSeparator)
                                addToReverseMap(k, n, m)
                                return false
                        })
@@ -127,18 +128,15 @@ type cmInsertKeyBuilder struct {
 }
 
 func (b cmInsertKeyBuilder) ForPage(s string) *cmInsertKeyBuilder {
-       // TODO2 fmt.Println("ForPage:", s, "baseKey:", b.baseKey, "key:", b.key)
+       //fmt.Println("ForPage:", s, "baseKey:", b.baseKey, "key:", b.key)
        baseKey := b.baseKey
        b.baseKey = s
 
-       if !strings.HasPrefix(s, "/") {
-               s = "/" + s
-       }
-
        if baseKey != "/" {
                // Don't repeat the section path in the key.
                s = strings.TrimPrefix(s, baseKey)
        }
+       s = strings.TrimPrefix(s, "/")
 
        switch b.tree {
        case b.m.sections:
@@ -154,10 +152,10 @@ func (b cmInsertKeyBuilder) ForPage(s string) *cmInsertKeyBuilder {
 }
 
 func (b cmInsertKeyBuilder) ForResource(s string) *cmInsertKeyBuilder {
-       // TODO2 fmt.Println("ForResource:", s, "baseKey:", b.baseKey, "key:", b.key)
+       //fmt.Println("ForResource:", s, "baseKey:", b.baseKey, "key:", b.key)
 
-       s = strings.TrimPrefix(s, "/")
-       s = strings.TrimPrefix(s, strings.TrimPrefix(b.baseKey, "/")+"/")
+       baseKey := helpers.AddTrailingSlash(b.baseKey)
+       s = strings.TrimPrefix(s, baseKey)
 
        switch b.tree {
        case b.m.pages:
@@ -173,14 +171,23 @@ func (b cmInsertKeyBuilder) ForResource(s string) *cmInsertKeyBuilder {
 
 func (b *cmInsertKeyBuilder) Insert(n *contentNode) *cmInsertKeyBuilder {
        if b.err == nil {
-               b.tree.Insert(cleanTreeKey(b.key), n)
+               b.tree.Insert(b.Key(), n)
        }
        return b
 }
 
+func (b *cmInsertKeyBuilder) Key() string {
+       switch b.tree {
+       case b.m.sections, b.m.taxonomies:
+               return cleanSectionTreeKey(b.key)
+       default:
+               return cleanTreeKey(b.key)
+       }
+}
+
 func (b *cmInsertKeyBuilder) DeleteAll() *cmInsertKeyBuilder {
        if b.err == nil {
-               b.tree.DeletePrefix(cleanTreeKey(b.key))
+               b.tree.DeletePrefix(b.Key())
        }
        return b
 }
@@ -211,15 +218,16 @@ func (b *cmInsertKeyBuilder) WithFile(fi hugofs.FileMetaInfo) *cmInsertKeyBuilde
 }
 
 func (b *cmInsertKeyBuilder) WithSection(s string) *cmInsertKeyBuilder {
+       s = cleanSectionTreeKey(s)
        b.newTopLevel()
        b.tree = b.m.sections
        b.baseKey = s
        b.key = s
-       // TODO2 fmt.Println("WithSection:", s, "baseKey:", b.baseKey, "key:", b.key)
        return b
 }
 
 func (b *cmInsertKeyBuilder) WithTaxonomy(s string) *cmInsertKeyBuilder {
+       s = cleanSectionTreeKey(s)
        b.newTopLevel()
        b.tree = b.m.taxonomies
        b.baseKey = s
@@ -233,20 +241,17 @@ func (b *cmInsertKeyBuilder) getBundle(s string) (string, string) {
        m := b.m
        section, _ := m.getSection(s)
 
-       p := s
-       if section != "/" {
-               p = strings.TrimPrefix(s, section)
-       }
+       p := strings.TrimPrefix(s, section)
 
-       bundlePathParts := strings.Split(p, "/")[1:]
+       bundlePathParts := strings.Split(p, "/")
        basePath := section + cmBranchSeparator
 
        // Put it into an existing bundle if found.
        for i := len(bundlePathParts) - 2; i >= 0; i-- {
                bundlePath := path.Join(bundlePathParts[:i]...)
-               searchKey := basePath + "/" + bundlePath + cmLeafSeparator
+               searchKey := basePath + bundlePath + cmLeafSeparator
                if _, found := m.pages.Get(searchKey); found {
-                       return section + "/" + bundlePath, searchKey
+                       return section + bundlePath, searchKey
                }
        }
 
@@ -432,7 +437,7 @@ func (m *contentMap) CreateMissingNodes() error {
                                sectionPath = sectionPath[:firstSlash]
                        }
                }
-               sect = cleanTreeKey(sect)
+               sect = cleanSectionTreeKey(sect)
                _, found := m.sections.Get(sect)
                if !found {
                        m.sections.Insert(sect, &contentNode{path: sectionPath})
@@ -440,7 +445,7 @@ func (m *contentMap) CreateMissingNodes() error {
        }
 
        for _, view := range m.cfg.taxonomyConfig {
-               s := cleanTreeKey(view.plural)
+               s := cleanSectionTreeKey(view.plural)
                _, found := m.taxonomies.Get(s)
                if !found {
                        b := &contentNode{
@@ -476,15 +481,20 @@ func (m *contentMap) newContentNodeFromFi(fi hugofs.FileMetaInfo) *contentNode {
 }
 
 func (m *contentMap) getFirstSection(s string) (string, *contentNode) {
+       s = helpers.AddTrailingSlash(s)
        for {
                k, v, found := m.sections.LongestPrefix(s)
+
                if !found {
                        return "", nil
                }
-               if strings.Count(k, "/") == 1 {
+
+               if strings.Count(k, "/") <= 2 {
                        return k, v.(*contentNode)
                }
-               s = path.Dir(s)
+
+               s = helpers.AddTrailingSlash(path.Dir(strings.TrimSuffix(s, "/")))
+
        }
 }
 
@@ -507,10 +517,7 @@ func (m *contentMap) getOrCreateSection(n *contentNode, s string) (string, *cont
        }
 
        if mustCreate {
-               k = s[:strings.Index(s[1:], "/")+1]
-               if k == "" {
-                       k = "/"
-               }
+               k = cleanSectionTreeKey(s[:strings.Index(s[1:], "/")+1])
 
                b = &contentNode{
                        path: n.rootSection(),
@@ -523,7 +530,9 @@ func (m *contentMap) getOrCreateSection(n *contentNode, s string) (string, *cont
 }
 
 func (m *contentMap) getPage(section, name string) *contentNode {
-       key := section + cmBranchSeparator + "/" + name + cmLeafSeparator
+       section = helpers.AddTrailingSlash(section)
+       key := section + cmBranchSeparator + name + cmLeafSeparator
+
        v, found := m.pages.Get(key)
        if found {
                return v.(*contentNode)
@@ -532,7 +541,9 @@ func (m *contentMap) getPage(section, name string) *contentNode {
 }
 
 func (m *contentMap) getSection(s string) (string, *contentNode) {
-       k, v, found := m.sections.LongestPrefix(path.Dir(s))
+       s = helpers.AddTrailingSlash(path.Dir(strings.TrimSuffix(s, "/")))
+
+       k, v, found := m.sections.LongestPrefix(s)
 
        if found {
                return k, v.(*contentNode)
@@ -541,21 +552,18 @@ func (m *contentMap) getSection(s string) (string, *contentNode) {
 }
 
 func (m *contentMap) getTaxonomyParent(s string) (string, *contentNode) {
-       s = path.Dir(s)
-       if s == "/" {
-               v, found := m.sections.Get(s)
-               if found {
-                       return s, v.(*contentNode)
-               }
-               return "", nil
+       s = helpers.AddTrailingSlash(path.Dir(strings.TrimSuffix(s, "/")))
+       k, v, found := m.taxonomies.LongestPrefix(s)
+
+       if found {
+               return k, v.(*contentNode)
        }
 
-       for _, tree := range []*contentTree{m.taxonomies, m.sections} {
-               k, v, found := tree.LongestPrefix(s)
-               if found {
-                       return k, v.(*contentNode)
-               }
+       v, found = m.sections.Get("/")
+       if found {
+               return s, v.(*contentNode)
        }
+
        return "", nil
 }
 
@@ -569,6 +577,15 @@ func cleanTreeKey(k string) string {
        return k
 }
 
+func cleanSectionTreeKey(k string) string {
+       k = cleanTreeKey(k)
+       if k != "/" {
+               k += "/"
+       }
+
+       return k
+}
+
 func (m *contentMap) onSameLevel(s1, s2 string) bool {
        return strings.Count(s1, "/") == strings.Count(s2, "/")
 }
@@ -606,13 +623,13 @@ func (m *contentMap) deleteOrphanSections() {
                        return false
                }
 
-               if s == "/" || strings.Count(s, "/") > 1 {
+               if s == "/" || strings.Count(s, "/") > 2 {
                        return false
                }
 
                prefixBundle := s + cmBranchSeparator
 
-               if !(m.sections.hasPrefix(s+"/") || m.pages.hasPrefix(prefixBundle) || m.resources.hasPrefix(prefixBundle)) {
+               if !(m.sections.hasBelow(s) || m.pages.hasBelow(prefixBundle) || m.resources.hasBelow(prefixBundle)) {
                        sectionsToDelete = append(sectionsToDelete, s)
                }
 
@@ -630,13 +647,15 @@ func (m *contentMap) deletePage(s string) {
 }
 
 func (m *contentMap) deleteSectionByPath(s string) {
-       m.sections.Delete(s)
-       m.sections.DeletePrefix(s + "/")
-       m.pages.DeletePrefix(s + cmBranchSeparator)
-       m.pages.DeletePrefix(s + "/")
-       m.resources.DeletePrefix(s + cmBranchSeparator)
-       m.resources.DeletePrefix(s + cmLeafSeparator)
-       m.resources.DeletePrefix(s + "/")
+       if !strings.HasSuffix(s, "/") {
+               panic("section must end with a slash")
+       }
+       if !strings.HasPrefix(s, "/") {
+               panic("section must start with a slash")
+       }
+       m.sections.DeletePrefix(s)
+       m.pages.DeletePrefix(s)
+       m.resources.DeletePrefix(s)
 }
 
 func (m *contentMap) deletePageByPath(s string) {
@@ -648,8 +667,7 @@ func (m *contentMap) deletePageByPath(s string) {
 }
 
 func (m *contentMap) deleteTaxonomy(s string) {
-       m.taxonomies.Delete(s)
-       m.taxonomies.DeletePrefix(s + "/")
+       m.taxonomies.DeletePrefix(s)
 }
 
 func (m *contentMap) reduceKeyPart(dir, filename string) string {
@@ -817,7 +835,7 @@ func (c *contentTree) WalkQuery(query pageMapQuery, walkFn contentTreeNodeCallba
                filter = contentTreeNoListAlwaysFilter
        }
        if query.Prefix != "" {
-               c.WalkPrefix(query.Prefix, func(s string, v interface{}) bool {
+               c.WalkBelow(query.Prefix, func(s string, v interface{}) bool {
                        n := v.(*contentNode)
                        if filter != nil && filter(s, n) {
                                return false
@@ -862,6 +880,18 @@ func (c contentTrees) WalkPrefix(prefix string, fn contentTreeNodeCallback) {
        }
 }
 
+// WalkBelow walks the tree below the given prefix, i.e. it skips the
+// node with the given prefix as key.
+func (c *contentTree) WalkBelow(prefix string, fn radix.WalkFn) {
+       c.Tree.WalkPrefix(prefix, func(s string, v interface{}) bool {
+               if s == prefix {
+                       return false
+               }
+               return fn(s, v)
+       })
+
+}
+
 func (c *contentTree) getMatch(matches func(b *contentNode) bool) string {
        var match string
        c.Walk(func(s string, v interface{}) bool {
@@ -881,9 +911,9 @@ func (c *contentTree) getMatch(matches func(b *contentNode) bool) string {
        return match
 }
 
-func (c *contentTree) hasPrefix(s string) bool {
+func (c *contentTree) hasBelow(s1 string) bool {
        var t bool
-       c.Tree.WalkPrefix(s, func(s string, v interface{}) bool {
+       c.WalkBelow(s1, func(s2 string, v interface{}) bool {
                t = true
                return true
        })
@@ -953,12 +983,7 @@ func (c *contentTreeRef) getPagesRecursive() page.Pages {
                Filter: c.n.p.m.getListFilter(true),
        }
 
-       query.Prefix = c.key + cmBranchSeparator
-       c.m.collectPages(query, func(c *contentNode) {
-               pas = append(pas, c.p)
-       })
-
-       query.Prefix = c.key + "/"
+       query.Prefix = c.key
        c.m.collectPages(query, func(c *contentNode) {
                pas = append(pas, c.p)
        })
index 6be56f3f5356cf1c11628f7292811892f3d32986..7516a702923f7e0c9e71dda6e0da21338c99ec4b 100644 (file)
@@ -70,7 +70,7 @@ func (m *pageMap) createMissingTaxonomyNodes() error {
        m.taxonomyEntries.Walk(func(s string, v interface{}) bool {
                n := v.(*contentNode)
                vi := n.viewInfo
-               k := cleanTreeKey(vi.name.plural + "/" + vi.termKey)
+               k := cleanSectionTreeKey(vi.name.plural + "/" + vi.termKey)
 
                if _, found := m.taxonomies.Get(k); !found {
                        vic := &contentBundleViewInfo{
@@ -266,6 +266,7 @@ func (m *pageMap) newResource(fim hugofs.FileMetaInfo, owner *pageState) (resour
 
 func (m *pageMap) createSiteTaxonomies() error {
        m.s.taxonomies = make(TaxonomyList)
+       var walkErr error
        m.taxonomies.Walk(func(s string, v interface{}) bool {
                n := v.(*contentNode)
                t := n.viewInfo
@@ -276,7 +277,11 @@ func (m *pageMap) createSiteTaxonomies() error {
                        m.s.taxonomies[viewName.plural] = make(Taxonomy)
                } else {
                        taxonomy := m.s.taxonomies[viewName.plural]
-                       m.taxonomyEntries.WalkPrefix(s+"/", func(ss string, v interface{}) bool {
+                       if taxonomy == nil {
+                               walkErr = errors.Errorf("missing taxonomy: %s", viewName.plural)
+                               return true
+                       }
+                       m.taxonomyEntries.WalkPrefix(s, func(ss string, v interface{}) bool {
                                b2 := v.(*contentNode)
                                info := b2.viewInfo
                                taxonomy.add(info.termKey, page.NewWeightedPage(info.weight, info.ref.p, n.p))
@@ -294,7 +299,7 @@ func (m *pageMap) createSiteTaxonomies() error {
                }
        }
 
-       return nil
+       return walkErr
 }
 
 func (m *pageMap) createListAllPages() page.Pages {
@@ -426,7 +431,6 @@ func (m *pageMap) assembleSections() error {
 
        m.sections.Walk(func(s string, v interface{}) bool {
                n := v.(*contentNode)
-
                var shouldBuild bool
 
                defer func() {
@@ -596,11 +600,12 @@ func (m *pageMap) attachPageToViews(s string, b *contentNode) {
                                },
                        }
 
-                       if s == "/" {
-                               // To avoid getting an empty key.
-                               s = page.KindHome
+                       var key string
+                       if strings.HasSuffix(s, "/") {
+                               key = cleanSectionTreeKey(path.Join(viewName.plural, termKey, s))
+                       } else {
+                               key = cleanTreeKey(path.Join(viewName.plural, termKey, s))
                        }
-                       key := cleanTreeKey(path.Join(viewName.plural, termKey, s))
                        m.taxonomyEntries.Insert(key, bv)
                }
        }
@@ -638,19 +643,10 @@ func (m *pageMap) collectPagesAndSections(query pageMapQuery, fn func(c *content
 }
 
 func (m *pageMap) collectSections(query pageMapQuery, fn func(c *contentNode)) error {
-       var level int
-       isHome := query.Prefix == "/"
-
-       if !isHome {
-               level = strings.Count(query.Prefix, "/")
-       }
+       level := strings.Count(query.Prefix, "/")
 
        return m.collectSectionsFn(query, func(s string, c *contentNode) bool {
-               if s == query.Prefix {
-                       return false
-               }
-
-               if (strings.Count(s, "/") - level) != 1 {
+               if strings.Count(s, "/") != level+1 {
                        return false
                }
 
@@ -745,10 +741,11 @@ func (m *pageMaps) AssemblePages() error {
                        return err
                }
 
-               a := (&sectionWalker{m: pm.contentMap}).applyAggregates()
+               sw := &sectionWalker{m: pm.contentMap}
+               a := sw.applyAggregates()
                _, mainSectionsSet := pm.s.s.Info.Params()["mainsections"]
                if !mainSectionsSet && a.mainSection != "" {
-                       mainSections := []string{a.mainSection}
+                       mainSections := []string{strings.TrimRight(a.mainSection, "/")}
                        pm.s.s.Info.Params()["mainSections"] = mainSections
                        pm.s.s.Info.Params()["mainsections"] = mainSections
                }
@@ -847,7 +844,7 @@ func (b *pagesMapBucket) getTaxonomies() page.Pages {
        b.sectionsInit.Do(func() {
                var pas page.Pages
                ref := b.owner.treeRef
-               ref.m.collectTaxonomies(ref.key+"/", func(c *contentNode) {
+               ref.m.collectTaxonomies(ref.key, func(c *contentNode) {
                        pas = append(pas, c.p)
                })
                page.SortByDefault(pas)
@@ -888,8 +885,12 @@ type sectionAggregateHandler struct {
        s string
 }
 
+func (h *sectionAggregateHandler) String() string {
+       return fmt.Sprintf("%s/%s - %d - %s", h.sectionAggregate.datesAll, h.sectionAggregate.datesSection, h.sectionPageCount, h.s)
+}
+
 func (h *sectionAggregateHandler) isRootSection() bool {
-       return h.s != "/" && strings.Count(h.s, "/") == 1
+       return h.s != "/" && strings.Count(h.s, "/") == 2
 }
 
 func (h *sectionAggregateHandler) handleNested(v sectionWalkHandler) error {
@@ -963,11 +964,13 @@ func (w *sectionWalker) applyAggregates() *sectionAggregateHandler {
 func (w *sectionWalker) walkLevel(prefix string, createVisitor func() sectionWalkHandler) sectionWalkHandler {
 
        level := strings.Count(prefix, "/")
+
        visitor := createVisitor()
 
-       w.m.taxonomies.WalkPrefix(prefix, func(s string, v interface{}) bool {
+       w.m.taxonomies.WalkBelow(prefix, func(s string, v interface{}) bool {
                currentLevel := strings.Count(s, "/")
-               if currentLevel > level {
+
+               if currentLevel > level+1 {
                        return false
                }
 
@@ -977,8 +980,8 @@ func (w *sectionWalker) walkLevel(prefix string, createVisitor func() sectionWal
                        return true
                }
 
-               if currentLevel == 1 {
-                       nested := w.walkLevel(s+"/", createVisitor)
+               if currentLevel == 2 {
+                       nested := w.walkLevel(s, createVisitor)
                        if w.err = visitor.handleNested(nested); w.err != nil {
                                return true
                        }
@@ -995,9 +998,9 @@ func (w *sectionWalker) walkLevel(prefix string, createVisitor func() sectionWal
                return w.err != nil
        })
 
-       w.m.sections.WalkPrefix(prefix, func(s string, v interface{}) bool {
+       w.m.sections.WalkBelow(prefix, func(s string, v interface{}) bool {
                currentLevel := strings.Count(s, "/")
-               if currentLevel > level {
+               if currentLevel > level+1 {
                        return false
                }
 
@@ -1016,11 +1019,9 @@ func (w *sectionWalker) walkLevel(prefix string, createVisitor func() sectionWal
                        return true
                }
 
-               if s != "/" {
-                       nested := w.walkLevel(s+"/", createVisitor)
-                       if w.err = visitor.handleNested(nested); w.err != nil {
-                               return true
-                       }
+               nested := w.walkLevel(s, createVisitor)
+               if w.err = visitor.handleNested(nested); w.err != nil {
+                       return true
                }
 
                w.err = visitor.handleSectionPost()
index 4ddbf8c7f528448c254d068c9e37503a278fcd0b..9ec30201a388ce4ec7d8f4386a78de63706df542 100644 (file)
@@ -155,19 +155,19 @@ func TestContentMap(t *testing.T) {
 
                expect := `
           Tree 0:
-               /blog__hb_/a__hl_
-               /blog__hb_/b/c__hl_
+               /blog/__hb_a__hl_
+               /blog/__hb_b/c__hl_
               Tree 1:
-               /blog
+               /blog/
               Tree 2:
-               /blog__hb_/a__hl_b/data.json
-               /blog__hb_/a__hl_logo.png
-               /blog__hl_sectiondata.json
-              en/pages/blog__hb_/a__hl_|f:blog/a/index.md
+               /blog/__hb_a__hl_b/data.json
+               /blog/__hb_a__hl_logo.png
+               /blog/__hl_sectiondata.json
+              en/pages/blog/__hb_a__hl_|f:blog/a/index.md
                 - R: blog/a/b/data.json
                 - R: blog/a/logo.png
-              en/pages/blog__hb_/b/c__hl_|f:blog/b/c/index.md
-              en/sections/blog|f:blog/_index.md
+              en/pages/blog/__hb_b/c__hl_|f:blog/b/c/index.md
+              en/sections/blog/|f:blog/_index.md
                 - P: blog/a/index.md
                 - P: blog/b/c/index.md
                 - R: blog/sectiondata.json
@@ -194,24 +194,24 @@ func TestContentMap(t *testing.T) {
 
                expect = `
                         Tree 0:
-               /blog__hb_/a__hl_
-               /blog__hb_/b/c__hl_
+               /blog/__hb_a__hl_
+               /blog/__hb_b/c__hl_
               Tree 1:
-               /blog
+               /blog/
               Tree 2:
-               /blog__hb_/a__hl_b/data.json
-               /blog__hb_/a__hl_b/data2.json
-               /blog__hb_/a__hl_logo.png
-               /blog__hb_/b/c__hl_d/data3.json
-               /blog__hl_sectiondata.json
-               /blog__hl_sectiondata2.json
-              en/pages/blog__hb_/a__hl_|f:blog/a/index.md
+               /blog/__hb_a__hl_b/data.json
+               /blog/__hb_a__hl_b/data2.json
+               /blog/__hb_a__hl_logo.png
+               /blog/__hb_b/c__hl_d/data3.json
+               /blog/__hl_sectiondata.json
+               /blog/__hl_sectiondata2.json
+              en/pages/blog/__hb_a__hl_|f:blog/a/index.md
                 - R: blog/a/b/data.json
                 - R: blog/a/b/data2.json
                 - R: blog/a/logo.png
-              en/pages/blog__hb_/b/c__hl_|f:blog/b/c/index.md
+              en/pages/blog/__hb_b/c__hl_|f:blog/b/c/index.md
                 - R: blog/b/c/d/data3.json
-              en/sections/blog|f:blog/_index.md
+              en/sections/blog/|f:blog/_index.md
                 - P: blog/a/index.md
                 - P: blog/b/c/index.md
                 - R: blog/sectiondata.json
@@ -226,26 +226,26 @@ func TestContentMap(t *testing.T) {
 
                c.Assert(m.testDump(), hqt.IsSameString, `
                 Tree 0:
-               /blog__hb_/a__hl_
-               /blog__hb_/b/c__hl_
-               /blog__hb_/b__hl_
+               /blog/__hb_a__hl_
+               /blog/__hb_b/c__hl_
+               /blog/__hb_b__hl_
               Tree 1:
-               /blog
+               /blog/
               Tree 2:
-               /blog__hb_/a__hl_b/data.json
-               /blog__hb_/a__hl_b/data2.json
-               /blog__hb_/a__hl_logo.png
-               /blog__hb_/b/c__hl_d/data3.json
-               /blog__hl_sectiondata.json
-               /blog__hl_sectiondata2.json
-              en/pages/blog__hb_/a__hl_|f:blog/a/index.md
+               /blog/__hb_a__hl_b/data.json
+               /blog/__hb_a__hl_b/data2.json
+               /blog/__hb_a__hl_logo.png
+               /blog/__hb_b/c__hl_d/data3.json
+               /blog/__hl_sectiondata.json
+               /blog/__hl_sectiondata2.json
+              en/pages/blog/__hb_a__hl_|f:blog/a/index.md
                 - R: blog/a/b/data.json
                 - R: blog/a/b/data2.json
                 - R: blog/a/logo.png
-              en/pages/blog__hb_/b/c__hl_|f:blog/b/c/index.md
+              en/pages/blog/__hb_b/c__hl_|f:blog/b/c/index.md
                 - R: blog/b/c/d/data3.json
-              en/pages/blog__hb_/b__hl_|f:blog/b.md
-              en/sections/blog|f:blog/_index.md
+              en/pages/blog/__hb_b__hl_|f:blog/b.md
+              en/sections/blog/|f:blog/_index.md
                 - P: blog/a/index.md
                 - P: blog/b/c/index.md
                 - P: blog/b.md
@@ -280,19 +280,19 @@ func TestContentMap(t *testing.T) {
                c.Assert(got, hqt.IsSameString, `
                        
                         Tree 0:
-               /__hb_/bundle__hl_
-               /blog__hb_/a__hl_
-               /blog__hb_/page__hl_
+               /__hb_bundle__hl_
+               /blog/__hb_a__hl_
+               /blog/__hb_page__hl_
               Tree 1:
                /
-               /blog
+               /blog/
               Tree 2:
-              en/pages/__hb_/bundle__hl_|f:bundle/index.md
-              en/pages/blog__hb_/a__hl_|f:blog/a/index.md
-              en/pages/blog__hb_/page__hl_|f:blog/page.md
+              en/pages/__hb_bundle__hl_|f:bundle/index.md
+              en/pages/blog/__hb_a__hl_|f:blog/a/index.md
+              en/pages/blog/__hb_page__hl_|f:blog/page.md
               en/sections/
                 - P: bundle/index.md
-              en/sections/blog
+              en/sections/blog/
                 - P: blog/a/index.md
                 - P: blog/page.md
             
index bd518c1e16daecc2adac36566e4b45f4d54a2d67..dbcc31236981e30eae0005ffb09561657baa5fc4 100644 (file)
@@ -133,22 +133,21 @@ func (p *pageState) GitInfo() *gitmap.GitInfo {
 
 // GetTerms gets the terms defined on this page in the given taxonomy.
 func (p *pageState) GetTerms(taxonomy string) page.Pages {
-       taxonomy = strings.ToLower(taxonomy)
+       if p.treeRef == nil {
+               return nil
+       }
+
        m := p.s.pageMap
-       prefix := cleanTreeKey(taxonomy)
 
-       var self string
-       if p.IsHome() {
-               // TODO(bep) make this less magical, see taxonomyEntries.Insert.
-               self = "/" + page.KindHome
-       } else if p.treeRef != nil {
-               self = p.treeRef.key
-       }
+       taxonomy = strings.ToLower(taxonomy)
+       prefix := cleanSectionTreeKey(taxonomy)
+       self := strings.TrimPrefix(p.treeRef.key, "/")
 
        var pas page.Pages
 
        m.taxonomies.WalkQuery(pageMapQuery{Prefix: prefix}, func(s string, n *contentNode) bool {
-               if _, found := m.taxonomyEntries.Get(s + self); found {
+               key := s + self
+               if _, found := m.taxonomyEntries.Get(key); found {
                        pas = append(pas, n.p)
                }
                return false
index a6f66ffbcca70a2a0f0022a40f168e956381b6f1..5b53dc4cd1c9e6a84340ac75be35257858308ba6 100644 (file)
@@ -58,7 +58,7 @@ func (pt pageTree) IsAncestor(other interface{}) (bool, error) {
                return true, nil
        }
 
-       if strings.HasPrefix(ref2.key, ref1.key+"/") {
+       if strings.HasPrefix(ref2.key, ref1.key) {
                return true, nil
        }
 
@@ -109,7 +109,7 @@ func (pt pageTree) IsDescendant(other interface{}) (bool, error) {
                return true, nil
        }
 
-       if strings.HasPrefix(ref1.key, ref2.key+"/") {
+       if strings.HasPrefix(ref1.key, ref2.key) {
                return true, nil
        }
 
@@ -123,9 +123,11 @@ func (pt pageTree) FirstSection() page.Page {
                return pt.p.s.home
        }
        key := ref.key
+
        if !ref.isSection() {
                key = path.Dir(key)
        }
+
        _, b := ref.m.getFirstSection(key)
        if b == nil {
                return nil
index 7cdac7453251cb4455e9fcee89f4cdf9b2edbe46..49378452f239b83ce6768a4116d1450aec8b4eda 100644 (file)
@@ -169,18 +169,20 @@ func (c *PageCollections) getPageNew(context page.Page, ref string) (page.Page,
 func (c *PageCollections) getSectionOrPage(ref string) (*contentNode, string) {
        var n *contentNode
 
-       s, v, found := c.pageMap.sections.LongestPrefix(ref)
+       pref := helpers.AddTrailingSlash(ref)
+       s, v, found := c.pageMap.sections.LongestPrefix(pref)
 
        if found {
                n = v.(*contentNode)
        }
 
-       if found && s == ref {
+       if found && s == pref {
                // A section
                return n, ""
        }
 
        m := c.pageMap
+
        filename := strings.TrimPrefix(strings.TrimPrefix(ref, s), "/")
        langSuffix := "." + m.s.Lang()
 
@@ -224,9 +226,11 @@ func shouldDoSimpleLookup(ref string) bool {
 
 func (c *PageCollections) getContentNode(context page.Page, isReflink bool, ref string) (*contentNode, error) {
        ref = filepath.ToSlash(strings.ToLower(strings.TrimSpace(ref)))
+
        if ref == "" {
                ref = "/"
        }
+
        inRef := ref
        navUp := strings.HasPrefix(ref, "..")
        var doSimpleLookup bool
@@ -275,9 +279,11 @@ func (c *PageCollections) getContentNode(context page.Page, isReflink bool, ref
        }
 
        // Check if it's a taxonomy node
-       s, v, found := m.taxonomies.LongestPrefix(ref)
+       pref := helpers.AddTrailingSlash(ref)
+       s, v, found := m.taxonomies.LongestPrefix(pref)
+
        if found {
-               if !m.onSameLevel(ref, s) {
+               if !m.onSameLevel(pref, s) {
                        return nil, nil
                }
                return v.(*contentNode), nil
index bf3832c9642ad9b0442299c8a0a8838a682ccc56..bb846da854ee1e6b6a46f9dae6c00cf2ed04f272 100644 (file)
@@ -211,6 +211,9 @@ func TestGetPage(t *testing.T) {
        writeSource(t, fs, filepath.Join("content", "sect3", "b1", "index.md"), pc("b1 bundle"))
        writeSource(t, fs, filepath.Join("content", "sect3", "index", "index.md"), pc("index bundle"))
 
+       writeSource(t, fs, filepath.Join("content", "section_bundle_overlap", "_index.md"), pc("index overlap section"))
+       writeSource(t, fs, filepath.Join("content", "section_bundle_overlap_bundle", "index.md"), pc("index overlap bundle"))
+
        s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
 
        sec3, err := s.getPageNew(nil, "/sect3")
@@ -282,6 +285,9 @@ func TestGetPage(t *testing.T) {
                // Bundle variants
                {"Bundle regular", page.KindPage, nil, []string{"sect3/b1", "sect3/b1/index.md", "sect3/b1/index.en.md"}, "b1 bundle"},
                {"Bundle index name", page.KindPage, nil, []string{"sect3/index/index.md", "sect3/index"}, "index bundle"},
+
+               // https://github.com/gohugoio/hugo/issues/7301
+               {"Section and bundle overlap", page.KindPage, nil, []string{"section_bundle_overlap_bundle"}, "index overlap bundle"},
        }
 
        for _, test := range tests {
index f37e37b89590654c0e6cc3c79be8aa593446d7eb..1f43318fa58d78dc115a19778885767d8f73b6a6 100644 (file)
@@ -637,6 +637,7 @@ Cats Paginator {{ range $cats.Paginator.Pages }}{{ .RelPermalink }}|{{ end }}:EN
        b.Assert(funny, qt.Not(qt.IsNil))
 
        b.Assert(cat.Parent().IsHome(), qt.Equals, true)
+       b.Assert(funny.Kind(), qt.Equals, "taxonomy")
        b.Assert(funny.Parent(), qt.Equals, cat)
 
        b.AssertFileContent("public/index.html", `