# frozen_string_literal: true require "docker_manager/git_repo" require_relative "../support/git_helpers" RSpec.describe DockerManager::GitRepo do describe ".find_all" do subject(:all_repos) { described_class.find_all } it "returns a list of repos" do expect(all_repos).to be_present end it "contains the `docker_manager` and `discourse` repos" do expect(all_repos.map(&:name)).to include("discourse", "docker_manager") end end describe ".find" do it "does not find invalid repos" do expect(described_class.find(" NOT A REPO")).to be_blank end it "returns valid repos" do repo = described_class.find_all.first expect(repo.path).to be_present end end context "with git repo" do def shallow_clone? clone_method == GitHelpers::CLONE_SHALLOW end def skip_for_shallow_clone pending("Doesn't work on shallow clones") if shallow_clone? end subject(:repo) do prepare_repos repo = described_class.new(@local_repo.path) repo.update_remote! unless @skip_update_remote repo end let!(:initial_branch) { "main" } before do @skip_update_remote = false @local_repo = @remote_git_repo = nil @before_local_repo_clone = [] @after_local_repo_clone = [] end after { @remote_git_repo.destroy } def prepare_repos return if @local_repo && @remote_git_repo @remote_git_repo = GitHelpers::RemoteGitRepo.new(initial_branch: initial_branch) @remote_git_repo.commit( filename: "foo.txt", commits: [ { content: "A", date: "2023-03-06T20:31:17Z" }, { content: "B", date: "2023-03-06T21:08:52Z", tags: %w[v3.1.0.beta1 beta latest-release], }, { content: "C", date: "2023-03-06T22:48:29Z" }, ], ) @remote_git_repo.create_branches("tests-passed") @before_local_repo_clone.each { |callback| callback.call } @local_repo = @remote_git_repo.create_local_clone(method: clone_method) @after_local_repo_clone.each { |callback| callback.call } @remote_git_repo.in_remote_repo { |git| git.call("log --pretty=oneline") } end def add_new_commits @remote_git_repo.commit( filename: "foo.txt", commits: [ { content: "D", date: "2023-03-07T10:11:19Z" }, { content: "E", date: "2023-03-07T12:58:29Z", tags: %w[v3.1.0.beta2 beta latest-release], }, { content: "F", date: "2023-03-07T15:22:23Z" }, ], ) @remote_git_repo.rebase(source_branch: "main", target_branch: "tests-passed") end shared_examples "common tests" do context "when tracking `tests-passed` branch" do before { @after_local_repo_clone << -> { @local_repo.checkout("tests-passed") } } describe "#has_local_main?" do context "with existing `main` branch" do let(:initial_branch) { "main" } it "detects the branch" do expect(repo.has_local_main?).to eq(true) end end context "with missing `main` branch" do let(:initial_branch) { "master" } it "doesn't detect the branch" do expect(repo.has_local_main?).to eq(false) end end end describe "#tracking_ref" do it "returns the correct remote branch" do expect(repo.tracking_ref).to eq("origin/tests-passed") end context "with `master` as initial branch" do let(:initial_branch) { "master" } before { @after_local_repo_clone << -> { @local_repo.checkout("master") } } it "returns `origin/master` if a repo hasn't been renamed" do expect(repo.tracking_ref).to eq("origin/master") end it "returns `origin/main` if a repo has been renamed but still tracks `master`" do @after_local_repo_clone << -> do @remote_git_repo.rename_branch(old_name: "master", new_name: "main") end expect(repo.tracking_ref).to eq("origin/main") end end context "with `main` as current local branch" do before { @after_local_repo_clone << -> { @local_repo.checkout("main") } } it "returns `origin/main` if a repo points at `origin/main`" do expect(repo.tracking_ref).to eq("origin/main") end end end describe "#upstream_branch" do it "returns the correct branch name" do expect(repo.upstream_branch).to eq("origin/tests-passed") end context "with `master` as initial branch" do let(:initial_branch) { "master" } before { @after_local_repo_clone << -> { @local_repo.checkout("master") } } it "returns `origin/master` if a repo hasn't been renamed" do expect(repo.upstream_branch).to eq("origin/master") end it "returns `origin/master` if a repo has been renamed but still tracks `master`" do @after_local_repo_clone << -> do @remote_git_repo.rename_branch(old_name: "master", new_name: "main") end expect(repo.upstream_branch).to eq("origin/master") end end context "with `main` as current local branch" do before { @after_local_repo_clone << -> { @local_repo.checkout("main") } } it "returns `origin/main` if a repo points at `origin/main`" do expect(repo.upstream_branch).to eq("origin/main") end end end describe "#upstream_branch_exist?" do it "returns true when upstream branch exist" do expect(repo.upstream_branch_exist?).to eq(true) end it "returns false when upstream branch doesn't exist" do @after_local_repo_clone << -> { @remote_git_repo.delete_branches("tests-passed") } expect(repo.upstream_branch_exist?).to eq(false) end end describe "#detached_head?" do it "returns false" do expect(repo.detached_head?).to eq(false) end end context "when local clone and origin are the same" do describe "#latest_local_commit" do it "returns the correct commit hash" do expect(repo.latest_local_commit).to eq("16a1d8111ff1eb6e8fc1d1b973b4fd92cacbebcc") end end describe "#latest_origin_commit" do it "returns the correct commit hash" do expect(repo.latest_origin_commit).to eq("16a1d8111ff1eb6e8fc1d1b973b4fd92cacbebcc") end end describe "#latest_local_commit_date" do it "returns the correct commit date" do expect(repo.latest_local_commit_date).to eq("2023-03-06T22:48:29Z") end end describe "#latest_origin_commit_date" do it "returns the correct commit date" do expect(repo.latest_origin_commit_date).to eq("2023-03-06T22:48:29Z") end end context "with no tags" do before do @before_local_repo_clone << -> do @remote_git_repo.delete_tags("beta", "latest-release", "v3.1.0.beta1") end end describe "#latest_local_tag_version" do it "returns nil as version" do expect(repo.latest_local_tag_version).to be_nil end end describe "#latest_origin_tag_version" do it "returns nil as version" do expect(repo.latest_origin_tag_version).to be_nil end end end context "with `beta`, `latest-release` and version tags on HEAD~1" do before { skip_for_shallow_clone } describe "#latest_local_tag_version" do it "returns the correct version and ignores the `beta` tag" do expect(repo.latest_local_tag_version).to eq("latest-release +1") end end describe "#latest_origin_tag_version" do it "returns the correct version and ignores the `beta` and `latest-release` tags" do expect(repo.latest_origin_tag_version).to eq("v3.1.0.beta1 +1") end end end context "with `beta` and version tags on HEAD~1" do before do skip_for_shallow_clone @before_local_repo_clone << -> { @remote_git_repo.delete_tags("latest-release") } end describe "#latest_local_tag_version" do it "returns the correct version and ignores the `beta` tag" do expect(repo.latest_local_tag_version).to eq("v3.1.0.beta1 +1") end end describe "#latest_origin_tag_version" do it "returns the correct version and ignores the `beta` tag" do expect(repo.latest_origin_tag_version).to eq("v3.1.0.beta1 +1") end end end describe "#commits_behind" do it "returns 0 because local and origin are the same" do expect(repo.commits_behind).to eq(0) end end describe "#update_remote!" do it "fetches the correct amount of new commits" do prepare_repos expect { repo.update_remote! }.to not_change { @local_repo.commit_count } end end end context "when origin has new commits" do before { @after_local_repo_clone << method(:add_new_commits) } describe "#latest_local_commit" do it "returns the correct commit hash" do expect(repo.latest_local_commit).to eq("16a1d8111ff1eb6e8fc1d1b973b4fd92cacbebcc") end end describe "#latest_origin_commit" do it "returns the correct commit hash" do expect(repo.latest_origin_commit).to eq("44b4ef6472e902d767335c4b19d47fd7a079d7c3") end end describe "#latest_local_commit_date" do it "returns the correct commit date" do expect(repo.latest_local_commit_date).to eq("2023-03-06T22:48:29Z") end end describe "#latest_origin_commit_date" do it "returns the correct commit date" do expect(repo.latest_origin_commit_date).to eq("2023-03-07T15:22:23Z") end end describe "#latest_local_tag_version" do it "returns the correct version" do skip_for_shallow_clone expect(repo.latest_local_tag_version).to eq("v3.1.0.beta1 +1") end end describe "#latest_origin_tag_version" do it "returns the correct version and ignores the `beta` and `latest-release` tags" do skip_for_shallow_clone expect(repo.latest_origin_tag_version).to eq("v3.1.0.beta2 +1") end end describe "#commits_behind" do it "returns the correct number of commits" do skip_for_shallow_clone expect(repo.commits_behind).to eq(3) end end describe "#update_remote!" do it "fetches the correct amount of new commits" do prepare_repos expect { repo.update_remote! }.to change { @local_repo.commit_count }.by( fetch_commit_count, ) end end end end context "when tracking `beta` tag" do before do @after_local_repo_clone << -> do unless shallow_clone? @local_repo.checkout("beta") # Mimics the behavior of `web.template.yml` where we store the value of the `$version` variable # as a user-defined config value in git. # See https://github.com/discourse/discourse_docker/blob/main/templates/web.template.yml @local_repo.git("config user.discourse-version beta") end end end describe "#has_local_main?" do context "with existing `main` branch" do let(:initial_branch) { "main" } it "detects the branch" do expect(repo.has_local_main?).to eq(true) end end context "with missing `main` branch" do let(:initial_branch) { "master" } it "doesn't detect the branch" do expect(repo.has_local_main?).to eq(false) end end end describe "#tracking_ref" do it "returns the correct remote branch" do skip_for_shallow_clone expect(repo.tracking_ref).to eq("beta") end end describe "#upstream_branch" do it "doesn't return a branch name" do skip_for_shallow_clone expect(repo.upstream_branch).to be_nil end end describe "#upstream_branch_exist?" do it "returns false because we aren't tracking a branch" do skip_for_shallow_clone expect(repo.upstream_branch_exist?).to eq(false) end end describe "#detached_head?" do it "returns true" do skip_for_shallow_clone expect(repo.detached_head?).to eq(true) end end context "when local clone and origin are the same" do describe "#latest_local_commit" do it "returns the correct commit hash" do skip_for_shallow_clone expect(repo.latest_local_commit).to eq("e43b6978c22ea3aeafbcf96c6e4fff5af0b7da29") end end describe "#latest_origin_commit" do it "returns the correct commit hash" do skip_for_shallow_clone expect(repo.latest_origin_commit).to eq("e43b6978c22ea3aeafbcf96c6e4fff5af0b7da29") end end describe "#latest_local_commit_date" do it "returns the correct commit date" do skip_for_shallow_clone expect(repo.latest_local_commit_date).to eq("2023-03-06T21:08:52Z") end end describe "#latest_origin_commit_date" do it "returns the correct commit date" do skip_for_shallow_clone expect(repo.latest_origin_commit_date).to eq("2023-03-06T21:08:52Z") end end describe "#latest_local_tag_version" do it "returns the correct version and ignores the `beta` tag" do skip_for_shallow_clone expect(repo.latest_local_tag_version).to eq("latest-release") end end describe "#latest_origin_tag_version" do it "returns the correct version and ignores the `beta` and `latest-release` tags" do skip_for_shallow_clone expect(repo.latest_origin_tag_version).to eq("v3.1.0.beta1") end end describe "#commits_behind" do it "returns 0 because local and origin are the same" do expect(repo.commits_behind).to eq(0) end end describe "#update_remote!" do it "fetches the correct amount of new commits" do prepare_repos expect { repo.update_remote! }.to not_change { @local_repo.commit_count } end end end context "when origin has new commits" do before { @after_local_repo_clone << method(:add_new_commits) } describe "#latest_local_commit" do it "returns the correct commit hash" do skip_for_shallow_clone expect(repo.latest_local_commit).to eq("e43b6978c22ea3aeafbcf96c6e4fff5af0b7da29") end end describe "#latest_origin_commit" do it "returns the correct commit hash" do skip_for_shallow_clone expect(repo.latest_origin_commit).to eq("bebd76be58db951fac6abc8d4d0746951fcd1082") end end describe "#latest_local_commit_date" do it "returns the correct commit date" do skip_for_shallow_clone expect(repo.latest_local_commit_date).to eq("2023-03-06T21:08:52Z") end end describe "#latest_origin_commit_date" do it "returns the correct commit date" do skip_for_shallow_clone expect(repo.latest_origin_commit_date).to eq("2023-03-07T12:58:29Z") end end describe "#latest_local_tag_version" do it "returns the correct version" do skip_for_shallow_clone expect(repo.latest_local_tag_version).to eq("v3.1.0.beta1") end end describe "#latest_origin_tag_version" do it "returns the correct version" do skip_for_shallow_clone expect(repo.latest_origin_tag_version).to eq("v3.1.0.beta2") end end describe "#commits_behind" do it "returns the correct number of commits" do skip_for_shallow_clone expect(repo.commits_behind).to eq(3) end end describe "#update_remote!" do it "fetches the correct amount of new commits" do prepare_repos expect { repo.update_remote! }.to change { @local_repo.commit_count }.by( fetch_commit_count, ) end end end end context "when tracking deleted branch" do before do @before_local_repo_clone << -> do @remote_git_repo.delete_tags("beta") @remote_git_repo.create_branches("beta") end @after_local_repo_clone << -> do @local_repo.checkout("beta") @remote_git_repo.delete_branches("beta") @remote_git_repo.in_working_directory do |git| git.call("tag -m 'latest beta release' -a beta latest-release^{}") git.call("push origin beta") end end end describe "#latest_local_commit" do it "returns the correct commit hash" do expect(repo.latest_local_commit).to eq("16a1d8111ff1eb6e8fc1d1b973b4fd92cacbebcc") end end describe "#latest_origin_commit" do it "returns the correct commit hash" do skip_for_shallow_clone expect(repo.latest_origin_commit).to be_nil end end describe "#latest_local_commit_date" do it "returns the correct commit date" do expect(repo.latest_local_commit_date).to eq("2023-03-06T22:48:29Z") end end describe "#latest_origin_commit_date" do it "returns the correct commit date" do skip_for_shallow_clone expect(repo.latest_origin_commit_date).to be_nil end end describe "#latest_local_tag_version" do it "returns the correct version" do skip_for_shallow_clone expect(repo.latest_local_tag_version).to eq("latest-release +1") end end describe "#latest_origin_tag_version" do it "returns the correct version and ignores the `beta` and `latest-release` tags" do expect(repo.latest_origin_tag_version).to be_nil end end describe "#commits_behind" do it "returns the correct number of commits" do expect(repo.commits_behind).to eq(0) end end end describe "#url" do before do @skip_update_remote = true @after_local_repo_clone << -> { @local_repo.git("remote set-url origin #{remote_url}") } end context "with GitHub HTTPS URL" do let(:remote_url) { "https://github.com/discourse/example.git" } it "returns the unmodified URL" do expect(repo.url).to eq("https://github.com/discourse/example.git") end end context "with GitHub SSH URL" do let(:remote_url) { "git@github.com:discourse/example.git" } it "returns a HTTPS URL" do expect(repo.url).to eq("https://github.com/discourse/example.git") end end context "with a different HTTPS URL" do let(:remote_url) { "https://example.com/discourse.git" } it "returns the unmodified URL" do expect(repo.url).to eq("https://example.com/discourse.git") end end context "with a different SSH URL" do let(:remote_url) { "git@example.com:discourse.git" } it "returns the unmodified URL" do expect(repo.url).to eq("git@example.com:discourse.git") end end end end context "with full clone" do let!(:clone_method) { GitHelpers::CLONE_FULL } let(:fetch_commit_count) { 3 } include_examples "common tests" end context "with shallow clone" do let!(:clone_method) { GitHelpers::CLONE_SHALLOW } let(:fetch_commit_count) { 1 } include_examples "common tests" end context "with partial (treeless) clone" do let!(:clone_method) { GitHelpers::CLONE_TREELESS } let(:fetch_commit_count) { 3 } include_examples "common tests" end context "with modern discourse-compatibility file" do let!(:clone_method) { GitHelpers::CLONE_SHALLOW } before do @before_local_repo_clone << -> do @remote_git_repo.commit( filename: ".discourse-compatibility", commits: [{ content: "<= 1000.0.0: twoPointFiveBranch", date: "2023-03-06T20:31:17Z" }], ) end end it "works" do DockerManager::FallbackCompatibilityParser.expects(:find_compatible_resource).never expect(repo.tracking_ref).to eq("twoPointFiveBranch") end it "works even in old core" do Discourse .stubs(:find_compatible_resource) .with { |version_list| version_list.include?("<=") } .raises(ArgumentError) expect(repo.tracking_ref).to eq("twoPointFiveBranch") end end end end