@@ -1278,3 +1278,166 @@ def test_worktree_prune(git_repo: GitSync, tmp_path: pathlib.Path) -> None:
12781278 # Dry run should also succeed
12791279 result_dry = git_repo .cmd .worktrees .prune (dry_run = True )
12801280 assert "error" not in result_dry .lower () or result_dry == ""
1281+
1282+
1283+ # GitNotes tests
1284+
1285+
1286+ class NoteAddFixture (t .NamedTuple ):
1287+ """Fixture for git notes add tests."""
1288+
1289+ test_id : str
1290+ message : str | None
1291+ force : bool
1292+
1293+
1294+ NOTE_ADD_FIXTURES : list [NoteAddFixture ] = [
1295+ NoteAddFixture (
1296+ test_id = "simple-message" ,
1297+ message = "Test note message" ,
1298+ force = False ,
1299+ ),
1300+ NoteAddFixture (
1301+ test_id = "message-with-force" ,
1302+ message = "Replacement note" ,
1303+ force = True ,
1304+ ),
1305+ ]
1306+
1307+
1308+ @pytest .mark .parametrize (
1309+ list (NoteAddFixture ._fields ),
1310+ NOTE_ADD_FIXTURES ,
1311+ ids = [test .test_id for test in NOTE_ADD_FIXTURES ],
1312+ )
1313+ def test_notes_add (
1314+ git_repo : GitSync ,
1315+ test_id : str ,
1316+ message : str | None ,
1317+ force : bool ,
1318+ ) -> None :
1319+ """Test GitNotesManager.add()."""
1320+ # Add a note to HEAD
1321+ result = git_repo .cmd .notes .add (message = message , force = force )
1322+ # Should succeed (empty string) or show overwriting message
1323+ assert result == "" or "Overwriting" in result or "error" not in result .lower ()
1324+
1325+
1326+ def test_notes_list (git_repo : GitSync ) -> None :
1327+ """Test GitNotesManager.ls()."""
1328+ # Add a note first
1329+ git_repo .cmd .notes .add (message = "Test note for listing" , force = True )
1330+
1331+ # List notes
1332+ notes = git_repo .cmd .notes .ls ()
1333+ assert isinstance (notes , list )
1334+ assert len (notes ) > 0
1335+
1336+ # Each note should have object_sha and note_sha
1337+ for note in notes :
1338+ assert note .object_sha is not None
1339+ assert note .note_sha is not None
1340+
1341+
1342+ def test_notes_get (git_repo : GitSync ) -> None :
1343+ """Test GitNotesManager.get()."""
1344+ # Add a note first
1345+ git_repo .cmd .notes .add (message = "Test note for get" , force = True )
1346+
1347+ # Get the HEAD revision
1348+ head_sha = git_repo .cmd .rev_parse (args = "HEAD" )
1349+
1350+ # Get the note by object_sha
1351+ note = git_repo .cmd .notes .get (object_sha = head_sha )
1352+ assert note is not None
1353+ assert note .object_sha == head_sha
1354+
1355+
1356+ def test_notes_filter (git_repo : GitSync ) -> None :
1357+ """Test GitNotesManager.filter()."""
1358+ # Add a note first
1359+ git_repo .cmd .notes .add (message = "Test note for filter" , force = True )
1360+
1361+ # Filter notes (should return at least one)
1362+ notes = git_repo .cmd .notes .filter ()
1363+ assert isinstance (notes , list )
1364+ assert len (notes ) >= 0 # May or may not have notes depending on state
1365+
1366+
1367+ def test_notes_show (git_repo : GitSync ) -> None :
1368+ """Test GitNoteCmd.show()."""
1369+ # Add a note first
1370+ note_message = "Test note for show method"
1371+ git_repo .cmd .notes .add (message = note_message , force = True )
1372+
1373+ # Get the note
1374+ head_sha = git_repo .cmd .rev_parse (args = "HEAD" )
1375+ note = git_repo .cmd .notes .get (object_sha = head_sha )
1376+ assert note is not None
1377+
1378+ # Show the note content
1379+ result = note .show ()
1380+ assert note_message in result
1381+
1382+
1383+ def test_notes_append (git_repo : GitSync ) -> None :
1384+ """Test GitNoteCmd.append()."""
1385+ # Add a note first
1386+ initial_message = "Initial note"
1387+ git_repo .cmd .notes .add (message = initial_message , force = True )
1388+
1389+ # Get the note
1390+ head_sha = git_repo .cmd .rev_parse (args = "HEAD" )
1391+ note = git_repo .cmd .notes .get (object_sha = head_sha )
1392+ assert note is not None
1393+
1394+ # Append to the note
1395+ append_message = "Appended content"
1396+ result = note .append (message = append_message )
1397+ assert result == "" or "error" not in result .lower ()
1398+
1399+ # Verify the appended content
1400+ note_content = note .show ()
1401+ assert append_message in note_content
1402+
1403+
1404+ def test_notes_remove (git_repo : GitSync ) -> None :
1405+ """Test GitNoteCmd.remove()."""
1406+ # Add a note first
1407+ git_repo .cmd .notes .add (message = "Note to be removed" , force = True )
1408+
1409+ # Get the note
1410+ head_sha = git_repo .cmd .rev_parse (args = "HEAD" )
1411+ note = git_repo .cmd .notes .get (object_sha = head_sha )
1412+ assert note is not None
1413+
1414+ # Remove the note
1415+ result = note .remove ()
1416+ assert result == "" or "error" not in result .lower ()
1417+
1418+ # Verify it's removed (should have fewer notes or none)
1419+ notes_after = git_repo .cmd .notes .ls ()
1420+ object_shas = [n .object_sha for n in notes_after ]
1421+ assert head_sha not in object_shas
1422+
1423+
1424+ def test_notes_prune (git_repo : GitSync ) -> None :
1425+ """Test GitNotesManager.prune()."""
1426+ # Prune should succeed even if nothing to prune
1427+ result = git_repo .cmd .notes .prune ()
1428+ assert result == "" or "error" not in result .lower ()
1429+
1430+ # Dry run should also succeed
1431+ result_dry = git_repo .cmd .notes .prune (dry_run = True )
1432+ assert "error" not in result_dry .lower () or result_dry == ""
1433+
1434+
1435+ def test_notes_get_ref (git_repo : GitSync ) -> None :
1436+ """Test GitNotesManager.get_ref()."""
1437+ # Add a note first (to ensure the notes ref exists)
1438+ git_repo .cmd .notes .add (message = "Note for get_ref test" , force = True )
1439+
1440+ # Get the notes ref
1441+ result = git_repo .cmd .notes .get_ref ()
1442+ # Should return the ref name or be empty if not set
1443+ assert result == "refs/notes/commits" or result == "" or "notes" in result
0 commit comments