Thanks to visit codestin.com
Credit goes to github.com

Skip to content

GitDir behaves inconsistently when the provided path contains one or more symlinks #88

@coltrane

Description

@coltrane

Problem

  1. When I call GitDir.fromExisting(path) where path is a symlink that points directly to the root of the work tree...

    ACTUAL: The operation succeeds, but the resulting gitDir.path contains the unresolved symlink path.

    EXPECTED: The operation succeeds, and gitDir.path contains the absolutized, canonicalized path with all symlinks resolved.

    REASONING: All paths returned by git rev-parse are canonicalized, and have symlinks resolved. This is true for both relative and absolute paths it returns. This is a required piece of the way git works, because it's the only way to reliably manipulate and compare paths on the scale git needs to. It makes sense that GitDir would work the same way -- canonicalizing and removing symlinks so that the worktree path stored in GitDir matches the path that git reports. Judging from the code, I believe this is exactly what it was intended to do. So I'd classify this as a minor issue, currently causing inconsistent behavior when working with symlinks.

  2. When I call GitDir.fromExisting(path, allowSubdirectory: true) where path is a symlink that points down into a sub-directory within the work tree (not the root of the work tree)...

    ACTUAL: The operation fails with an exception indicating that "The provided value 'my-repo/path/to/some/dir' is not the root of a git directory". The failure and message is non-sensical since allowSubdirectory: true was set, and we intentionally passed a sub-directory, not the root. The same operation succeeds if we resolve all symlinks in our path and try again.

    EXPECTED: The operation should succeed and work the same way, for both the symlink path, and the target path with symlinks resolved.

    REASONING: For consistency, GitDir should produce the same result whether constructed with a symlink or a fully resolved path, as long as the target directory is the same in both cases. Also, all of the reasoning above, from #1, also applies here.

Example

These test cases cover both situations described above.

import 'dart:io';

import 'package:git/git.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;

Future<void> main() async {
  group('creating GitDir with a symlink', () {
    late GitDir repo;

    setUp(() async {
      // create a regular directory, and create a GitDir there.
      Directory.current = Directory(d.sandbox);
      final dir = await Directory('repo').create(recursive: true);
      repo = await GitDir.init(dir.path);
    });

    test('calling fromExisting() with a link to worktree root', () async {
      // create a symlink that points to worktree's root
      Directory.current = Directory(d.sandbox);
      final linkToRepoDir = await Link('link-to-root').create(repo.path);

      final repoFromLink = await GitDir.fromExisting(linkToRepoDir.path);
      expect(repo.path, repoFromLink.path,   // <------------------------------------------- FAIL 1
          reason: 'GitDir should resolve symlinks');
    });

    test('calling fromExisting() with a link to inside worktree', () async {
      // create a directory tree within worktree
      Directory.current = Directory(repo.path);
      await Directory('path/within/work/tree').create(recursive: true);

      // create a symlink that points inside the worktree below the root
      Directory.current = Directory(d.sandbox);
      final linkDestinationPath = p.join(repo.path, 'path/within/work');
      final linkToRepoDir =
          await Link('link-to-child').create(linkDestinationPath);

      // make sure this path works correctly when it's not a symlink
      final repoFromDirectPath = await GitDir.fromExisting(linkDestinationPath,  // <------- FAIL 2
          allowSubdirectory: true);
      expect(
        repo.path,
        repoFromDirectPath.path,
        reason: 'GitDir should point to the tree\'s root',
      );

      // now try the same thing using a symlink to the same location
      final repoFromLink = await GitDir.fromExisting(linkToRepoDir.path,
          allowSubdirectory: true);
      expect(
        repo.path,
        repoFromLink.path,
        reason: 'GitDir should resolve symlinks, and point to the tree\'s root',
      );
    });
  });
}

OUTPUT

00:01 +0 -1: creating GitDir with a symlink calling fromExisting() with a link to worktree root [E]                                                                                                               
  Expected: '/private/var/folders/pm/74183vl91990j7tbzgtcvn7r0000gn/T/dart_test_dadLYd/link-to-root'
    Actual: '/private/var/folders/pm/74183vl91990j7tbzgtcvn7r0000gn/T/dart_test_dadLYd/repo'
     Which: is different.
            Expected: ... st_dadLYd/link-to-ro ...
              Actual: ... st_dadLYd/repo
                                    ^
             Differ at offset 74
  GitDir should resolve symlinks
  
  package:matcher          expect
  test/_symlink.dart 25:7  main.<fn>.<fn>
00:01 +0 -2: creating GitDir with a symlink calling fromExisting() with a link to inside worktree [E]                                                                                                             
  Invalid argument(s): The provided value "link-to-child" is not the root of a git directory
  package:git/src/git_dir.dart 481:5  GitDir.fromExisting

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions