nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: TobikoData/sqlmesh
Pull request #2967 (2024-07-30 16:31:05)

Fix: conditional statement parsing edge case
This PR aims to fix the following case:

```
>>> from sqlmesh.core.dialect import parse_one
>>> parse_one("@IF(cond, VACUUM ANALYZE)", dialect="postgres")
'@IF(cond, VACUUM ANALYZE' contains unsupported syntax. Falling back to parsing as a 'Command'.
Alias(
  this=MacroFunc(
    this=Anonymous(
      this=IF,
      expressions=[
        Column(
          this=Identifier(this=cond, quoted=False)),
        Command(this=VACUUM)])),
  alias=Identifier(this=ANALYZE, quoted=False))
```

The problem is that we're currently producing an `Alias` node as shown above, which happens because SQLGlot tokenizes both `VACUUM` and `ANALYZE` as `TokenType.COMMAND`, instead of producing a `STRING` for the latter.

This is a SQLMesh-related edge case, since SQLGlot doesn't generally expect a statement to show within a function, such as `@IF` in this case.


Patch:
diff --git a/sqlmesh/core/dialect.py b/sqlmesh/core/dialect.py
index 68fd73ed..004602b2 100644
--- a/sqlmesh/core/dialect.py
+++ b/sqlmesh/core/dialect.py
@@ -454,7 +454,13 @@ def _parse_if(self: Parser) -> t.Optional[exp.Expression]:
         else:
             self.raise_error("Expecting )")
 
-        return exp.Anonymous(this="IF", expressions=[cond, self._parse_statement()])
+        index = self._index
+        stmt = self._parse_statement()
+        if self._curr:
+            self._retreat(index)
+            stmt = self._parse_as_command(self._tokens[index])
+
+        return exp.Anonymous(this="IF", expressions=[cond, stmt])
 
 
 def _create_parser(parser_type: t.Type[exp.Expression], table_keys: t.List[str]) -> t.Callable:


Test patch:
diff --git a/tests/core/test_dialect.py b/tests/core/test_dialect.py
index fd188181..6c5a6da2 100644
--- a/tests/core/test_dialect.py
+++ b/tests/core/test_dialect.py
@@ -606,3 +606,6 @@ def test_conditional_statement():
         q.sql("snowflake")
         == "@IF(TRUE, COPY INTO 's3://example/data.csv' FROM EXTRA.EXAMPLE.TABLE STORAGE_INTEGRATION = S3_INTEGRATION FILE_FORMAT = (TYPE=CSV COMPRESSION=NONE NULL_IF=('') FIELD_OPTIONALLY_ENCLOSED_BY='\"') HEADER = TRUE OVERWRITE = TRUE SINGLE = TRUE /* this is a comment */)"
     )
+
+    q = parse_one("@IF(cond, VACUUM ANALYZE);", read="postgres")
+    assert q.sql(dialect="postgres") == "@IF(cond, VACUUM ANALYZE)"
Repository: TobikoData/sqlmesh
Pull request #2978 (2024-08-01 16:27:17)

fix: full_refresh is expected to be None as a default value but the v…
…alidator forced it to be a boolean


Patch:
diff --git a/sqlmesh/dbt/basemodel.py b/sqlmesh/dbt/basemodel.py
index b88ca295..a95b25b9 100644
--- a/sqlmesh/dbt/basemodel.py
+++ b/sqlmesh/dbt/basemodel.py
@@ -26,7 +26,6 @@ from sqlmesh.dbt.common import (
 from sqlmesh.dbt.relation import Policy, RelationType
 from sqlmesh.dbt.test import TestConfig
 from sqlmesh.utils import AttributeDict
-from sqlmesh.utils.conversions import ensure_bool
 from sqlmesh.utils.errors import ConfigError
 from sqlmesh.utils.pydantic import field_validator
 
@@ -144,11 +143,6 @@ class BaseModelConfig(GeneralConfig):
 
         return hooks
 
-    @field_validator("full_refresh", mode="before")
-    @classmethod
-    def _validate_bool(cls, v: str) -> bool:
-        return ensure_bool(v)
-
     @field_validator("grants", mode="before")
     @classmethod
     def _validate_grants(cls, v: t.Dict[str, str]) -> t.Dict[str, t.List[str]]:


Test patch:
diff --git a/tests/dbt/test_transformation.py b/tests/dbt/test_transformation.py
index 4dfb6805..6b5b7762 100644
--- a/tests/dbt/test_transformation.py
+++ b/tests/dbt/test_transformation.py
@@ -229,6 +229,10 @@ def test_model_kind():
         materialized=Materialization.INCREMENTAL, incremental_strategy="append"
     ).model_kind(context) == IncrementalUnmanagedKind(disable_restatement=False)
 
