// set partial to true to test if, for example,
            // "/a/b" matches the start of "/*/b/*/d"
            // Partial means, if you run out of file before you run
            // out of pattern, then that's fine, as long as all
            // the parts match.
            bool MatchOne(IList <string> file, IList <ParseItem> pattern, bool partial)
            {
                //if (options.debug) {
                //  console.error("matchOne",
                //                { "this": this
                //                , file: file
                //                , pattern: pattern })
                //}

                if (options.MatchBase && pattern.Count == 1)
                {
                    file = new[] { file.Last(s => !String.IsNullOrEmpty(s)) };
                }

                //if (options.debug) {
                //  console.error("matchOne", file.length, pattern.length)
                //}
                int fi = 0, pi = 0;

                for (; (fi < file.Count) && (pi < pattern.Count); fi++, pi++)
                {
                    //if (options.debug) {
                    //  console.error("matchOne loop")
                    //}
                    ParseItem p = pattern[pi];
                    string    f = file[fi];

                    //if (options.debug) {
                    //  console.error(pattern, p, f)
                    //}

                    // should be impossible.
                    // some invalid regexp stuff in the set.
                    if (p == null)
                    {
                        return(false);
                    }

                    if (p is GlobStar)
                    {
                        //if (options.debug)
                        //  console.error('GLOBSTAR', [pattern, p, f])

                        // "**"
                        // a/**/b/**/c would match the following:
                        // a/b/x/y/z/c
                        // a/x/y/z/b/c
                        // a/b/x/b/x/c
                        // a/b/c
                        // To do this, take the rest of the pattern after
                        // the **, and see if it would match the file remainder.
                        // If so, return success.
                        // If not, the ** "swallows" a segment, and try again.
                        // This is recursively awful.
                        //
                        // a/**/b/**/c matching a/b/x/y/z/c
                        // - a matches a
                        // - doublestar
                        //   - matchOne(b/x/y/z/c, b/**/c)
                        //     - b matches b
                        //     - doublestar
                        //       - matchOne(x/y/z/c, c) -> no
                        //       - matchOne(y/z/c, c) -> no
                        //       - matchOne(z/c, c) -> no
                        //       - matchOne(c, c) yes, hit
                        int fr = fi, pr = pi + 1;
                        if (pr == pattern.Count)
                        {
                            //if (options.debug)
                            //  console.error('** at the end')
                            // a ** at the end will just swallow the rest.
                            // We have found a match.
                            // however, it will not swallow /.x, unless
                            // options.dot is set.
                            // . and .. are *never* matched by **, for explosively
                            // exponential reasons.
                            for (; fi < file.Count; fi++)
                            {
                                if (file[fi] == "." || file[fi] == ".." ||
                                    (!options.Dot && !string.IsNullOrEmpty(file[fi]) && file[fi][0] == '.'))
                                {
                                    return(false);
                                }
                            }
                            return(true);
                        }

                        // ok, let's see if we can swallow whatever we can.
                        while (fr < file.Count)
                        {
                            var swallowee = file[fr];

                            //if (options.debug) {
                            //  console.error('\nglobstar while',
                            //                file, fr, pattern, pr, swallowee)
                            //}

                            // XXX remove this slice.  Just pass the start index.
                            if (this.MatchOne(file.Skip(fr).ToList(), pattern.Skip(pr).ToList(), partial))
                            {
                                //if (options.debug)
                                //  console.error('globstar found match!', fr, file.Count, swallowee)
                                // found a match.
                                return(true);
                            }
                            else
                            {
                                // can't swallow "." or ".." ever.
                                // can only swallow ".foo" when explicitly asked.
                                // TODO:: BERND FIXED THE CASE WHERE IT IS EMPTY ON START
                                if (swallowee == "." || swallowee == ".." ||
                                    (!options.Dot && swallowee.Length > 0 && swallowee[0] == '.'))
                                {
                                    //if (options.debug)
                                    //  console.error("dot detected!", file, fr, pattern, pr)
                                    break;
                                }

                                // ** swallows a segment, and continue.
                                //if (options.debug)
                                //  console.error('globstar swallow a segment, and continue')
                                fr++;
                            }
                        }
                        // no match was found.
                        // However, in partial mode, we can't say this is necessarily over.
                        // If there's more *pattern* left, then
                        if (partial)
                        {
                            // ran out of file
                            // console.error("\n>>> no match, partial?", file, fr, pattern, pr)
                            if (fr == file.Count)
                            {
                                return(true);
                            }
                        }
                        return(false);
                    }

                    // something other than **
                    // non-magic patterns just have to match exactly
                    // patterns with magic have been turned into regexps.
                    if (!p.Match(f, options))
                    {
                        return(false);
                    }
                }

                // Note: ending in / means that we'll get a final ""
                // at the end of the pattern.  This can only match a
                // corresponding "" at the end of the file.
                // If the file ends in /, then it can only match a
                // a pattern that ends in /, unless the pattern just
                // doesn't have any more for it. But, a/b/ should *not*
                // match "a/b/*", even though "" matches against the
                // [^/]*? pattern, except in partial mode, where it might
                // simply not be reached yet.
                // However, a/b/ should still satisfy a/*

                // now either we fell off the end of the pattern, or we're done.
                if (fi == file.Count && pi == pattern.Count)
                {
                    // ran out of pattern and filename at the same time.
                    // an exact hit!
                    return(true);
                }
                else if (fi == file.Count)
                {
                    // ran out of file, but still had pattern left.
                    // this is ok if we're doing the match as part of
                    // a glob fs traversal.
                    return(partial);
                }
                else if (pi == pattern.Count)
                {
                    // ran out of pattern, still have file left.
                    // this is only acceptable if we're on the very last
                    // empty segment of a file with a trailing slash.
                    // a/* should match a/b/
                    var emptyFileEnd = (fi == file.Count - 1) && (file[fi] == "");
                    return(emptyFileEnd);
                }

                // should be unreachable.
                throw new InvalidOperationException("wtf?");
            }