view tests/test-largefiles-cache.t @ 35569:964212780daf

rust: implementation of `hg` This commit provides a mostly-working implementation of the `hg` script in Rust along with scaffolding to support Rust in the repository. If you are familiar with Rust, the contents of the added rust/ directory should be pretty straightforward. We create an "hgcli" package that implements a binary application to run Mercurial. The output of this package is an "hg" binary. Our Rust `hg` (henceforth "rhg") essentially is a port of the existing `hg` Python script. The main difference is the creation of the embedded CPython interpreter is handled by the binary itself instead of relying on the shebang. In that sense, rhg is more similar to the "exe wrapper" we currently use on Windows. However, unlike the exe wrapper, rhg does not call the `hg` Python script. Instead, it uses the CPython APIs to import mercurial modules and call appropriate functions. The amount of code here is surprisingly small. It is my intent to replace the existing C-based exe wrapper with rhg. Preferably in the next Mercurial release. This should be achievable - at least for some Mercurial distributions. The future/timeline for rhg on other platforms is less clear. We already ship a hg.exe on Windows. So if we get the quirks with Rust worked out, shipping a Rust-based hg.exe should hopefully not be too contentious. Now onto the implementation. We're using python27-sys and the cpython crates for talking to the CPython API. We currently don't use too much functionality of the cpython crate and could have probably cut it out. However, it does provide a reasonable abstraction over unsafe {} CPython function calls. While we still have our fair share of those, at least we're not dealing with too much refcounting, error checking, etc. So I think the use of the cpython crate is justified. Plus, there is not-yet-implemented functionality that could benefit from cpython. I see our use of this crate only increasing. The cpython and python27-sys crates are not without their issues. The cpython crate didn't seem to account for the embedding use case in its design. Instead, it seems to assume that you are building a Python extension. It is making some questionable decisions around certain CPython APIs. For example, it insists that PyEval_ThreadsInitialized() is called and that the Python code likely isn't the main thread in the underlying application. It is also missing some functionality that is important for embedded use cases (such as exporting the path to the Python interpreter from its build script). After spending several hours trying to wrangle python27-sys and cpython, I gave up and forked the project on GitHub. Our Cargo.toml tracks this fork. I'm optimistic that the upstream project will accept our contributions and we can eventually unfork. There is a non-trivial amount of code in our custom Cargo build script. Our build.rs (which is called as part of building the hgcli crate): * Validates that the Python interpreter that was detected by the python27-sys crate provides a shared library (we only support shared library linking at this time - although this restriction could be loosened). * Validates that the Python is built with UCS-4 support. This ensures maximum Unicode compatibility. * Exports variables to the crate build allowing the built crate to e.g. find the path to the Python interpreter. The produced rhg should be considered alpha quality. There are several known deficiencies. Many of these are documented with inline TODOs. Probably the biggest limitation of rhg is that it assumes it is running from the ./rust/target/<target> directory of a source distribution. So, rhg is currently not very practical for real-world use. But, if you can `cargo build` it, running the binary *should* yield a working Mercurial CLI. In order to support using rhg with the test harness, we needed to hack up run-tests.py so the path to Mercurial's Python files is set properly. The change is extremely hacky and is only intended to be a stop-gap until the test harness gains first-class support for installing rhg. This will likely occur after we support running rhg outside the source directory. Despite its officially alpha quality, rhg copes extremely well with the test harness (at least on Linux). Using `run-tests.py --with-hg ../rust/target/debug/hg`, I only encounter the following failures: * test-run-tests.t -- Warnings emitted about using an unexpected Mercurial library. This is due to the hacky nature of setting the Python directory when run-tests.py detected rhg. * test-devel-warnings.t -- Expected stack trace missing frame for `hg` (This is expected since we no longer have an `hg` script!) * test-convert.t -- Test running `$PYTHON "$BINDIR"/hg`, which obviously assumes `hg` is a Python script. * test-merge-tools.t -- Same assumption about `hg` being executable with Python. * test-http-bad-server.t -- Seeing exit code 255 instead of 1 around line 358. * test-blackbox.t -- Exit code 255 instead of 1. * test-basic.t -- Exit code 255 instead of 1. It certainly looks like we have a bug around exit code handling. I don't think it is severe enough to hold up review and landing of this initial implementation. Perfect is the enemy of good. Differential Revision: https://phab.mercurial-scm.org/D1581
author Gregory Szorc <gregory.szorc@gmail.com>
date Wed, 10 Jan 2018 08:53:22 -0800
parents 4441705b7111
children f4e84dfc06fd
line wrap: on
line source

