diff --git a/swh/model/model.py b/swh/model/model.py
index 4f0810f20ca0d3919e7b5ae1da291b0b5aef599a..a3809f9d08ab807e1c715b4ee7e210528b9315c7 100644
--- a/swh/model/model.py
+++ b/swh/model/model.py
@@ -88,6 +88,45 @@ class Person(BaseModel):
     name = attr.ib(type=Optional[bytes])
     email = attr.ib(type=Optional[bytes])
 
+    @classmethod
+    def from_fullname(cls, fullname: bytes):
+        """Returns a Person object, by guessing the name and email from the
+        fullname, in the `name <email>` format.
+
+        The fullname is left unchanged."""
+        if fullname is None:
+            raise TypeError('fullname is None.')
+
+        name: Optional[bytes]
+        email: Optional[bytes]
+
+        try:
+            open_bracket = fullname.index(b'<')
+        except ValueError:
+            name = fullname
+            email = None
+        else:
+            raw_name = fullname[:open_bracket]
+            raw_email = fullname[open_bracket+1:]
+
+            if not raw_name:
+                name = None
+            else:
+                name = raw_name.strip()
+
+            try:
+                close_bracket = raw_email.rindex(b'>')
+            except ValueError:
+                email = raw_email
+            else:
+                email = raw_email[:close_bracket]
+
+        return Person(
+            name=name or None,
+            email=email or None,
+            fullname=fullname,
+        )
+
 
 @attr.s(frozen=True)
 class Timestamp(BaseModel):
diff --git a/swh/model/tests/test_model.py b/swh/model/tests/test_model.py
index be3219de216b116d664e647538084a7ba515aad3..8bffa80b491f65e98a51618fb1d5064f35bab15e 100644
--- a/swh/model/tests/test_model.py
+++ b/swh/model/tests/test_model.py
@@ -13,7 +13,7 @@ import pytest
 from swh.model.model import (
     Content, SkippedContent, Directory, Revision, Release, Snapshot,
     Timestamp, TimestampWithTimezone,
-    MissingData,
+    MissingData, Person
 )
 from swh.model.hashutil import hash_to_bytes, MultiHash
 from swh.model.hypothesis_strategies import objects, origins, origin_visits
@@ -108,6 +108,96 @@ def test_timestampwithtimezone_from_iso8601_negative_utc():
     )
 
 
+def test_person_from_fullname():
+    """The author should have name, email and fullname filled.
+
+    """
+    actual_person = Person.from_fullname(b'tony <ynot@dagobah>')
+    assert actual_person == Person(
+        fullname=b'tony <ynot@dagobah>',
+        name=b'tony',
+        email=b'ynot@dagobah',
+    )
+
+
+def test_person_from_fullname_no_email():
+    """The author and fullname should be the same as the input (author).
+
+    """
+    actual_person = Person.from_fullname(b'tony')
+    assert actual_person == Person(
+        fullname=b'tony',
+        name=b'tony',
+        email=None,
+    )
+
+
+def test_person_from_fullname_empty_person():
+    """Empty person has only its fullname filled with the empty
+    byte-string.
+
+    """
+    actual_person = Person.from_fullname(b'')
+    assert actual_person == Person(
+        fullname=b'',
+        name=None,
+        email=None,
+    )
+
+
+def test_git_author_line_to_author():
+    # edge case out of the way
+    with pytest.raises(TypeError):
+        Person.from_fullname(None)
+
+    tests = {
+        b'a <b@c.com>': Person(
+            name=b'a',
+            email=b'b@c.com',
+            fullname=b'a <b@c.com>',
+        ),
+        b'<foo@bar.com>': Person(
+            name=None,
+            email=b'foo@bar.com',
+            fullname=b'<foo@bar.com>',
+        ),
+        b'malformed <email': Person(
+            name=b'malformed',
+            email=b'email',
+            fullname=b'malformed <email'
+        ),
+        b'malformed <"<br"@ckets>': Person(
+            name=b'malformed',
+            email=b'"<br"@ckets',
+            fullname=b'malformed <"<br"@ckets>',
+        ),
+        b'trailing <sp@c.e> ': Person(
+            name=b'trailing',
+            email=b'sp@c.e',
+            fullname=b'trailing <sp@c.e> ',
+        ),
+        b'no<sp@c.e>': Person(
+            name=b'no',
+            email=b'sp@c.e',
+            fullname=b'no<sp@c.e>',
+        ),
+        b' more   <sp@c.es>': Person(
+            name=b'more',
+            email=b'sp@c.es',
+            fullname=b' more   <sp@c.es>',
+        ),
+        b' <>': Person(
+            name=None,
+            email=None,
+            fullname=b' <>',
+        ),
+    }
+
+    for person in sorted(tests):
+        expected_person = tests[person]
+        assert expected_person == Person.from_fullname(person)
+
+
 def test_content_get_hash():
     hashes = dict(
         sha1=b'foo', sha1_git=b'bar', sha256=b'baz', blake2s256=b'qux')