#!/usr/bin/env bash

. "$(dirname "$0")/fixtures/migrate.sh"
. "$(dirname "$0")/testlib.sh"

begin_test "migrate import (default branch)"
(
  set -e

  setup_multiple_local_branches

  md_oid="$(calc_oid "$(git cat-file -p :a.md)")"
  txt_oid="$(calc_oid "$(git cat-file -p :a.txt)")"
  md_feature_oid="$(calc_oid "$(git cat-file -p my-feature:a.md)")"

  git lfs migrate import

  assert_pointer "refs/heads/main" "a.md" "$md_oid" "140"
  assert_pointer "refs/heads/main" "a.txt" "$txt_oid" "120"

  assert_local_object "$md_oid" "140"
  assert_local_object "$txt_oid" "120"
  refute_local_object "$md_feature_oid" "30"

  main="$(git rev-parse refs/heads/main)"
  feature="$(git rev-parse refs/heads/my-feature)"

  main_attrs="$(git cat-file -p "$main:.gitattributes")"
  [ -z "$(git cat-file -p "$feature:.gitattributes")" ]

  echo "$main_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
  echo "$main_attrs" | grep -q "*.txt filter=lfs diff=lfs merge=lfs"

  # Ensure that hooks are installed. If we find 'git-lfs' somewhere in
  # .git/hooks/pre-push we assume that the rest went correctly, too.
  grep -q "git-lfs" .git/hooks/pre-push
)
end_test

begin_test "migrate import (bare repository)"
(
  set -e

  setup_multiple_remote_branches

  git lfs migrate import --everything
)
end_test

begin_test "migrate import (given branch)"
(
  set -e

  setup_multiple_local_branches

  md_oid="$(calc_oid "$(git cat-file -p :a.md)")"
  txt_oid="$(calc_oid "$(git cat-file -p :a.txt)")"
  md_feature_oid="$(calc_oid "$(git cat-file -p my-feature:a.md)")"

  git lfs migrate import my-feature

  assert_pointer "refs/heads/my-feature" "a.md" "$md_feature_oid" "30"
  assert_pointer "refs/heads/my-feature" "a.txt" "$txt_oid" "120"
  assert_pointer "refs/heads/main" "a.md" "$md_oid" "140"
  assert_pointer "refs/heads/main" "a.txt" "$txt_oid" "120"

  assert_local_object "$md_oid" "140"
  assert_local_object "$md_feature_oid" "30"
  assert_local_object "$txt_oid" "120"

  main="$(git rev-parse refs/heads/main)"
  feature="$(git rev-parse refs/heads/my-feature)"

  main_attrs="$(git cat-file -p "$main:.gitattributes")"
  feature_attrs="$(git cat-file -p "$feature:.gitattributes")"

  echo "$main_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
  echo "$main_attrs" | grep -q "*.txt filter=lfs diff=lfs merge=lfs"
  echo "$feature_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
  echo "$feature_attrs" | grep -q "*.txt filter=lfs diff=lfs merge=lfs"
)
end_test

begin_test "migrate import (default branch with filter)"
(
  set -e

  setup_multiple_local_branches

  md_oid="$(calc_oid "$(git cat-file -p :a.md)")"
  txt_oid="$(calc_oid "$(git cat-file -p :a.txt)")"
  md_feature_oid="$(calc_oid "$(git cat-file -p my-feature:a.md)")"

  git lfs migrate import --include "*.md"

  assert_pointer "refs/heads/main" "a.md" "$md_oid" "140"

  assert_local_object "$md_oid" "140"
  refute_local_object "$txt_oid" "120"
  refute_local_object "$md_feature_oid" "30"

  main="$(git rev-parse refs/heads/main)"
  feature="$(git rev-parse refs/heads/my-feature)"

  main_attrs="$(git cat-file -p "$main:.gitattributes")"
  [ -z "$(git cat-file -p "$feature:.gitattributes")" ]

  echo "$main_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
  echo "$main_attrs" | grep -vq "*.txt filter=lfs diff=lfs merge=lfs"
)
end_test

begin_test "migrate import (given branch with filter)"
(
  set -e

  setup_multiple_local_branches

  md_oid="$(calc_oid "$(git cat-file -p :a.md)")"
  txt_oid="$(calc_oid "$(git cat-file -p :a.txt)")"
  md_feature_oid="$(calc_oid "$(git cat-file -p my-feature:a.md)")"

  git lfs migrate import --include "*.md" my-feature

  assert_pointer "refs/heads/my-feature" "a.md" "$md_feature_oid" "30"
  assert_pointer "refs/heads/my-feature~1" "a.md" "$md_oid" "140"

  assert_local_object "$md_oid" "140"
  assert_local_object "$md_feature_oid" "30"
  refute_local_object "$txt_oid" "120"

  main="$(git rev-parse refs/heads/main)"
  feature="$(git rev-parse refs/heads/my-feature)"

  main_attrs="$(git cat-file -p "$main:.gitattributes")"
  feature_attrs="$(git cat-file -p "$feature:.gitattributes")"

  echo "$main_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
  echo "$main_attrs" | grep -vq "*.txt filter=lfs diff=lfs merge=lfs"
  echo "$feature_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
  echo "$feature_attrs" | grep -vq "*.txt filter=lfs diff=lfs merge=lfs"
)
end_test