+    assert ModelConfig(
+        materialized=Materialization.INCREMENTAL, incremental_strategy="append", full_refresh=None
+    ).model_kind(context) == IncrementalUnmanagedKind(disable_restatement=False)
+
     assert ModelConfig(
         materialized=Materialization.INCREMENTAL,
         incremental_strategy="insert_overwrite",
Repository: TobikoData/sqlmesh
Pull request #3004 (2024-08-13 18:01:49)

Feat: allow returning >1 statements using macros
Addresses one of the items mentioned in https://tobiko-data.slack.com/archives/C044BRE5W4S/p1723562831081849


Patch:
diff --git a/sqlmesh/core/renderer.py b/sqlmesh/core/renderer.py
index 95d35288..187c600a 100644
--- a/sqlmesh/core/renderer.py
+++ b/sqlmesh/core/renderer.py
@@ -7,6 +7,7 @@ from pathlib import Path
 
 from sqlglot import exp, parse
 from sqlglot.errors import SqlglotError
+from sqlglot.helper import ensure_list
 from sqlglot.optimizer.annotate_types import annotate_types
 from sqlglot.optimizer.qualify import qualify
 from sqlglot.optimizer.simplify import simplify
@@ -187,11 +188,11 @@ class BaseExpressionRenderer:
 
         for expression in expressions:
             try:
-                expression = macro_evaluator.transform(expression)  # type: ignore
+                transformed_expressions = ensure_list(macro_evaluator.transform(expression))
             except MacroEvalError as ex:
                 raise_config_error(f"Failed to resolve macro for expression. {ex}", self._path)
 
-            if expression:
+            for expression in t.cast(t.List[exp.Expression], transformed_expressions):
                 with self._normalize_and_quote(expression) as expression:
                     if hasattr(expression, "selects"):
                         for select in expression.selects:


Test patch:
diff --git a/tests/core/test_model.py b/tests/core/test_model.py
index 04872502..02b70271 100644
--- a/tests/core/test_model.py
+++ b/tests/core/test_model.py
@@ -621,14 +621,31 @@ def test_model_pre_post_statements():
     ]
     assert model.pre_statements == expected_pre
 
-    expected_post = [
-        *d.parse("@foo(bar='x', val=@this)"),
-        *d.parse("DROP TABLE x2;"),
-    ]
+    expected_post = d.parse("@foo(bar='x', val=@this); DROP TABLE x2;")
     assert model.post_statements == expected_post
 
     assert model.query == d.parse("SELECT 1 AS x")[0]
 
