#!/usr/bin/env bash

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

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

  setup_multiple_local_branches_tracked

  # Add b.md, a pointer existing only on main
  lfstest-genrandom --base64 160 >b.md
  git add b.md
  git commit -m "add b.md"

  md_oid="$(calc_oid "$(cat a.md)")"
  txt_oid="$(calc_oid "$(cat a.txt)")"
  b_md_oid="$(calc_oid "$(cat b.md)")"

  git checkout my-feature
  md_feature_oid="$(calc_oid "$(cat a.md)")"
  git checkout main

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

  git lfs migrate export --include="*.md, *.txt"

  refute_pointer "refs/heads/main" "a.md"
  refute_pointer "refs/heads/main" "a.txt"
  refute_pointer "refs/heads/main" "b.md"
  assert_pointer "refs/heads/my-feature" "a.md" "$md_feature_oid" "30"

  # b.md should be pruned as no pointer exists to reference it
  refute_local_object "$b_md_oid" "160"

  # Other objects should not be pruned as they're still referenced in `feature`
  # by pointers
  assert_local_object "$md_oid" "140"
  assert_local_object "$txt_oid" "120"
  assert_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")"
  feature_attrs="$(git cat-file -p "$feature:.gitattributes")"

  echo "$main_attrs" | grep -q "*.md !text !filter !merge !diff"
  echo "$main_attrs" | grep -q "*.txt !text !filter !merge !diff"

  echo "$feature_attrs" | grep -q "*.md !text !filter !merge !diff" && exit 1
  echo "$feature_attrs" | grep -q "*.txt !text !filter !merge !diff" && exit 1
  true
)
end_test

begin_test "migrate export (with remote)"
(
  set -e

  setup_single_remote_branch_tracked

  git push origin main

  md_oid="$(calc_oid "$(cat a.md)")"
  txt_oid="$(calc_oid "$(cat a.txt)")"

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

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

  # Flush the cache to ensure all objects have to be downloaded
  rm -rf .git/lfs/objects

  git lfs migrate export --everything --include="*.md, *.txt"

  refute_pointer "refs/heads/main" "a.md"
  refute_pointer "refs/heads/main" "a.txt"

  # All pointers have been exported, so all objects should be pruned
  refute_local_object "$md_oid" "50"
  refute_local_object "$txt_oid" "30"

  main="$(git rev-parse refs/heads/main)"
  main_attrs="$(git cat-file -p "$main:.gitattributes")"

  echo "$main_attrs" | grep -q "*.md !text !filter !merge !diff"
  echo "$main_attrs" | grep -q "*.txt !text !filter !merge !diff"
)
end_test

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

  setup_single_local_branch_tracked

  md_oid="$(calc_oid "$(cat a.md)")"
  txt_oid="$(calc_oid "$(cat a.txt)")"

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

  git lfs migrate export --include="*" --exclude="a.md"

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

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

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

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

  echo "$main_attrs" | grep -q "* !text !filter !merge !diff"
  echo "$main_attrs" | grep -q "a.md filter=lfs diff=lfs merge=lfs"
)
end_test

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

  setup_single_remote_branch_tracked
  git push origin main

  md_oid="$(calc_oid "$(cat a.md)")"
  txt_oid="$(calc_oid "$(cat a.txt)")"

  make_bare

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

  git lfs migrate export --everything --include="*"

  refute_pointer "refs/heads/main" "a.md"
  refute_pointer "refs/heads/main" "a.txt"

  # All pointers have been exported, so all objects should be pruned
  refute_local_object "$md_oid" "50"
  refute_local_object "$txt_oid" "30"
)
end_test

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

  setup_multiple_local_branches_tracked

  md_oid="$(calc_oid "$(cat a.md)")"
  txt_oid="$(calc_oid "$(cat a.txt)")"

  git checkout my-feature
  md_feature_oid="$(calc_oid "$(cat a.md)")"
  git checkout main

  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"

  git lfs migrate export --include="*.md,*.txt" my-feature

  refute_pointer "refs/heads/my-feature" "a.md"
  refute_pointer "refs/heads/my-feature" "a.txt"
  refute_pointer "refs/heads/main" "a.md"
  refute_pointer "refs/heads/main" "a.txt"

  # No pointers left, so all objects should be pruned
  refute_local_object "$md_feature_oid" "30"
  refute_local_object "$txt_oid" "120"
  refute_local_object "$md_oid" "140"

  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 !text !filter !merge !diff"
  echo "$main_attrs" | grep -q "*.txt !text !filter !merge !diff"
  echo "$feature_attrs" | grep -q "*.md !text !filter !merge !diff"
  echo "$feature_attrs" | grep -q "*.txt !text !filter !merge !diff"
)
end_test