begin_test "migrate import (default branch, exclude remote refs)"
(
  set -e

  setup_single_remote_branch

  md_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/main:a.md")")"
  txt_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/main:a.txt")")"
  md_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.md")")"
  txt_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"

  git lfs migrate import

  assert_pointer "refs/heads/main" "a.md" "$md_oid" "50"
  assert_pointer "refs/heads/main" "a.txt" "$txt_oid" "30"

  assert_local_object "$md_oid" "50"
  assert_local_object "$txt_oid" "30"
  refute_local_object "$md_remote_oid" "140"
  refute_local_object "$txt_remote_oid" "120"

  main="$(git rev-parse refs/heads/main)"
  remote="$(git rev-parse refs/remotes/origin/main)"

  main_attrs="$(git cat-file -p "$main:.gitattributes")"
  [ -z "$(git cat-file -p "$remote:.gitattributes")" ]

  echo "$main_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
  echo "$main_attrs" | grep -vq "*.txt filter=lfs diff=lfs merge=lfs"
)
end_test

begin_test "migrate import (given branch, exclude remote refs)"
(
  set -e

  setup_multiple_remote_branches

  md_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.md")")"
  md_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/main:a.md")")"
  md_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.md")")"
  txt_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"
  txt_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/main:a.txt")")"
  txt_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.txt")")"

  git lfs migrate import my-feature

  assert_pointer "refs/heads/main" "a.md" "$md_main_oid" "21"
  assert_pointer "refs/heads/my-feature" "a.md" "$md_feature_oid" "31"
  assert_pointer "refs/heads/main" "a.txt" "$txt_main_oid" "20"
  assert_pointer "refs/heads/my-feature" "a.txt" "$txt_feature_oid" "30"

  assert_local_object "$md_feature_oid" "31"
  assert_local_object "$md_main_oid" "21"
  assert_local_object "$txt_feature_oid" "30"
  assert_local_object "$txt_main_oid" "20"
  refute_local_object "$md_remote_oid" "11"
  refute_local_object "$txt_remote_oid" "10"

  main="$(git rev-parse refs/heads/main)"
  feature="$(git rev-parse refs/heads/my-feature)"
  remote="$(git rev-parse refs/remotes/origin/main)"

  main_attrs="$(git cat-file -p "$main:.gitattributes")"
  [ -z "$(git cat-file -p "$remote:.gitattributes")" ]
  feature_attrs="$(git cat-file -p "$feature:.gitattributes")"

  echo "$main_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
  echo "$main_attrs" | grep -q "*.txt filter=lfs diff=lfs merge=lfs"
  echo "$feature_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
  echo "$feature_attrs" | grep -vq "*.txt filter=lfs diff=lfs merge=lfs"
)
end_test

begin_test "migrate import (given ref, --skip-fetch)"
(
  set -e

  setup_single_remote_branch

  md_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.md")")"
  md_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/main:a.md")")"
  txt_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"
  txt_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/main:a.txt")")"

  git tag pseudo-remote "$(git rev-parse refs/remotes/origin/main)"
  # Remove the refs/remotes/origin/main ref, and instruct 'git lfs migrate' to
  # not fetch it.
  git update-ref -d refs/remotes/origin/main

  git lfs migrate import --skip-fetch

  assert_pointer "refs/heads/main" "a.md" "$md_main_oid" "50"
  assert_pointer "pseudo-remote" "a.md" "$md_remote_oid" "140"
  assert_pointer "refs/heads/main" "a.txt" "$txt_main_oid" "30"
  assert_pointer "pseudo-remote" "a.txt" "$txt_remote_oid" "120"

  assert_local_object "$md_main_oid" "50"
  assert_local_object "$txt_main_oid" "30"
  assert_local_object "$md_remote_oid" "140"
  assert_local_object "$txt_remote_oid" "120"

  main="$(git rev-parse refs/heads/main)"
  remote="$(git rev-parse pseudo-remote)"

  main_attrs="$(git cat-file -p "$main:.gitattributes")"
  remote_attrs="$(git cat-file -p "$remote:.gitattributes")"

  echo "$main_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
  echo "$main_attrs" | grep -q "*.txt filter=lfs diff=lfs merge=lfs"
  echo "$remote_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
  echo "$remote_attrs" | grep -q "*.txt filter=lfs diff=lfs merge=lfs"
)
end_test

