diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -215,6 +215,30 @@ def canonpath(root, cwd, myname): elif name == root: return '' else: + # Determine whether `name' is in the hierarchy at or beneath `root', + # by iterating name=dirname(name) until that causes no change (can't + # check name == '/', because that doesn't work on windows). For each + # `name', compare dev/inode numbers. If they match, the list `rel' + # holds the reversed list of components making up the relative file + # name we want. + root_st = os.stat(root) + rel = [] + while True: + try: + name_st = os.stat(name) + except OSError: + break + if os.path.samestat(name_st, root_st): + rel.reverse() + name = os.path.join(*rel) + audit_path(name) + return pconvert(name) + dirname, basename = os.path.split(name) + rel.append(basename) + if dirname == name: + break + name = dirname + raise Abort('%s not under root' % myname) def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None): diff --git a/tests/test-symlinks b/tests/test-symlinks --- a/tests/test-symlinks +++ b/tests/test-symlinks @@ -40,3 +40,18 @@ mkfifo a.c # it should show a.c, dir/a.o and dir/b.o deleted hg status hg status a.c + +echo '# test absolute path through symlink outside repo' +cd .. +p=`pwd` +hg init x +ln -s x y +cd x +touch f +hg add f +hg status $p/y/f + +echo '# try symlink outside repo to file inside' +ln -s x/f ../z +# this should fail +hg status ../z && { echo hg mistakenly exited with status 0; exit 1; } || : diff --git a/tests/test-symlinks.out b/tests/test-symlinks.out --- a/tests/test-symlinks.out +++ b/tests/test-symlinks.out @@ -9,3 +9,7 @@ adding dir/b.o ? .hgignore a.c: unsupported file type (type is fifo) ! a.c +# test absolute path through symlink outside repo +A f +# try symlink outside repo to file inside +abort: ../z not under root