Create user cache directory

  $ USERCACHE=`pwd`/cache; export USERCACHE
  $ cat <<EOF >> ${HGRCPATH}
  > [extensions]
  > hgext.largefiles=
  > [largefiles]
  > usercache=${USERCACHE}
  > EOF
  $ mkdir -p ${USERCACHE}

Create source repo, and commit adding largefile.

  $ hg init src
  $ cd src
  $ echo large > large
  $ hg add --large large
  $ hg commit -m 'add largefile'
  $ hg rm large
  $ hg commit -m 'branchhead without largefile' large
  $ hg up -qr 0
  $ rm large
  $ echo "0000000000000000000000000000000000000000" > .hglf/large
  $ hg commit -m 'commit missing file with corrupt standin' large
  abort: large: file not found!
  [255]
  $ hg up -Cqr 0
  $ cd ..

Discard all cached largefiles in USERCACHE

  $ rm -rf ${USERCACHE}

Create mirror repo, and pull from source without largefile:
"pull" is used instead of "clone" for suppression of (1) updating to
tip (= caching largefile from source repo), and (2) recording source
repo as "default" path in .hg/hgrc.

  $ hg init mirror
  $ cd mirror
  $ hg pull ../src
  pulling from ../src
  requesting all changes
  adding changesets
  adding manifests
  adding file changes
  added 2 changesets with 1 changes to 1 files
  new changesets eb85d9124f3f:26c18ce05e4e
  (run 'hg update' to get a working copy)

Update working directory to "tip", which requires largefile("large"),
but there is no cache file for it.  So, hg must treat it as
"missing"(!) file.

  $ hg update -r0
  getting changed largefiles
  large: largefile 7f7097b041ccf68cc5561e9600da4655d21c6d18 not available from file:/*/$TESTTMP/mirror (glob)
  0 largefiles updated, 0 removed
  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
  $ hg status
  ! large

Update working directory to null: this cleanup .hg/largefiles/dirstate

  $ hg update null
  getting changed largefiles
  0 largefiles updated, 0 removed
  0 files updated, 0 files merged, 1 files removed, 0 files unresolved

Update working directory to tip, again.

  $ hg update -r0
  getting changed largefiles
  large: largefile 7f7097b041ccf68cc5561e9600da4655d21c6d18 not available from file:/*/$TESTTMP/mirror (glob)
  0 largefiles updated, 0 removed
  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
  $ hg status
  ! large
  $ cd ..

Verify that largefiles from pulled branchheads are fetched, also to an empty repo

  $ hg init mirror2
  $ hg -R mirror2 pull src -r0
  pulling from src
  adding changesets
  adding manifests
  adding file changes
  added 1 changesets with 1 changes to 1 files
  new changesets eb85d9124f3f
  (run 'hg update' to get a working copy)

#if unix-permissions

Portable way to print file permissions:

  $ cat > ls-l.py <<EOF
  > #!$PYTHON
  > from __future__ import absolute_import, print_function
  > import os
  > import sys
  > path = sys.argv[1]
  > print('%03o' % (os.lstat(path).st_mode & 0o777))
  > EOF
  $ chmod +x ls-l.py

Test that files in .hg/largefiles inherit mode from .hg/store, not
from file in working copy:

  $ cd src
  $ chmod 750 .hg/store
  $ chmod 660 large
  $ echo change >> large
  $ hg commit -m change
  created new head
  $ ../ls-l.py .hg/largefiles/e151b474069de4ca6898f67ce2f2a7263adf8fea
  640

Test permission of with files in .hg/largefiles created by update:

  $ cd ../mirror
  $ rm -r "$USERCACHE" .hg/largefiles # avoid links
  $ chmod 750 .hg/store
  $ hg pull ../src --update -q
  $ ../ls-l.py .hg/largefiles/e151b474069de4ca6898f67ce2f2a7263adf8fea
  640

Test permission of files created by push:

  $ hg serve -R ../src -d -p $HGPORT --pid-file hg.pid \
  >          --config "web.allow_push=*" --config web.push_ssl=no
  $ cat hg.pid >> $DAEMON_PIDS

  $ echo change >> large
  $ hg commit -m change

  $ rm -r "$USERCACHE"

  $ hg push -q http://localhost:$HGPORT/

  $ ../ls-l.py ../src/.hg/largefiles/b734e14a0971e370408ab9bce8d56d8485e368a9
  640

  $ cd ..

