public function getTreeId($appRoot)
    {
        $hashes = [];

        // Get a hash representing all the files in the application, excluding
        // the project config folder (configured in service.project_config_dir).
        $tree = $this->gitHelper->execute(['ls-files', '-s'], $appRoot);
        if ($tree === false) {
            return false;
        }
        $tree = preg_replace(
            '#^|\n[^\n]+?' . preg_quote($this->config->get('service.project_config_dir')) . '\n|$#',
            "\n",
            $tree
        );
        $hashes[] = sha1($tree);

        // Include the hashes of untracked and modified files.
        $others = $this->gitHelper->execute(
            [
                'ls-files',
                '--modified',
                '--others',
                '--exclude-standard',
                '-x ' . $this->config->get('service.project_config_dir'),
                '.',
            ],
            $appRoot
        );
        if ($others === false) {
            return false;
        }
        $count = 0;
        foreach (explode("\n", $others) as $filename) {
            if ($count > 5000) {
                return false;
            }
            $filename = "$appRoot/$filename";
            if (is_file($filename)) {
                $hashes[] = sha1_file($filename);
                $count++;
            }
        }

        // Include relevant build settings.
        $relevant = ['abslinks', 'copy', 'clone', 'no-cache', 'working-copy', 'lock', 'no-deps'];
        $settings = array_intersect_key($this->settings, array_flip($relevant));
        $hashes[] = serialize($settings);

        $hashes[] = self::BUILD_VERSION;

        // Combine them all.
        return sha1(implode(' ', $hashes));
    }