begin_test "migrate import (un-annotated tags)"
(
  set -e

  setup_single_local_branch_with_tags

  txt_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"

  git lfs migrate import --everything

  assert_pointer "refs/heads/main" "a.txt" "$txt_main_oid" "2"
  assert_local_object "$txt_main_oid" "2"

  git tag --points-at "$(git rev-parse HEAD)" | grep -q "v1.0.0"
)
end_test

begin_test "migrate import (annotated tags)"
(
  set -e

  setup_single_local_branch_with_annotated_tags

  txt_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"

  git lfs migrate import --everything

  assert_pointer "refs/heads/main" "a.txt" "$txt_main_oid" "2"
  assert_local_object "$txt_main_oid" "2"

  git tag --points-at "$(git rev-parse HEAD)" | grep -q "v1.0.0"
)
end_test

begin_test "migrate import (include/exclude ref)"
(
  set -e

  setup_multiple_remote_branches

  md_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.md")")"
  md_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/main:a.md")")"
  md_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.md")")"
  txt_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"
  txt_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/main:a.txt")")"
  txt_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.txt")")"

  git lfs migrate import \
    --include-ref=refs/heads/my-feature \
    --exclude-ref=refs/heads/main

  assert_pointer "refs/heads/my-feature" "a.md" "$md_feature_oid" "31"
  assert_pointer "refs/heads/my-feature" "a.txt" "$txt_feature_oid" "30"

  assert_local_object "$md_feature_oid" "31"
  refute_local_object "$md_main_oid" "21"
  assert_local_object "$txt_feature_oid" "30"
  refute_local_object "$txt_main_oid" "20"
  refute_local_object "$md_remote_oid" "11"
  refute_local_object "$txt_remote_oid" "10"

  main="$(git rev-parse refs/heads/main)"
  feature="$(git rev-parse refs/heads/my-feature)"
  remote="$(git rev-parse refs/remotes/origin/main)"

  [ -z "$(git cat-file -p "$main:.gitattributes")" ]
  [ -z "$(git cat-file -p "$remote:.gitattributes")" ]
  feature_attrs="$(git cat-file -p "$feature:.gitattributes")"

  echo "$feature_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
  echo "$feature_attrs" | grep -q "*.txt filter=lfs diff=lfs merge=lfs"
)
end_test

begin_test "migrate import (include/exclude ref args)"
(
  set -e

  setup_multiple_remote_branches

  md_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.md")")"
  md_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/main:a.md")")"
  md_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.md")")"
  txt_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"
  txt_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/main:a.txt")")"
  txt_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.txt")")"

  git lfs migrate import my-feature ^main

  assert_pointer "refs/heads/my-feature" "a.md" "$md_feature_oid" "31"
  assert_pointer "refs/heads/my-feature" "a.txt" "$txt_feature_oid" "30"

  assert_local_object "$md_feature_oid" "31"
  refute_local_object "$md_main_oid" "21"
  assert_local_object "$txt_feature_oid" "30"
  refute_local_object "$txt_main_oid" "20"
  refute_local_object "$md_remote_oid" "11"
  refute_local_object "$txt_remote_oid" "10"

  main="$(git rev-parse refs/heads/main)"
  feature="$(git rev-parse refs/heads/my-feature)"
  remote="$(git rev-parse refs/remotes/origin/main)"

  [ -z "$(git cat-file -p "$main:.gitattributes")" ]
  [ -z "$(git cat-file -p "$remote:.gitattributes")" ]
  feature_attrs="$(git cat-file -p "$feature:.gitattributes")"

  echo "$feature_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
  echo "$feature_attrs" | grep -q "*.txt filter=lfs diff=lfs merge=lfs"
)
end_test

begin_test "migrate import (include/exclude ref with filter)"
(
  set -e

  setup_multiple_remote_branches

  md_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.md")")"
  md_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/main:a.md")")"
  md_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.md")")"
  txt_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"
  txt_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/main:a.txt")")"
  txt_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.txt")")"

  git lfs migrate import \
    --include="*.txt" \
    --include-ref=refs/heads/my-feature \
    --exclude-ref=refs/heads/main

  assert_pointer "refs/heads/my-feature" "a.txt" "$txt_feature_oid" "30"

  refute_local_object "$md_feature_oid" "31"
  refute_local_object "$md_main_oid" "21"
  assert_local_object "$txt_feature_oid" "30"
  refute_local_object "$txt_main_oid" "20"
  refute_local_object "$md_remote_oid" "11"
  refute_local_object "$txt_remote_oid" "10"

  main="$(git rev-parse refs/heads/main)"
  feature="$(git rev-parse refs/heads/my-feature)"
  remote="$(git rev-parse refs/remotes/origin/main)"

  [ -z "$(git cat-file -p "$main:.gitattributes")" ]
  [ -z "$(git cat-file -p "$remote:.gitattributes")" ]
  feature_attrs="$(git cat-file -p "$feature:.gitattributes")"

  echo "$feature_attrs" | grep -vq "*.md filter=lfs diff=lfs merge=lfs"
  echo "$feature_attrs" | grep -q "*.txt filter=lfs diff=lfs merge=lfs"
)
end_test