#endif

Test issue 4053 (remove --after on a deleted, uncommitted file shouldn't say
it is missing, but a remove on a nonexistent unknown file still should.  Same
for a forget.)

  $ cd src
  $ touch x
  $ hg add x
  $ mv x y
  $ hg remove -A x y ENOENT
  ENOENT: * (glob)
  not removing y: file is untracked
  [1]
  $ hg add y
  $ mv y z
  $ hg forget y z ENOENT
  ENOENT: * (glob)
  not removing z: file is already untracked
  [1]

Largefiles are accessible from the share's store
  $ cd ..
  $ hg share -q src share_dst --config extensions.share=
  $ hg -R share_dst update -r0
  getting changed largefiles
  1 largefiles updated, 0 removed
  1 files updated, 0 files merged, 0 files removed, 0 files unresolved

  $ echo modified > share_dst/large
  $ hg -R share_dst ci -m modified
  created new head

Only dirstate is in the local store for the share, and the largefile is in the
share source's local store.  Avoid the extra largefiles added in the unix
conditional above.
  $ hash=`hg -R share_dst cat share_dst/.hglf/large`
  $ echo $hash
  e2fb5f2139d086ded2cb600d5a91a196e76bf020

  $ find share_dst/.hg/largefiles/* | sort
  share_dst/.hg/largefiles/dirstate

  $ find src/.hg/largefiles/* | egrep "(dirstate|$hash)" | sort
  src/.hg/largefiles/dirstate
  src/.hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020

Verify that backwards compatibility is maintained for old storage layout
  $ mv src/.hg/largefiles/$hash share_dst/.hg/largefiles
  $ hg verify --quiet --lfa -R share_dst --config largefiles.usercache=

Inject corruption into the largefiles store and see how update handles that:

  $ cd src
  $ hg up -qC tip
  $ cat large
  modified
  $ rm large
  $ cat .hglf/large
  e2fb5f2139d086ded2cb600d5a91a196e76bf020
  $ mv .hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020 ..
  $ echo corruption > .hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020
  $ hg up -C
  getting changed largefiles
  large: data corruption in $TESTTMP/src/.hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020 with hash 6a7bb2556144babe3899b25e5428123735bb1e27
  0 largefiles updated, 0 removed
  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
  updated to "cd24c147f45c: modified"
  [12] other heads for branch "default" (re)
  $ hg st
  ! large
  ? z
  $ rm .hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020

#if serve

Test coverage of error handling from putlfile:

  $ mkdir $TESTTMP/mirrorcache
  $ hg serve -R ../mirror -d -p $HGPORT1 --pid-file hg.pid --config largefiles.usercache=$TESTTMP/mirrorcache
  $ cat hg.pid >> $DAEMON_PIDS

  $ hg push http://localhost:$HGPORT1 -f --config files.usercache=nocache
  pushing to http://localhost:$HGPORT1/
  searching for changes
  abort: remotestore: could not open file $TESTTMP/src/.hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020: HTTP Error 403: ssl required
  [255]

  $ rm .hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020

Test coverage of 'missing from store':

  $ hg serve -R ../mirror -d -p $HGPORT2 --pid-file hg.pid --config largefiles.usercache=$TESTTMP/mirrorcache --config "web.allow_push=*" --config web.push_ssl=no
  $ cat hg.pid >> $DAEMON_PIDS

  $ hg push http://localhost:$HGPORT2 -f --config largefiles.usercache=nocache
  pushing to http://localhost:$HGPORT2/
  searching for changes
  abort: largefile e2fb5f2139d086ded2cb600d5a91a196e76bf020 missing from store (needs to be uploaded)
  [255]

Verify that --lfrev controls which revisions are checked for largefiles to push

  $ hg push http://localhost:$HGPORT2 -f --config largefiles.usercache=nocache --lfrev tip
  pushing to http://localhost:$HGPORT2/
  searching for changes
  abort: largefile e2fb5f2139d086ded2cb600d5a91a196e76bf020 missing from store (needs to be uploaded)
  [255]

  $ hg push http://localhost:$HGPORT2 -f --config largefiles.usercache=nocache --lfrev null
  pushing to http://localhost:$HGPORT2/
  searching for changes
  remote: adding changesets
  remote: adding manifests
  remote: adding file changes
  remote: added 1 changesets with 1 changes to 1 files (+1 heads)

#endif