begin_test "migrate export (no filter)"
(
  set -e

  setup_multiple_local_branches_tracked

  git lfs migrate export --yes 2>&1 | tee migrate.log
  if [ ${PIPESTATUS[0]} -eq 0 ]; then
    echo >&2 "fatal: expected git lfs migrate export to fail, didn't"
    exit 1
  fi

  grep "One or more files must be specified with --include" migrate.log
)
end_test

begin_test "migrate export (exclude remote refs)"
(
  set -e

  setup_single_remote_branch_tracked

  md_oid="$(calc_oid "$(cat a.md)")"
  txt_oid="$(calc_oid "$(cat a.txt)")"

  git checkout refs/remotes/origin/main
  md_remote_oid="$(calc_oid "$(cat a.md)")"
  txt_remote_oid="$(calc_oid "$(cat a.txt)")"
  git checkout main

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

  assert_pointer "refs/remotes/origin/main" "a.md" "$md_remote_oid" "140"
  assert_pointer "refs/remotes/origin/main" "a.txt" "$txt_remote_oid" "120"

  git lfs migrate export --include="*.md,*.txt"

  refute_pointer "refs/heads/main" "a.md"
  refute_pointer "refs/heads/main" "a.txt"

  refute_local_object "$md_oid" "50"
  refute_local_object "$txt_oid" "30"

  assert_pointer "refs/remotes/origin/main" "a.md" "$md_remote_oid" "140"
  assert_pointer "refs/remotes/origin/main" "a.txt" "$txt_remote_oid" "120"

  # Since these two objects exist on the remote, they should be removed with
  # our prune operation
  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")"
  remote_attrs="$(git cat-file -p "$remote:.gitattributes")"

  echo "$main_attrs" | grep -q "*.md !text !filter !merge !diff"
  echo "$main_attrs" | grep -q "*.txt !text !filter !merge !diff"

  echo "$remote_attrs" | grep -q "*.md !text !filter !merge !diff" && exit 1
  echo "$remote_attrs" | grep -q "*.txt !text !filter !merge !diff" && exit 1
  true
)
end_test

begin_test "migrate export (--skip-fetch)"
(
  set -e

  setup_single_remote_branch_tracked

  md_main_oid="$(calc_oid "$(cat a.md)")"
  txt_main_oid="$(calc_oid "$(cat a.txt)")"

  git checkout refs/remotes/origin/main
  md_remote_oid="$(calc_oid "$(cat a.md)")"
  txt_remote_oid="$(calc_oid "$(cat a.txt)")"
  git checkout main

  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

  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"

  git lfs migrate export --skip-fetch --include="*.md,*.txt"

  refute_pointer "refs/heads/main" "a.md"
  refute_pointer "pseudo-remote" "a.md"
  refute_pointer "refs/heads/main" "a.txt"
  refute_pointer "pseudo-remote" "a.txt"

  refute_local_object "$md_main_oid" "50"
  refute_local_object "$md_remote_oid" "140"
  refute_local_object "$txt_main_oid" "30"
  refute_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 !text !filter !merge !diff"
  echo "$main_attrs" | grep -q "*.txt !text !filter !merge !diff"
  echo "$remote_attrs" | grep -q "*.md !text !filter !merge !diff"
  echo "$remote_attrs" | grep -q "*.txt !text !filter !merge !diff"
)
end_test

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

  setup_multiple_remote_branches_gitattrs

  md_main_oid="$(calc_oid "$(cat a.md)")"
  txt_main_oid="$(calc_oid "$(cat a.txt)")"

  git checkout refs/remotes/origin/main
  md_remote_oid="$(calc_oid "$(cat a.md)")"
  txt_remote_oid="$(calc_oid "$(cat a.txt)")"

  git checkout my-feature
  md_feature_oid="$(calc_oid "$(cat a.md)")"
  txt_feature_oid="$(calc_oid "$(cat a.txt)")"

  git checkout main

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

  assert_pointer "refs/heads/main" "a.md" "$md_main_oid" "21"
  assert_pointer "refs/heads/main" "a.txt" "$txt_main_oid" "20"

  assert_pointer "refs/remotes/origin/main" "a.md" "$md_remote_oid" "11"
  assert_pointer "refs/remotes/origin/main" "a.txt" "$txt_remote_oid" "10"

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

  # Master objects should not be pruned as they exist in unpushed commits
  assert_local_object "$md_main_oid" "21"
  assert_local_object "$txt_main_oid" "20"

  # Remote main objects should be pruned as they exist in the remote
  refute_local_object "$md_remote_oid" "11"
  refute_local_object "$txt_remote_oid" "10"

  # txt_feature_oid should be pruned as it's no longer a pointer, but
  # md_feature_oid should remain as it's still a pointer in unpushed commits
  assert_local_object "$md_feature_oid" "31"
  refute_local_object "$txt_feature_oid" "30"

  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")"
  remote_attrs="$(git cat-file -p "$remote:.gitattributes")"
  feature_attrs="$(git cat-file -p "$feature:.gitattributes")"

  echo "$main_attrs" | grep -q "*.txt !text !filter !merge !diff" && exit 1
  echo "$remote_attrs" | grep -q "*.txt !text !filter !merge !diff" && exit 1
  echo "$feature_attrs" | grep -q "*.txt !text !filter !merge !diff"
)
end_test

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

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

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

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

  setup_single_local_branch_tracked 0755

  md_oid="$(calc_oid "$(cat a.md)")"
  txt_oid="$(calc_oid "$(cat a.txt)")"

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

  [ -x .gitattributes ]

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

  [ ! -x .gitattributes ]

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

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

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

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

  echo "$main_attrs" | grep -q "*.txt !text !filter !merge !diff"

  attrs_main_sha="$(git show $main:.gitattributes | git hash-object --stdin)"
  md_main_sha="$(git show $main:a.md | 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 $md_main_sha	a.md