begin_test "migrate import (invalid ref)"
(
  set -e
  remove_and_create_local_repo "migrate-import-invalid-ref"
  git commit --allow-empty -m "initial commit"

  git lfs migrate import --yes jibberish >migrate.log 2>&1 && exit 1
  grep "can't resolve ref" migrate.log
)
end_test

begin_test "migrate import (above)"
(
  set -e
  setup_single_local_branch_untracked

  md_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.md")")"
  txt_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"

  git lfs migrate import --above 121B
  # Ensure that 'a.md', whose size is above our 121 byte threshold
  # was converted into a git-lfs pointer by the migration.
  assert_local_object "$md_main_oid" "140"
  assert_pointer "refs/heads/main" "a.md" "$md_main_oid" "140"
  refute_pointer "refs/heads/main" "a.txt" "$txt_main_oid" "120"
  refute_local_object "$txt_main_oid" "120"

  # The migration should have identified that *.md files are now
  # tracked because it migrated a.md
  main_attrs="$(git cat-file -p "$main:.gitattributes")"

  echo "$main_attrs" | grep -q "/a.md filter=lfs diff=lfs merge=lfs"
  echo "$main_attrs" | grep -vq "/a.txt filter=lfs diff=lfs merge=lfs"
  git check-attr filter -- a.txt | grep -vq lfs
)
end_test

begin_test "migrate import (above without extension)"
(
  set -e
  setup_single_local_branch_untracked "just-b"

  b_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:just-b")")"
  txt_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"

  git lfs migrate import --above 121B
  # Ensure that 'b', whose size is above our 121 byte threshold
  # was converted into a git-lfs pointer by the migration.
  assert_local_object "$b_main_oid" "140"
  assert_pointer "refs/heads/main" "just-b" "$b_main_oid" "140"
  refute_pointer "refs/heads/main" "a.txt" "$txt_main_oid" "120"
  refute_local_object "$txt_main_oid" "120"

  # The migration should have identified that /b is now tracked
  # because it migrated it.
  main_attrs="$(git cat-file -p "$main:.gitattributes")"

  echo "$main_attrs" | grep -q "/just-b filter=lfs diff=lfs merge=lfs"
  echo "$main_attrs" | grep -vq "/a.txt filter=lfs diff=lfs merge=lfs"
  git check-attr filter -- a.txt | grep -vq lfs
)
end_test

begin_test "migrate import (above with multiple files)"
(
  set -e
  # It is important that this file sort after "a.txt".
  setup_single_local_branch_untracked "b.txt"

  a_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"
  b_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:b.txt")")"

  git lfs migrate import --above 121B
  # Ensure that 'a.md', whose size is above our 121 byte threshold
  # was converted into a git-lfs pointer by the migration.
  assert_local_object "$b_main_oid" "140"
  assert_pointer "refs/heads/main" "b.txt" "$b_main_oid" "140"
  refute_pointer "refs/heads/main" "a.txt" "$a_main_oid" "120"
  refute_local_object "$a_main_oid" "120"

  # The migration should have identified that *.md files are now
  # tracked because it migrated a.md
  main_attrs="$(git cat-file -p "$main:.gitattributes")"

  echo "$main_attrs" | grep -q "/b.txt filter=lfs diff=lfs merge=lfs"
  git check-attr filter -- a.txt | grep -vq lfs
)
end_test

begin_test "migrate import (above with include or exclude)"
(
  set -e
  setup_single_local_branch_untracked

  md_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.md")")"
  txt_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"

  git lfs migrate import --above 121B --include "*.md" && exit 1
  git lfs migrate import --above 121B --exclude "*.txt" && exit 1
  git lfs migrate import --above 121B --fixup && exit 1
  true
)
end_test

begin_test "migrate import (existing .gitattributes)"
(
  set -e

  setup_local_branch_with_gitattrs

  main="$(git rev-parse refs/heads/main)"

  txt_main_oid="$(calc_oid "$(git cat-file -p "$main:a.txt")")"

  git lfs migrate import --yes --include-ref=refs/heads/main --include="*.txt"

  assert_local_object "$txt_main_oid" "120"

  main="$(git rev-parse refs/heads/main)"
  prev="$(git rev-parse refs/heads/main^1)"

  diff -u <(git cat-file -p $main:.gitattributes) <(cat <<-EOF
*.txt filter=lfs diff=lfs merge=lfs -text
*.other filter=lfs diff=lfs merge=lfs -text
EOF)

  diff -u <(git cat-file -p $prev:.gitattributes) <(cat <<-EOF
*.txt filter=lfs diff=lfs merge=lfs -text
EOF)
)
end_test