+    @macro()
+    def multiple_statements(evaluator):
+        return ["CREATE TABLE t1 AS SELECT 1 AS c", "CREATE TABLE t2 AS SELECT 2 AS c"]
+
+    expressions = d.parse(
+        """
+        MODEL (name db.table);
+
+        SELECT 1 AS col;
+
+        @multiple_statements()
+        """
+    )
+    model = load_sql_based_model(expressions)
+
+    expected_post = d.parse(
+        'CREATE TABLE "t1" AS SELECT 1 AS "c"; CREATE TABLE "t2" AS SELECT 2 AS "c"'
+    )
+    assert model.render_post_statements() == expected_post
+
 
 def test_seed_hydration():
     expressions = d.parse(
Repository: TobikoData/sqlmesh
Pull request #3141 (2024-09-16 23:26:31)

fix: correctly generate when_matched SQL
Prior to this PR if a user had variables in their model definition and used when_matched then they would get a parsing error. This is because after we replace variables we generate SQL with the model def and reparse it which doesn't involve the Pydantic model validators. As a result we have to generate the SQL in a format that matches what the user would have originally put in which is with the comma separating it. 

Updated the test case to include a variable to make sure this is tested going forward. 


Patch:
diff --git a/sqlmesh/core/dialect.py b/sqlmesh/core/dialect.py
index 1a4f134d..e93be512 100644
--- a/sqlmesh/core/dialect.py
+++ b/sqlmesh/core/dialect.py
@@ -579,7 +579,12 @@ def _props_sql(self: Generator, expressions: t.List[exp.Expression]) -> str:
     size = len(expressions)
 
     for i, prop in enumerate(expressions):
-        sql = self.indent(f"{prop.name} {self.sql(prop, 'value')}")
+        value = prop.args.get("value")
+        if prop.name == "when_matched" and isinstance(value, list):
+            output_value = ", ".join(self.sql(v) for v in value)
+        else:
+            output_value = self.sql(prop, "value")
+        sql = self.indent(f"{prop.name} {output_value}")
 
         if i < size - 1:
             sql += ","


Test patch:
diff --git a/tests/core/test_model.py b/tests/core/test_model.py
index 51217d1a..92534d82 100644
--- a/tests/core/test_model.py
+++ b/tests/core/test_model.py
@@ -3916,7 +3916,7 @@ def test_when_matched_multiple():
     expressions = d.parse(
         """
         MODEL (
-          name db.employees,
+          name @{schema}.employees,
           kind INCREMENTAL_BY_UNIQUE_KEY (
             unique_key name,
             when_matched WHEN MATCHED AND source.x = 1 THEN UPDATE SET target.salary = COALESCE(source.salary, target.salary),
@@ -3933,7 +3933,7 @@ def test_when_matched_multiple():
         "WHEN MATCHED THEN UPDATE SET __MERGE_TARGET__.salary = COALESCE(__MERGE_SOURCE__.salary, __MERGE_TARGET__.salary)",
     ]
 
-    model = load_sql_based_model(expressions, dialect="hive")
+    model = load_sql_based_model(expressions, dialect="hive", variables={"schema": "db"})
     assert len(model.kind.when_matched) == 2
     assert model.kind.when_matched[0].sql() == expected_when_matched[0]
     assert model.kind.when_matched[1].sql() == expected_when_matched[1]
Repository: TobikoData/sqlmesh
Pull request #3152 (2024-09-17 22:36:09)

Fix: Support comments in Jinja queries coming from dbt models
Previously, the query rendering failed when the `JINJA_QUERY_BEGIN` block contained standalone comments.


Patch:
diff --git a/sqlmesh/core/renderer.py b/sqlmesh/core/renderer.py
index 1156f59d..e395c1f6 100644
--- a/sqlmesh/core/renderer.py
+++ b/sqlmesh/core/renderer.py
@@ -424,6 +424,8 @@ class QueryRenderer(BaseExpressionRenderer):
             except ParsetimeAdapterCallError:
                 return None
 
+            expressions = [e for e in expressions if not isinstance(e, exp.Semicolon)]
+
             if not expressions:
                 raise ConfigError(f"Failed to render query at '{self._path}':\n{self._expression}")
 


Test patch:
diff --git a/tests/core/test_model.py b/tests/core/test_model.py
index 92534d82..c07f3c73 100644
--- a/tests/core/test_model.py
+++ b/tests/core/test_model.py
@@ -5858,6 +5858,42 @@ def test_trailing_comments():
     assert not model.render_post_statements()
 
 
+def test_comments_in_jinja_query():
+    expressions = d.parse(
+        """
+        MODEL (name db.table);
+
+        JINJA_QUERY_BEGIN;
+        /* some comment A */
+
+        SELECT 1;
+        /* some comment B */
+
+        JINJA_END;
+        """
+    )
+    model = load_sql_based_model(expressions)
+    assert model.render_query().sql() == '/* some comment A */ SELECT 1 AS "1"'
+
+    expressions = d.parse(
+        """
+        MODEL (name db.table);
+
+        JINJA_QUERY_BEGIN;
+        /* some comment A */
+
+        SELECT 1;
+        SELECT 2;
+        /* some comment B */
+
+        JINJA_END;
+        """
+    )
+    model = load_sql_based_model(expressions)
+    with pytest.raises(ConfigError, match=r"Too many statements in query.*"):
+        model.render_query()
+
+
 def test_staged_file_path():
     expressions = d.parse(
         """
Repository: TobikoData/sqlmesh
Pull request #3193 (2024-09-27 15:40:47)

Fix(bigquery): fix BQ materialized view column comments
Fixes #3166 

izeigerman: Test?
treysp: My bad - unit test in original PR didn't cover the full surface area

Patch:
diff --git a/sqlmesh/core/engine_adapter/bigquery.py b/sqlmesh/core/engine_adapter/bigquery.py
index 08f3cc2b..8861199a 100644
--- a/sqlmesh/core/engine_adapter/bigquery.py
+++ b/sqlmesh/core/engine_adapter/bigquery.py
@@ -549,7 +549,7 @@ class BigQueryEngineAdapter(InsertOverwriteWithMergeMixin, ClusteredByMixin):
         table_kind: str = "TABLE",
         materialized_view: bool = False,
     ) -> None:
-        if not table_kind == "VIEW" and materialized_view:
+        if not (table_kind == "VIEW" and materialized_view):
             table = self._get_table(table_name)
 
             # convert Table object to dict


Test patch:
diff --git a/tests/core/engine_adapter/test_bigquery.py b/tests/core/engine_adapter/test_bigquery.py
index 2ef36578..da7d648b 100644
--- a/tests/core/engine_adapter/test_bigquery.py
+++ b/tests/core/engine_adapter/test_bigquery.py
@@ -628,16 +628,25 @@ def test_comments(make_mocked_engine_adapter: t.Callable, mocker: MockerFixture)
         long_table_comment,
     )
 