100644 blob $txt_main_sha	a.txt
EOF
  )
)
end_test

begin_test "migrate export (.gitattributes symlink)"
(
  set -e

  setup_single_local_branch_tracked link

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

  grep "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 export (--object-map)"
(
  set -e

  setup_multiple_local_branches_tracked

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

  git log --all --pretty='format:%H' > "${output_dir}/old_sha.txt"
  git lfs migrate export --everything --include="*" --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 export (--verbose)"
(
  set -e

  setup_multiple_local_branches_tracked

  original_head="$(git rev-parse HEAD)"

  git lfs migrate export --everything --include="*" --verbose --yes 2>&1 | tee migrate.log
  grep -q "  commit ${original_head}: a\.md" migrate.log
)
end_test

begin_test "migrate export (--remote)"
(
  set -e

  setup_single_remote_branch_tracked

  git push origin main

  md_oid="$(calc_oid "$(cat a.md)")"
  txt_oid="$(calc_oid "$(cat a.txt)")"

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

  # Flush the cache to ensure all objects have to be downloaded
  rm -rf .git/lfs/objects

  # Setup a new remote and invalidate the default
  remote_url="$(git config --get remote.origin.url)"
  git remote add zeta "$remote_url"
  git remote set-url origin ""

  git lfs migrate export --everything --remote="zeta" --include="*.md, *.txt"

  refute_pointer "refs/heads/main" "a.md"
  refute_pointer "refs/heads/main" "a.txt"

  refute_local_object "$md_oid" "50"
  refute_local_object "$txt_oid" "30"
)
end_test

begin_test "migrate export (invalid --remote)"
(
  set -e

  setup_single_remote_branch_tracked

  git lfs migrate export --include="*" --remote="zz" --yes 2>&1 \
    | tee migrate.log
  if [ ${PIPESTATUS[0]} -eq 0 ]; then
    echo >&2 "fatal: expected git lfs migrate export to fail, didn't"
    exit 1
  fi

  grep "Invalid remote zz provided" migrate.log
)
end_test

begin_test "migrate export (invalid pointer)"
(
  set -e

  git init repo1
  git init repo2

  cd repo1
  echo "git-lfs" > problematic_file
  git add .
  git commit -m "create repo"

  git lfs migrate export --include="*" --everything --yes

  cd ../repo2
  echo "not git-lfs" > problematic_file
  git add .
  git commit -m "create repo"

  git lfs migrate export --include="*" --everything --yes
)
end_test