begin_test "migrate import (--exclude with existing .gitattributes)"
(
  set -e

  setup_local_branch_with_gitattrs

  main="$(git rev-parse refs/heads/main)"

  txt_main_oid="$(calc_oid "$(git cat-file -p "$main:a.txt")")"

  git lfs migrate import --yes --include-ref=refs/heads/main --include="*.txt" --exclude="*.bin"

  assert_local_object "$txt_main_oid" "120"

  main="$(git rev-parse refs/heads/main)"
  prev="$(git rev-parse refs/heads/main^1)"

  diff -u <(git cat-file -p $main:.gitattributes) <(cat <<-EOF
*.txt filter=lfs diff=lfs merge=lfs -text
*.other filter=lfs diff=lfs merge=lfs -text
*.bin !text -filter -merge -diff
EOF)

  diff -u <(git cat-file -p $prev:.gitattributes) <(cat <<-EOF
*.txt filter=lfs diff=lfs merge=lfs -text
*.bin !text -filter -merge -diff
EOF)
)
end_test

begin_test "migrate import (existing .gitattributes with different permissions)"
(
  set -e

  # Windows lacks POSIX permissions.
  [ "$IS_WINDOWS" -eq 1 ] && exit 0

  setup_local_branch_with_gitattrs 0755

  main="$(git rev-parse refs/heads/main)"

  txt_main_oid="$(calc_oid "$(git cat-file -p "$main:a.txt")")"

  [ -x .gitattributes ]

  git lfs migrate import --yes --include-ref=refs/heads/main --include="*.txt"

  [ ! -x .gitattributes ]

  assert_local_object "$txt_main_oid" "120"

  main="$(git rev-parse refs/heads/main)"
  prev="$(git rev-parse refs/heads/main^1)"

  diff -u <(git cat-file -p $main:.gitattributes) <(cat <<-EOF
*.txt filter=lfs diff=lfs merge=lfs -text
*.other filter=lfs diff=lfs merge=lfs -text
EOF
  )

  diff -u <(git cat-file -p $prev:.gitattributes) <(cat <<-EOF
*.txt filter=lfs diff=lfs merge=lfs -text
EOF
  )

  attrs_main_sha="$(git show $main:.gitattributes | git hash-object --stdin)"
  txt_main_sha="$(git show $main:a.txt | git hash-object --stdin)"

  diff -u <(git ls-tree $main) <(cat <<-EOF
100644 blob $attrs_main_sha	.gitattributes
100644 blob $txt_main_sha	a.txt
EOF
  )
)
end_test

begin_test "migrate import (existing .gitattributes symlink)"
(
  set -e

  setup_local_branch_with_gitattrs link

  git lfs migrate import --yes --include-ref=refs/heads/main --include="*.txt" 2>&1 | tee migrate.log
  if [ ${PIPESTATUS[0]} -eq 0 ]; then
    echo >&2 "fatal: expected git lfs migrate import to fail, didn't"
    exit 1
  fi

  grep "migrate: expected '.gitattributes' to be a file, got a symbolic link" migrate.log

  main="$(git rev-parse refs/heads/main)"

  attrs_main_sha="$(git show $main:.gitattributes | git hash-object --stdin)"

  diff -u <(git ls-tree $main -- .gitattributes) <(cat <<-EOF
120000 blob $attrs_main_sha	.gitattributes
EOF
  )
)
end_test

begin_test "migrate import (identical contents, different permissions)"
(
  set -e

  # Windows lacks POSIX permissions.
  [ "$IS_WINDOWS" -eq 1 ] && exit 0

  setup_multiple_local_branches
  git checkout main

  echo "foo" >foo.dat
  git add .
  git commit -m "add file"

  chmod u+x foo.dat
  git add .
  git commit -m "make file executable"

  # Verify we have executable permissions.
  [ -x foo.dat ]

  git lfs migrate import --everything --include="*.dat"

  # Verify we have executable permissions.
  [ -x foo.dat ]
)
end_test

begin_test "migrate import (tags with same name as branches)"
(
  set -e

  setup_multiple_local_branches
  git checkout main

  contents="hello"
  oid=$(calc_oid "$contents")
  printf "$contents" >hello.dat
  git add .
  git commit -m "add file"

  git branch foo
  git tag foo
  git tag bar

  git lfs migrate import --everything --include="*.dat"

  [ "$(git rev-parse refs/heads/foo)" = "$(git rev-parse refs/tags/foo)" ]
  [ "$(git rev-parse refs/heads/foo)" = "$(git rev-parse refs/tags/bar)" ]

  assert_pointer "refs/heads/foo" hello.dat "$oid" 5
)
end_test

begin_test "migrate import (bare repository)"
(
  set -e

  setup_multiple_local_branches
  make_bare

  git lfs migrate import \
    --include-ref=main
)
end_test