+    # Only called if column comments are registered
+    get_table_mock = mocker.patch(
+        "sqlmesh.core.engine_adapter.bigquery.BigQueryEngineAdapter._get_table"
+    )
+
+    db_call_mock = mocker.patch(
+        "sqlmesh.core.engine_adapter.bigquery.BigQueryEngineAdapter._db_call"
+    )
+
     adapter.create_view(
         "test_table",
         parse_one("SELECT a, b FROM source_table"),
         table_description=long_table_comment,
+        column_descriptions={"a": long_column_comment},
     )
+    assert get_table_mock.called
 
     # Bigquery doesn't support column comments for materialized views
-    db_call_mock = mocker.patch(
-        "sqlmesh.core.engine_adapter.bigquery.BigQueryEngineAdapter._db_call"
-    )
+    db_call_mock.reset_mock()
 
     adapter.create_view(
         "test_table",
Repository: TobikoData/sqlmesh
Pull request #3221 (2024-10-03 22:23:54)

Fix: DBT seed column order
Prior to this change, any DBT seeds using the `data_type` variant of indicating the column type (as opposed to the other method of using a `column_types` property) would have their `columns_to_types` set in a different order than the source data.

This caused assumptions in the seed rendering logic to fail, producing queries where the values to insert did not line up with their column names.


Patch:
diff --git a/sqlmesh/dbt/seed.py b/sqlmesh/dbt/seed.py
index eaa088d1..96a837a7 100644
--- a/sqlmesh/dbt/seed.py
+++ b/sqlmesh/dbt/seed.py
@@ -63,9 +63,17 @@ class SeedConfig(BaseModelConfig):
                 name: AGATE_TYPE_MAPPING[tpe.__class__]
                 for name, tpe in zip(agate_table.column_names, agate_table.column_types)
             }