begin_test "migrate import (nested sub-trees, no filter)"
(
  set -e

  setup_single_local_branch_deep_trees

  oid="$(calc_oid "$(git cat-file -p :foo/bar/baz/a.txt)")"
  size="$(git cat-file -p :foo/bar/baz/a.txt | wc -c | awk '{ print $1 }')"

  git lfs migrate import --everything

  assert_local_object "$oid" "$size"
)
end_test

begin_test "migrate import (prefix include(s))"
(
  set -e

  includes="foo/bar/baz/** foo/**/baz/a.txt *.txt"
  for include in $includes; do
    setup_single_local_branch_deep_trees

    oid="$(calc_oid "$(git cat-file -p :foo/bar/baz/a.txt)")"

    git lfs migrate import --include="$include"

    assert_local_object "$oid" 120

    cd ..
  done
)
end_test

begin_test "migrate import (--everything)"
(
  set -e

  setup_multiple_local_branches
  git checkout main

  main_txt_oid="$(calc_oid "$(git cat-file -p :a.txt)")"
  main_md_oid="$(calc_oid "$(git cat-file -p :a.md)")"
  feature_md_oid="$(calc_oid "$(git cat-file -p my-feature:a.md)")"
  main_txt_size="$(git cat-file -p :a.txt | wc -c | awk '{ print $1 }')"
  main_md_size="$(git cat-file -p :a.md | wc -c | awk '{ print $1 }')"
  feature_md_size="$(git cat-file -p my-feature:a.md | wc -c | awk '{ print $1 }')"

  git lfs migrate import --everything

  assert_pointer "main" "a.txt" "$main_txt_oid" "$main_txt_size"
  assert_pointer "main" "a.md" "$main_md_oid" "$main_md_size"
  assert_pointer "my-feature" "a.md" "$feature_md_oid" "$feature_md_size"
)
end_test

begin_test "migrate import (ambiguous reference)"
(
  set -e

  setup_multiple_local_branches

  # Create an ambiguously named reference sharing the name as the SHA-1 of
  # "HEAD".
  sha="$(git rev-parse HEAD)"
  git tag "$sha"

  git lfs migrate import --everything
)
end_test

begin_test "migrate import (--everything with args)"
(
  set -e

  setup_multiple_local_branches

  [ "$(git lfs migrate import --everything main 2>&1)" = \
    "Cannot use --everything with explicit reference arguments" ]
)
end_test

begin_test "migrate import (--everything with --include-ref)"
(
  set -e

  setup_multiple_local_branches

  [ "$(git lfs migrate import --everything --include-ref=refs/heads/main 2>&1)" = \
    "Cannot use --everything with --include-ref or --exclude-ref" ]
)
end_test

begin_test "migrate import (--everything with --exclude-ref)"
(
  set -e

  setup_multiple_local_branches

  [ "$(git lfs migrate import --everything --exclude-ref=refs/heads/main 2>&1)" = \
    "Cannot use --everything with --include-ref or --exclude-ref" ]
)
end_test

begin_test "migrate import (--everything and --include with glob pattern)"
(
  set -e

  setup_multiple_local_branches

  md_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.md")")"
  txt_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"
  md_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.md")")"
  txt_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.txt")")"

  git lfs migrate import --verbose --everything --include='*.[mM][dD]'

  assert_pointer "refs/heads/main" "a.md" "$md_main_oid" "140"
  assert_pointer "refs/heads/my-feature" "a.md" "$md_feature_oid" "30"

  assert_local_object "$md_main_oid" "140"
  assert_local_object "$md_feature_oid" "30"
  refute_local_object "$txt_main_oid"
  refute_local_object "$txt_feature_oid"
)
end_test

begin_test "migrate import (--everything with tag pointing to tag)"
(
  set -e

  setup_multiple_local_branches

  md_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.md")")"
  txt_main_oid="$(calc_oid "$(git cat-file -p "refs/heads/main:a.txt")")"
  md_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.md")")"
  txt_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.txt")")"

  git tag -a -m abc abc refs/heads/main
  git tag -a -m def def refs/tags/abc

  git lfs migrate import --verbose --everything --include='*.[mM][dD]'

  assert_pointer "refs/heads/main" "a.md" "$md_main_oid" "140"
  assert_pointer "refs/tags/abc" "a.md" "$md_main_oid" "140"
  assert_pointer "refs/tags/def" "a.md" "$md_main_oid" "140"
  assert_pointer "refs/heads/my-feature" "a.md" "$md_feature_oid" "30"

  git tag --points-at refs/tags/abc | grep -q def
  git tag --points-at refs/tags/def | grep -q abc && exit 1

  assert_local_object "$md_main_oid" "140"
  assert_local_object "$md_feature_oid" "30"
  refute_local_object "$txt_main_oid"
  refute_local_object "$txt_feature_oid"
)
end_test

begin_test "migrate import (nested sub-trees and --include with wildcard)"
(
  set -e

  setup_single_local_branch_deep_trees

  oid="$(calc_oid "$(git cat-file -p :foo/bar/baz/a.txt)")"
  size="$(git cat-file -p :foo/bar/baz/a.txt | wc -c | awk '{ print $1 }')"

  git lfs migrate import --include="**/*ar/**"

  assert_pointer "refs/heads/main" "foo/bar/baz/a.txt" "$oid" "$size"
  assert_local_object "$oid" "$size"
)
end_test

begin_test "migrate import (handle copies of files)"
(
  set -e

  setup_single_local_branch_deep_trees

  # add the object from the sub-tree to the root directory
  cp foo/bar/baz/a.txt a.txt
  git add a.txt
  git commit -m "duplicated file"

  oid_root="$(calc_oid "$(git cat-file -p :a.txt)")"
  oid_tree="$(calc_oid "$(git cat-file -p :foo/bar/baz/a.txt)")"
  size="$(git cat-file -p :foo/bar/baz/a.txt | wc -c | awk '{ print $1 }')"

  # only import objects under "foo"
  git lfs migrate import --include="foo/**"

  assert_pointer "refs/heads/main" "foo/bar/baz/a.txt" "$oid_tree" "$size"
  assert_local_object "$oid_tree" "$size"

  # "a.txt" is not under "foo" and therefore should not be in LFS
  oid_root_after_migration="$(calc_oid "$(git cat-file -p :a.txt)")"
  [ "$oid_root" = "$oid_root_after_migration" ]
)
end_test

begin_test "migrate import (filter matches files only)"
(
  set -e

  setup_single_local_branch_same_file_tree_ext

  txt_root_oid="$(calc_oid "$(git cat-file -p :a.txt)")"
  txt_foo_oid="$(calc_oid "$(git cat-file -p :foo/a.txt)")"
  md_bar_oid="$(calc_oid "$(git cat-file -p :bar.txt/b.md)")"
  txt_bar_oid="$(calc_oid "$(git cat-file -p :bar.txt/b.txt)")"

  git lfs migrate import --include="*.txt"

  assert_local_object "$txt_root_oid" "120"
  assert_local_object "$txt_foo_oid" "120"
  assert_local_object "$txt_bar_oid" "120"
  refute_local_object "$md_bar_oid"
)
end_test

begin_test "migrate import (--object-map)"
(
  set -e

  setup_multiple_local_branches

  output_dir="$GIT_LFS_TEST_DIR/import-object-map-$(lfstest-genrandom --base64url 32)"
  mkdir -p "$output_dir"

  git log --all --pretty='format:%H' > "${output_dir}/old_sha.txt"
  git lfs migrate import --everything --object-map "${output_dir}/object-map.txt"
  git log --all --pretty='format:%H' > "${output_dir}/new_sha.txt"
  paste -d',' "${output_dir}/old_sha.txt" "${output_dir}/new_sha.txt" > "${output_dir}/expected-map.txt"

  diff -u <(sort "${output_dir}/expected-map.txt") <(sort "${output_dir}/object-map.txt")
)
end_test

begin_test "migrate import (--include with space)"
(
  set -e

  setup_local_branch_with_space

  oid="$(calc_oid "$(git cat-file -p :"a file.txt")")"

  git lfs migrate import --include "a file.txt"

  assert_pointer "refs/heads/main" "a file.txt" "$oid" 50
  cat .gitattributes
  if [ 1 -ne "$(grep -c "a\[\[:space:\]\]file.txt" .gitattributes)" ]; then
    echo >&2 "fatal: expected \"a[[:space:]]file.txt\" to appear in .gitattributes"
    echo >&2 "fatal: got"
    sed -e 's/^/  /g' < .gitattributes >&2
    exit 1
  fi
)
end_test

begin_test "migrate import (handle symbolic link)"
(
  set -e

  setup_local_branch_with_symlink

  txt_oid="$(calc_oid "$(git cat-file -p :a.txt)")"
  link_oid="$(calc_oid "$(git cat-file -p :link.txt)")"

  git lfs migrate import --include="*.txt"

  assert_pointer "refs/heads/main" "a.txt" "$txt_oid" "120"

  assert_local_object "$txt_oid" "120"
  # "link.txt" is a symbolic link so it should be not in LFS
  refute_local_object "$link_oid" "5"
)
end_test

begin_test "migrate import (commit --allow-empty)"
(
  set -e

  reponame="migrate---allow-empty"
  git init "$reponame"
  cd "$reponame"

  git commit --allow-empty -m "initial commit"

  original_head="$(git rev-parse HEAD)"
  git lfs migrate import --everything
  migrated_head="$(git rev-parse HEAD)"

  assert_ref_unmoved "HEAD" "$original_head" "$migrated_head"
)
end_test