-            for key in missing_types or inferred_types:
-                columns[key] = inferred_types[key]
-            kwargs["columns"] = columns
+
+            # The columns list built from the mixture of supplied and inferred types needs to
+            # be in the same order as the data for assumptions elsewhere in the codebase to hold true
+            new_columns = {}
+            for column_name in agate_table.column_names:
+                if (column_name in missing_types) or (column_name not in columns):
+                    new_columns[column_name] = inferred_types[column_name]
+                else:
+                    new_columns[column_name] = columns[column_name]
+
+            kwargs["columns"] = new_columns
 
         return create_seed_model(
             self.canonical_name(context),


Test patch:
diff --git a/tests/dbt/test_transformation.py b/tests/dbt/test_transformation.py
index 1a02cdde..6050cbf3 100644
--- a/tests/dbt/test_transformation.py
+++ b/tests/dbt/test_transformation.py
@@ -515,6 +515,36 @@ def test_seed_partial_column_inference(tmp_path):
     assert sqlmesh_seed.columns_to_types == expected_column_types
     assert sqlmesh_seed.column_descriptions == expected_column_descriptions
 
+    # Check that everything still lines up
+    seed_df = next(sqlmesh_seed.render_seed())
+    assert list(seed_df.columns) == list(sqlmesh_seed.columns_to_types.keys())
+
+
+def test_seed_column_order(tmp_path):
+    seed_csv = tmp_path / "seed.csv"
+
+    with open(seed_csv, "w", encoding="utf-8") as fd:
+        fd.writelines("\n".join(["id,name", "0,Toby", "1,Tyson", "2,Ryan"]))
+
+    seed = SeedConfig(
+        name="test_model",
+        package="package",
+        path=Path(seed_csv),
+        columns={
+            "id": ColumnConfig(name="id"),
+            "name": ColumnConfig(name="name", data_type="varchar"),
+        },
+    )
+
+    context = DbtContext()
+    context.project_name = "Foo"
+    context.target = DuckDbConfig(name="target", schema="test")
+    sqlmesh_seed = seed.to_sqlmesh(context)
+
+    # Check that everything still lines up
+    seed_df = next(sqlmesh_seed.render_seed())
+    assert list(seed_df.columns) == list(sqlmesh_seed.columns_to_types.keys())
+
 
 def test_agate_integer_cast():
     agate_integer = Integer(null_values=("null", ""))
Repository: TobikoData/sqlmesh
Pull request #3232 (2024-10-09 16:09:53)

Fix: reset macro registry *after* loading models
This addresses a bug that was [reported](https://tobiko-data.slack.com/archives/C044BRE5W4S/p1728485147986839?thread_ts=1728478460.658349&cid=C044BRE5W4S) in the Slack channel.

Our override of `_parse_table_parts` for Snowflake checks whether `@some_name...` appears in the registry's macros as a heuristic, to disambiguate macro funcs/vars from staged paths. Since we're [resetting](https://github.com/TobikoData/sqlmesh/blob/main/sqlmesh/core/loader.py#L301) the macro registry to `standard_macros` after loading the macros, we won't have access to the user-defined macros when this method is called (models are loaded after macros), so there are cases where the parsing will fail, i.e. when user-defined macros are used as table functions (see added test).


Patch:
diff --git a/sqlmesh/core/loader.py b/sqlmesh/core/loader.py
index 1b2f2632..beb8d035 100644
--- a/sqlmesh/core/loader.py
+++ b/sqlmesh/core/loader.py
@@ -93,11 +93,18 @@ class Loader(abc.ABC):
         self._config_mtimes = {path: max(mtimes) for path, mtimes in config_mtimes.items()}
 
         macros, jinja_macros = self._load_scripts()
+
+        # All macros need to be known at parse time
+        standard_macros = macro.get_registry()
+        macro.set_registry(macros)
+
         audits = self._load_audits(macros=macros, jinja_macros=jinja_macros)
         models = self._load_models(
             macros, jinja_macros, context.gateway or context.config.default_gateway, audits or None
         )
 
+        macro.set_registry(standard_macros)
+
         for model in models.values():
             self._add_model_to_dag(model)
 


Test patch:
diff --git a/tests/core/test_model.py b/tests/core/test_model.py
index b62abf4e..279701c1 100644
--- a/tests/core/test_model.py
+++ b/tests/core/test_model.py
@@ -5922,3 +5922,38 @@ def test_cache():
     model = load_sql_based_model(expressions)
     assert model.depends_on == {'"y"'}
     assert model.copy(update={"depends_on_": {'"z"'}}).depends_on == {'"z"', '"y"'}
+
+
+def test_snowflake_macro_func_as_table(tmp_path: Path):
+    init_example_project(tmp_path, dialect="duckdb")
+
+    custom_macro_file = tmp_path / "macros/custom_macros.py"
+    custom_macro_file.parent.mkdir(parents=True, exist_ok=True)
+    custom_macro_file.write_text("""
+from sqlmesh import macro
+
+@macro()
+def custom_macro(evaluator, arg1, arg2):
+    return f"{arg1}{arg2}"
+    """)
+
+    new_snowflake_model_file = tmp_path / "models/new_model.sql"
+    new_snowflake_model_file.parent.mkdir(parents=True, exist_ok=True)
+    new_snowflake_model_file.write_text("""
+MODEL (
+  name sqlmesh_example.test,
+  dialect snowflake,
+);
+
+@DEF(foo, foo);
+@DEF(bar, bar);
+
+SELECT * FROM @custom_macro(@foo, @bar)
+    """)
+
+    config = Config(model_defaults=ModelDefaultsConfig(dialect="duckdb"))
+    context = Context(paths=tmp_path, config=config)
+
+    query = context.get_model("sqlmesh_example.test").render_query()
+
+    assert t.cast(exp.Query, query).sql("snowflake") == 'SELECT * FROM "FOOBAR" AS "FOOBAR"'
Repository: TobikoData/sqlmesh
Pull request #3243 (2024-10-11 15:46:14)

Fix: handle Paren in depends_on validator
Fixes a validation error when the `depends_on` is specified using parentheses, like `depends_on (foo)`.


Patch:
diff --git a/sqlmesh/core/model/common.py b/sqlmesh/core/model/common.py
index f3bcfd5b..ee63f26d 100644
--- a/sqlmesh/core/model/common.py
+++ b/sqlmesh/core/model/common.py
@@ -93,6 +93,9 @@ def depends_on(cls: t.Type, v: t.Any, values: t.Dict[str, t.Any]) -> t.Optional[
     dialect = values.get("dialect")
     default_catalog = values.get("default_catalog")
 
+    if isinstance(v, exp.Paren):
+        v = v.unnest()
+
     if isinstance(v, (exp.Array, exp.Tuple)):
         return {
             normalize_model_name(


Test patch:
diff --git a/tests/core/test_model.py b/tests/core/test_model.py
index 279701c1..ed1d254c 100644
--- a/tests/core/test_model.py
+++ b/tests/core/test_model.py
@@ -2661,17 +2661,17 @@ def test_missing_schema_warnings():
 
 
 def test_user_provided_depends_on():
-    expressions = d.parse(
-        """
-        MODEL (name db.table, depends_on [table_b]);
-
-        SELECT a FROM table_a
-        """
-    )
+    for l_delim, r_delim in (("(", ")"), ("[", "]")):
+        expressions = d.parse(
+            f"""
+            MODEL (name db.table, depends_on {l_delim}table_b{r_delim});
 
-    model = load_sql_based_model(expressions)
+            SELECT a FROM table_a
+            """
+        )
 
-    assert model.depends_on == {'"table_a"', '"table_b"'}
+        model = load_sql_based_model(expressions)
+        assert model.depends_on == {'"table_a"', '"table_b"'}, f"Delimiters {l_delim}, {r_delim}"
 
 
 def test_check_schema_mapping_when_rendering_at_runtime(assert_exp_eq):
Repository: rancher/fleet
Pull request #3539 (2025-04-10 15:19:04)

[v0.12] Prevent panic when fetching latest commit
This fixes a panic happening when the following conditions are met:
* strict host key checks are disabled
* `known_hosts` entries are found
* no secret exists, neither referenced through a `GitRepo`'s nor a `gitcredential` secret

In that case, emptying a secret's `data` field does not make sense, and would indeed lead to a panic. This commit ensures that said field is only emptied if doing so is needed and safe.

Refers to #3537
Follow-up to #3524.


Patch:
diff --git a/pkg/git/fetch.go b/pkg/git/fetch.go
index 6273618b..60ac185e 100644
--- a/pkg/git/fetch.go
+++ b/pkg/git/fetch.go
@@ -70,7 +70,7 @@ func (f *Fetch) LatestCommit(ctx context.Context, gitrepo *v1alpha1.GitRepo, cli
 		knownHosts = kh
 	}
 
-	if f.KnownHosts != nil && !f.KnownHosts.IsStrict() {
+	if secret.Data != nil && f.KnownHosts != nil && !f.KnownHosts.IsStrict() {
 		// This prevents errors about keys being mismatch or not found when host key checks are disabled.
 		secret.Data["known_hosts"] = nil
 	}


Test patch:
diff --git a/pkg/git/fetch_test.go b/pkg/git/fetch_test.go
index 882bf015..a667a61a 100644
--- a/pkg/git/fetch_test.go
+++ b/pkg/git/fetch_test.go
@@ -164,6 +164,32 @@ var _ = Describe("git fetch's LatestCommit tests", func() {
 		Expect(commit).To(Equal("2ada7cca738877df8459b3a34839a15e5683edaa"))
 	})
 
+	It("returns the commit for the expected branch with no secret", func() {
+		gr := &fleetv1.GitRepo{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-gitrepo",
+				Namespace: "test-ns",
+			},
+			Spec: fleetv1.GitRepoSpec{
+				ClientSecretName: "test-secret",
+				Repo:             fakeGithub.URL,
+				Branch:           "master",
+			},
+			Status: fleetv1.GitRepoStatus{
+				Commit: "",
+			},
+		}
+		c := newTestClient()
+		f := git.Fetch{
+			KnownHosts: mockKnownHostsGetter{
+				data: "foo",
+			},
+		}
+		commit, err := f.LatestCommit(context.Background(), gr, c)
+		Expect(err).ToNot(HaveOccurred())
+		Expect(commit).To(Equal("2ada7cca738877df8459b3a34839a15e5683edaa"))
+	})
+
 	It("returns an error when secret's type is not expected", func() {
 		secret := &corev1.Secret{
 			ObjectMeta: metav1.ObjectMeta{