begin_test "migrate import (multiple remotes)"
(
  set -e

  setup_multiple_remotes

  original_main="$(git rev-parse main)"

  git lfs migrate import

  migrated_main="$(git rev-parse main)"

  assert_ref_unmoved "main" "$original_main" "$migrated_main"
)
end_test

begin_test "migrate import (dirty copy, default negative answer)"
(
  set -e

  setup_local_branch_with_dirty_copy

  original_main="$(git rev-parse main)"

  echo | git lfs migrate import --everything 2>&1 | tee migrate.log
  grep "migrate: working copy must not be dirty" migrate.log

  migrated_main="$(git rev-parse main)"

  assert_ref_unmoved "main" "$original_main" "$migrated_main"
)
end_test

begin_test "migrate import (dirty copy, negative answer)"
(
  set -e

  setup_local_branch_with_dirty_copy

  original_main="$(git rev-parse main)"

  echo "n" | git lfs migrate import --everything 2>&1 | tee migrate.log
  grep "migrate: working copy must not be dirty" migrate.log

  migrated_main="$(git rev-parse main)"

  assert_ref_unmoved "main" "$original_main" "$migrated_main"
)
end_test

begin_test "migrate import (dirty copy, unknown then negative answer)"
(
  set -e

  setup_local_branch_with_dirty_copy

  original_main="$(git rev-parse main)"

  echo "x\nn" | git lfs migrate import --everything 2>&1 | tee migrate.log

  cat migrate.log

  [ "2" -eq "$(grep -o "override changes in your working copy" migrate.log \
    | wc -l | awk '{ print $1 }')" ]
  grep "migrate: working copy must not be dirty" migrate.log

  migrated_main="$(git rev-parse main)"

  assert_ref_unmoved "main" "$original_main" "$migrated_main"
)
end_test

begin_test "migrate import (dirty copy, positive answer)"
(
  set -e

  setup_local_branch_with_dirty_copy

  oid="$(calc_oid "$(git cat-file -p :a.txt)")"

  echo "y" | git lfs migrate import --everything 2>&1 | tee migrate.log
  grep "migrate: changes in your working copy will be overridden ..." \
    migrate.log

  assert_pointer "refs/heads/main" "a.txt" "$oid" "5"
  assert_local_object "$oid" "5"
)
end_test

begin_test "migrate import (non-standard refs)"
(
  set -e

  setup_multiple_local_branches_non_standard

  md_oid="$(calc_oid "$(git cat-file -p :a.md)")"
  txt_oid="$(calc_oid "$(git cat-file -p :a.txt)")"
  md_feature_oid="$(calc_oid "$(git cat-file -p my-feature:a.md)")"

  git lfs migrate import --everything

  assert_pointer "refs/heads/main" "a.md" "$md_oid" "140"
  assert_pointer "refs/heads/main" "a.txt" "$txt_oid" "120"
  assert_pointer "refs/pull/1/base" "a.md" "$md_oid" "140"
  assert_pointer "refs/pull/1/base" "a.txt" "$txt_oid" "120"

  assert_pointer "refs/heads/my-feature" "a.txt" "$txt_oid" "120"
  assert_pointer "refs/pull/1/head" "a.txt" "$txt_oid" "120"

  assert_local_object "$md_oid" "140"
  assert_local_object "$txt_oid" "120"
  assert_local_object "$md_feature_oid" "30"
)
end_test

begin_test "migrate import (copied file)"
(
  set -e

  setup_local_branch_with_copied_file

  git lfs migrate import --above=1b

  # Expect attributes for "/dir/a" and "/a"
  if ! grep -q "^/dir/a.txt" ./.gitattributes || ! grep -q "^/a.txt" ./.gitattributes; then
    exit 1
  fi
)
end_test

begin_test "migrate import (copied file with only a single path)"
(
  set -e

  setup_local_branch_with_copied_file

  oid="$(calc_oid "$(git cat-file -p :a.txt)")"

  # Prevent MSYS from rewriting /a.txt into a Windows path.
  MSYS_NO_PATHCONV=1 git lfs migrate import --include="/a.txt" --everything

  # Expect attribute for only "/a.txt".
  if grep -q "^/dir/a.txt" ./.gitattributes || ! grep -q "^/a.txt" ./.gitattributes; then
    exit 1
  fi
  refute_pointer "refs/heads/main" "dir/a.txt" "$oid" 5
)
end_test

begin_test "migrate import (filename special characters)"
(
  set -e
  setup_local_branch_with_special_character_files
  git lfs migrate import --above=1b
  # Windows does not allow creation of files with '*', so expect 2 files, not 3
  if [ "$IS_WINDOWS" -eq "1" ] ; then
    test "$(git check-attr filter -- *.bin |grep lfs | wc -l)" -eq 2 || exit 1
  else
    test "$(git check-attr filter -- *.bin |grep lfs | wc -l)" -eq 3 || exit 1
  fi
)
end_test
