diff --git a/swh/model/git_objects.py b/swh/model/git_objects.py
new file mode 100644
index 0000000000000000000000000000000000000000..16e69e769e5f9074dae5c44c8403929b1d0dafb4
--- /dev/null
+++ b/swh/model/git_objects.py
@@ -0,0 +1,403 @@
+# Copyright (C) 2015-2021  The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+from __future__ import annotations
+
+import datetime
+from functools import lru_cache
+from typing import Iterable, List, Optional, Tuple
+
+from . import model
+from .collections import ImmutableDict
+from .hashutil import git_object_header, hash_to_bytehex
+
+
+def directory_entry_sort_key(entry: model.DirectoryEntry):
+    """The sorting key for tree entries"""
+    if isinstance(entry, dict):
+        # For backward compatibility
+        entry = model.DirectoryEntry.from_dict(entry)
+    if entry.type == "dir":
+        return entry.name + b"/"
+    else:
+        return entry.name
+
+
+@lru_cache()
+def _perms_to_bytes(perms):
+    """Convert the perms value to its bytes representation"""
+    oc = oct(perms)[2:]
+    return oc.encode("ascii")
+
+
+def escape_newlines(snippet):
+    """Escape the newlines present in snippet according to git rules.
+
+    New lines in git manifests are escaped by indenting the next line by one
+    space.
+
+    """
+
+    if b"\n" in snippet:
+        return b"\n ".join(snippet.split(b"\n"))
+    else:
+        return snippet
+
+
+def format_date(date: model.Timestamp) -> bytes:
+    """Convert a date object into an UTC timestamp encoded as ascii bytes.
+
+    Git stores timestamps as an integer number of seconds since the UNIX epoch.
+
+    However, Software Heritage stores timestamps as an integer number of
+    microseconds (postgres type "datetime with timezone").
+
+    Therefore, we print timestamps with no microseconds as integers, and
+    timestamps with microseconds as floating point values. We elide the
+    trailing zeroes from microsecond values, to "future-proof" our
+    representation if we ever need more precision in timestamps.
+
+    """
+    if isinstance(date, dict):
+        # For backward compatibility
+        date = model.Timestamp.from_dict(date)
+
+    if not date.microseconds:
+        return str(date.seconds).encode()
+    else:
+        float_value = "%d.%06d" % (date.seconds, date.microseconds)
+        return float_value.rstrip("0").encode()
+
+
+@lru_cache()
+def format_offset(offset: int, negative_utc: Optional[bool] = None) -> bytes:
+    """Convert an integer number of minutes into an offset representation.
+
+    The offset representation is [+-]hhmm where:
+
+    - hh is the number of hours;
+    - mm is the number of minutes.
+
+    A null offset is represented as +0000.
+    """
+    if offset < 0 or offset == 0 and negative_utc:
+        sign = "-"
+    else:
+        sign = "+"
+
+    hours = abs(offset) // 60
+    minutes = abs(offset) % 60
+
+    t = "%s%02d%02d" % (sign, hours, minutes)
+    return t.encode()
+
+
+def normalize_timestamp(time_representation):
+    """Normalize a time representation for processing by Software Heritage
+
+    This function supports a numeric timestamp (representing a number of
+    seconds since the UNIX epoch, 1970-01-01 at 00:00 UTC), a
+    :obj:`datetime.datetime` object (with timezone information), or a
+    normalized Software Heritage time representation (idempotency).
+
+    Args:
+        time_representation: the representation of a timestamp
+
+    Returns:
+        dict: a normalized dictionary with three keys:
+
+            - timestamp: a dict with two optional keys:
+
+               - seconds: the integral number of seconds since the UNIX epoch
+               - microseconds: the integral number of microseconds
+
+            - offset: the timezone offset as a number of minutes relative to
+              UTC
+            - negative_utc: a boolean representing whether the offset is -0000
+              when offset = 0.
+
+    """
+    if time_representation is None:
+        return None
+    else:
+        return model.TimestampWithTimezone.from_dict(time_representation).to_dict()
+
+
+def directory_git_object(directory: model.Directory) -> bytes:
+    if isinstance(directory, dict):
+        # For backward compatibility
+        directory = model.Directory.from_dict(directory)
+
+    components = []
+
+    for entry in sorted(directory.entries, key=directory_entry_sort_key):
+        components.extend(
+            [_perms_to_bytes(entry.perms), b"\x20", entry.name, b"\x00", entry.target,]
+        )
+
+    return format_git_object_from_parts("tree", components)
+
+
+def format_git_object_from_headers(
+    git_type: str,
+    headers: Iterable[Tuple[bytes, bytes]],
+    message: Optional[bytes] = None,
+) -> bytes:
+    """Format a git_object comprised of a git header and a manifest,
+    which is itself a sequence of `headers`, and an optional `message`.
+
+    The git_object format, compatible with the git format for tag and commit
+    objects, is as follows:
+
+      - for each `key`, `value` in `headers`, emit:
+
+        - the `key`, literally
+        - an ascii space (``\\x20``)
+        - the `value`, with newlines escaped using :func:`escape_newlines`,
+        - an ascii newline (``\\x0a``)
+
+      - if the `message` is not None, emit:
+
+        - an ascii newline (``\\x0a``)
+        - the `message`, literally
+
+    Args:
+      headers: a sequence of key/value headers stored in the manifest;
+      message: an optional message used to trail the manifest.
+
+    Returns:
+      the formatted git_object as bytes
+    """
+    entries: List[bytes] = []
+
+    for key, value in headers:
+        entries.extend((key, b" ", escape_newlines(value), b"\n"))
+
+    if message is not None:
+        entries.extend((b"\n", message))
+
+    concatenated_entries = b"".join(entries)
+
+    header = git_object_header(git_type, len(concatenated_entries))
+    return header + concatenated_entries
+
+
+def format_git_object_from_parts(git_type: str, parts: Iterable[bytes]) -> bytes:
+    """Similar to :func:`format_git_object_from_headers`, but for manifests made of
+    a flat list of entries, instead of key-value + message, ie. trees and snapshots."""
+    concatenated_parts = b"".join(parts)
+
+    header = git_object_header(git_type, len(concatenated_parts))
+    return header + concatenated_parts
+
+
+def format_author_data(
+    author: model.Person, date_offset: Optional[model.TimestampWithTimezone]
+) -> bytes:
+    """Format authorship data according to git standards.
+
+    Git authorship data has two components:
+
+    - an author specification, usually a name and email, but in practice an
+      arbitrary bytestring
+    - optionally, a timestamp with a UTC offset specification
+
+    The authorship data is formatted thus::
+
+        `name and email`[ `timestamp` `utc_offset`]
+
+    The timestamp is encoded as a (decimal) number of seconds since the UNIX
+    epoch (1970-01-01 at 00:00 UTC). As an extension to the git format, we
+    support fractional timestamps, using a dot as the separator for the decimal
+    part.
+
+    The utc offset is a number of minutes encoded as '[+-]HHMM'. Note that some
+    tools can pass a negative offset corresponding to the UTC timezone
+    ('-0000'), which is valid and is encoded as such.
+
+    Returns:
+        the byte string containing the authorship data
+
+    """
+
+    ret = [author.fullname]
+
+    if date_offset is not None:
+        date_f = format_date(date_offset.timestamp)
+        offset_f = format_offset(date_offset.offset, date_offset.negative_utc)
+
+        ret.extend([b" ", date_f, b" ", offset_f])
+
+    return b"".join(ret)
+
+
+def revision_git_object(revision: model.Revision) -> bytes:
+    """Formats the git_object of a revision. See :func:`revision_identifier` for details
+    on the format."""
+    if isinstance(revision, dict):
+        # For backward compatibility
+        revision = model.Revision.from_dict(revision)
+
+    headers = [(b"tree", hash_to_bytehex(revision.directory))]
+    for parent in revision.parents:
+        if parent:
+            headers.append((b"parent", hash_to_bytehex(parent)))
+
+    headers.append((b"author", format_author_data(revision.author, revision.date)))
+    headers.append(
+        (b"committer", format_author_data(revision.committer, revision.committer_date),)
+    )
+
+    # Handle extra headers
+    metadata = revision.metadata or ImmutableDict()
+    extra_headers = revision.extra_headers or ()
+    if not extra_headers and "extra_headers" in metadata:
+        extra_headers = metadata["extra_headers"]
+
+    headers.extend(extra_headers)
+
+    return format_git_object_from_headers("commit", headers, revision.message)
+
+
+def target_type_to_git(target_type: model.ObjectType) -> bytes:
+    """Convert a software heritage target type to a git object type"""
+    return {
+        model.ObjectType.CONTENT: b"blob",
+        model.ObjectType.DIRECTORY: b"tree",
+        model.ObjectType.REVISION: b"commit",
+        model.ObjectType.RELEASE: b"tag",
+        model.ObjectType.SNAPSHOT: b"refs",
+    }[target_type]
+
+
+def release_git_object(release: model.Release) -> bytes:
+    if isinstance(release, dict):
+        # For backward compatibility
+        release = model.Release.from_dict(release)
+
+    headers = [
+        (b"object", hash_to_bytehex(release.target)),
+        (b"type", target_type_to_git(release.target_type)),
+        (b"tag", release.name),
+    ]
+
+    if release.author is not None:
+        headers.append((b"tagger", format_author_data(release.author, release.date)))
+
+    return format_git_object_from_headers("tag", headers, release.message)
+
+
+def snapshot_git_object(snapshot: model.Snapshot) -> bytes:
+    """Formats the git_object of a revision. See :func:`snapshot_identifier` for details
+    on the format."""
+    if isinstance(snapshot, dict):
+        # For backward compatibility
+        snapshot = model.Snapshot.from_dict(snapshot)
+
+    unresolved = []
+    lines = []
+
+    for name, target in sorted(snapshot.branches.items()):
+        if not target:
+            target_type = b"dangling"
+            target_id = b""
+        elif target.target_type == model.TargetType.ALIAS:
+            target_type = b"alias"
+            target_id = target.target
+            if target_id not in snapshot.branches or target_id == name:
+                unresolved.append((name, target_id))
+        else:
+            target_type = target.target_type.value.encode()
+            target_id = target.target
+
+        lines.extend(
+            [
+                target_type,
+                b"\x20",
+                name,
+                b"\x00",
+                ("%d:" % len(target_id)).encode(),
+                target_id,
+            ]
+        )
+
+    if unresolved:
+        raise ValueError(
+            "Branch aliases unresolved: %s"
+            % ", ".join("%r -> %r" % x for x in unresolved),
+            unresolved,
+        )
+
+    return format_git_object_from_parts("snapshot", lines)
+
+
+def raw_extrinsic_metadata_git_object(metadata: model.RawExtrinsicMetadata) -> bytes:
+    """Formats the git_object of a raw_extrinsic_metadata object.
+    See :func:`raw_extrinsic_metadata_identifier` for details
+    on the format."""
+    if isinstance(metadata, dict):
+        # For backward compatibility
+        metadata = model.RawExtrinsicMetadata.from_dict(metadata)
+
+    # equivalent to using math.floor(dt.timestamp()) to round down,
+    # as int(dt.timestamp()) rounds toward zero,
+    # which would map two seconds on the 0 timestamp.
+    #
+    # This should never be an issue in practice as Software Heritage didn't
+    # start collecting metadata before 2015.
+    timestamp = (
+        metadata.discovery_date.astimezone(datetime.timezone.utc)
+        .replace(microsecond=0)
+        .timestamp()
+    )
+    assert timestamp.is_integer()
+
+    headers = [
+        (b"target", str(metadata.target).encode()),
+        (b"discovery_date", str(int(timestamp)).encode("ascii")),
+        (
+            b"authority",
+            f"{metadata.authority.type.value} {metadata.authority.url}".encode(),
+        ),
+        (b"fetcher", f"{metadata.fetcher.name} {metadata.fetcher.version}".encode(),),
+        (b"format", metadata.format.encode()),
+    ]
+
+    for key in (
+        "origin",
+        "visit",
+        "snapshot",
+        "release",
+        "revision",
+        "path",
+        "directory",
+    ):
+        if getattr(metadata, key, None) is not None:
+            value: bytes
+            if key == "path":
+                value = getattr(metadata, key)
+            else:
+                value = str(getattr(metadata, key)).encode()
+
+            headers.append((key.encode("ascii"), value))
+
+    return format_git_object_from_headers(
+        "raw_extrinsic_metadata", headers, metadata.metadata
+    )
+
+
+def extid_git_object(extid: model.ExtID) -> bytes:
+    headers = [
+        (b"extid_type", extid.extid_type.encode("ascii")),
+    ]
+    extid_version = extid.extid_version
+    if extid_version != 0:
+        headers.append((b"extid_version", str(extid_version).encode("ascii")))
+
+    headers.extend(
+        [(b"extid", extid.extid), (b"target", str(extid.target).encode("ascii")),]
+    )
+
+    return format_git_object_from_headers("extid", headers)
diff --git a/swh/model/identifiers.py b/swh/model/identifiers.py
index 9d3b2fa132a4d84c56d641ead03668827e73a488..6c0b3b33261299f8b586e4e8c24b3959975199f3 100644
--- a/swh/model/identifiers.py
+++ b/swh/model/identifiers.py
@@ -6,13 +6,14 @@
 from __future__ import annotations
 
 import binascii
-import datetime
 from functools import lru_cache
-from typing import Any, Dict, Iterable, List, Optional, Tuple
+from typing import Any, Dict
 
 from . import model
-from .collections import ImmutableDict
-from .hashutil import MultiHash, git_object_header, hash_to_bytehex, hash_to_hex
+
+# Reexport for backward compatibility
+from .git_objects import *  # noqa
+from .hashutil import MultiHash, hash_to_hex
 
 # Reexport for backward compatibility
 from .swhids import *  # noqa
@@ -117,38 +118,6 @@ def content_identifier(content: Dict[str, Any]) -> Dict[str, bytes]:
     return MultiHash.from_data(content["data"]).digest()
 
 
-def directory_entry_sort_key(entry: model.DirectoryEntry):
-    """The sorting key for tree entries"""
-    if isinstance(entry, dict):
-        # For backward compatibility
-        entry = model.DirectoryEntry.from_dict(entry)
-    if entry.type == "dir":
-        return entry.name + b"/"
-    else:
-        return entry.name
-
-
-@lru_cache()
-def _perms_to_bytes(perms):
-    """Convert the perms value to its bytes representation"""
-    oc = oct(perms)[2:]
-    return oc.encode("ascii")
-
-
-def escape_newlines(snippet):
-    """Escape the newlines present in snippet according to git rules.
-
-    New lines in git manifests are escaped by indenting the next line by one
-    space.
-
-    """
-
-    if b"\n" in snippet:
-        return b"\n ".join(snippet.split(b"\n"))
-    else:
-        return snippet
-
-
 def directory_identifier(directory: Dict[str, Any]) -> str:
     """Return the intrinsic identifier for a directory.
 
@@ -188,193 +157,6 @@ def directory_identifier(directory: Dict[str, Any]) -> str:
     return hash_to_hex(model.Directory.from_dict(directory).id)
 
 
-def directory_git_object(directory: model.Directory) -> bytes:
-    if isinstance(directory, dict):
-        # For backward compatibility
-        directory = model.Directory.from_dict(directory)
-
-    components = []
-
-    for entry in sorted(directory.entries, key=directory_entry_sort_key):
-        components.extend(
-            [_perms_to_bytes(entry.perms), b"\x20", entry.name, b"\x00", entry.target,]
-        )
-
-    return format_git_object_from_parts("tree", components)
-
-
-def format_date(date: model.Timestamp) -> bytes:
-    """Convert a date object into an UTC timestamp encoded as ascii bytes.
-
-    Git stores timestamps as an integer number of seconds since the UNIX epoch.
-
-    However, Software Heritage stores timestamps as an integer number of
-    microseconds (postgres type "datetime with timezone").
-
-    Therefore, we print timestamps with no microseconds as integers, and
-    timestamps with microseconds as floating point values. We elide the
-    trailing zeroes from microsecond values, to "future-proof" our
-    representation if we ever need more precision in timestamps.
-
-    """
-    if isinstance(date, dict):
-        # For backward compatibility
-        date = model.Timestamp.from_dict(date)
-
-    if not date.microseconds:
-        return str(date.seconds).encode()
-    else:
-        float_value = "%d.%06d" % (date.seconds, date.microseconds)
-        return float_value.rstrip("0").encode()
-
-
-@lru_cache()
-def format_offset(offset: int, negative_utc: Optional[bool] = None) -> bytes:
-    """Convert an integer number of minutes into an offset representation.
-
-    The offset representation is [+-]hhmm where:
-
-    - hh is the number of hours;
-    - mm is the number of minutes.
-
-    A null offset is represented as +0000.
-    """
-    if offset < 0 or offset == 0 and negative_utc:
-        sign = "-"
-    else:
-        sign = "+"
-
-    hours = abs(offset) // 60
-    minutes = abs(offset) % 60
-
-    t = "%s%02d%02d" % (sign, hours, minutes)
-    return t.encode()
-
-
-def normalize_timestamp(time_representation):
-    """Normalize a time representation for processing by Software Heritage
-
-    This function supports a numeric timestamp (representing a number of
-    seconds since the UNIX epoch, 1970-01-01 at 00:00 UTC), a
-    :obj:`datetime.datetime` object (with timezone information), or a
-    normalized Software Heritage time representation (idempotency).
-
-    Args:
-        time_representation: the representation of a timestamp
-
-    Returns:
-        dict: a normalized dictionary with three keys:
-
-            - timestamp: a dict with two optional keys:
-
-               - seconds: the integral number of seconds since the UNIX epoch
-               - microseconds: the integral number of microseconds
-
-            - offset: the timezone offset as a number of minutes relative to
-              UTC
-            - negative_utc: a boolean representing whether the offset is -0000
-              when offset = 0.
-
-    """
-    if time_representation is None:
-        return None
-    else:
-        return model.TimestampWithTimezone.from_dict(time_representation).to_dict()
-
-
-def format_git_object_from_headers(
-    git_type: str,
-    headers: Iterable[Tuple[bytes, bytes]],
-    message: Optional[bytes] = None,
-) -> bytes:
-    """Format a git_object comprised of a git header and a manifest,
-    which is itself a sequence of `headers`, and an optional `message`.
-
-    The git_object format, compatible with the git format for tag and commit
-    objects, is as follows:
-
-      - for each `key`, `value` in `headers`, emit:
-
-        - the `key`, literally
-        - an ascii space (``\\x20``)
-        - the `value`, with newlines escaped using :func:`escape_newlines`,
-        - an ascii newline (``\\x0a``)
-
-      - if the `message` is not None, emit:
-
-        - an ascii newline (``\\x0a``)
-        - the `message`, literally
-
-    Args:
-      headers: a sequence of key/value headers stored in the manifest;
-      message: an optional message used to trail the manifest.
-
-    Returns:
-      the formatted git_object as bytes
-    """
-    entries: List[bytes] = []
-
-    for key, value in headers:
-        entries.extend((key, b" ", escape_newlines(value), b"\n"))
-
-    if message is not None:
-        entries.extend((b"\n", message))
-
-    concatenated_entries = b"".join(entries)
-
-    header = git_object_header(git_type, len(concatenated_entries))
-    return header + concatenated_entries
-
-
-def format_git_object_from_parts(git_type: str, parts: Iterable[bytes]) -> bytes:
-    """Similar to :func:`format_git_object_from_headers`, but for manifests made of
-    a flat list of entries, instead of key-value + message, ie. trees and snapshots."""
-    concatenated_parts = b"".join(parts)
-
-    header = git_object_header(git_type, len(concatenated_parts))
-    return header + concatenated_parts
-
-
-def format_author_data(
-    author: model.Person, date_offset: Optional[model.TimestampWithTimezone]
-) -> bytes:
-    """Format authorship data according to git standards.
-
-    Git authorship data has two components:
-
-    - an author specification, usually a name and email, but in practice an
-      arbitrary bytestring
-    - optionally, a timestamp with a UTC offset specification
-
-    The authorship data is formatted thus::
-
-        `name and email`[ `timestamp` `utc_offset`]
-
-    The timestamp is encoded as a (decimal) number of seconds since the UNIX
-    epoch (1970-01-01 at 00:00 UTC). As an extension to the git format, we
-    support fractional timestamps, using a dot as the separator for the decimal
-    part.
-
-    The utc offset is a number of minutes encoded as '[+-]HHMM'. Note that some
-    tools can pass a negative offset corresponding to the UTC timezone
-    ('-0000'), which is valid and is encoded as such.
-
-    Returns:
-        the byte string containing the authorship data
-
-    """
-
-    ret = [author.fullname]
-
-    if date_offset is not None:
-        date_f = format_date(date_offset.timestamp)
-        offset_f = format_offset(date_offset.offset, date_offset.negative_utc)
-
-        ret.extend([b" ", date_f, b" ", offset_f])
-
-    return b"".join(ret)
-
-
 def revision_identifier(revision: Dict[str, Any]) -> str:
     """Return the intrinsic identifier for a revision.
 
@@ -428,67 +210,11 @@ def revision_identifier(revision: Dict[str, Any]) -> str:
     return hash_to_hex(model.Revision.from_dict(revision).id)
 
 
-def revision_git_object(revision: model.Revision) -> bytes:
-    """Formats the git_object of a revision. See :func:`revision_identifier` for details
-    on the format."""
-    if isinstance(revision, dict):
-        # For backward compatibility
-        revision = model.Revision.from_dict(revision)
-
-    headers = [(b"tree", hash_to_bytehex(revision.directory))]
-    for parent in revision.parents:
-        if parent:
-            headers.append((b"parent", hash_to_bytehex(parent)))
-
-    headers.append((b"author", format_author_data(revision.author, revision.date)))
-    headers.append(
-        (b"committer", format_author_data(revision.committer, revision.committer_date),)
-    )
-
-    # Handle extra headers
-    metadata = revision.metadata or ImmutableDict()
-    extra_headers = revision.extra_headers or ()
-    if not extra_headers and "extra_headers" in metadata:
-        extra_headers = metadata["extra_headers"]
-
-    headers.extend(extra_headers)
-
-    return format_git_object_from_headers("commit", headers, revision.message)
-
-
-def target_type_to_git(target_type: model.ObjectType) -> bytes:
-    """Convert a software heritage target type to a git object type"""
-    return {
-        model.ObjectType.CONTENT: b"blob",
-        model.ObjectType.DIRECTORY: b"tree",
-        model.ObjectType.REVISION: b"commit",
-        model.ObjectType.RELEASE: b"tag",
-        model.ObjectType.SNAPSHOT: b"refs",
-    }[target_type]
-
-
 def release_identifier(release: Dict[str, Any]) -> str:
     """Return the intrinsic identifier for a release."""
     return hash_to_hex(model.Release.from_dict(release).id)
 
 
-def release_git_object(release: model.Release) -> bytes:
-    if isinstance(release, dict):
-        # For backward compatibility
-        release = model.Release.from_dict(release)
-
-    headers = [
-        (b"object", hash_to_bytehex(release.target)),
-        (b"type", target_type_to_git(release.target_type)),
-        (b"tag", release.name),
-    ]
-
-    if release.author is not None:
-        headers.append((b"tagger", format_author_data(release.author, release.date)))
-
-    return format_git_object_from_headers("tag", headers, release.message)
-
-
 def snapshot_identifier(snapshot: Dict[str, Any]) -> str:
     """Return the intrinsic identifier for a snapshot.
 
@@ -546,50 +272,6 @@ def snapshot_identifier(snapshot: Dict[str, Any]) -> str:
     return hash_to_hex(model.Snapshot.from_dict(snapshot).id)
 
 
-def snapshot_git_object(snapshot: model.Snapshot) -> bytes:
-    """Formats the git_object of a revision. See :func:`snapshot_identifier` for details
-    on the format."""
-    if isinstance(snapshot, dict):
-        # For backward compatibility
-        snapshot = model.Snapshot.from_dict(snapshot)
-
-    unresolved = []
-    lines = []
-
-    for name, target in sorted(snapshot.branches.items()):
-        if not target:
-            target_type = b"dangling"
-            target_id = b""
-        elif target.target_type == model.TargetType.ALIAS:
-            target_type = b"alias"
-            target_id = target.target
-            if target_id not in snapshot.branches or target_id == name:
-                unresolved.append((name, target_id))
-        else:
-            target_type = target.target_type.value.encode()
-            target_id = target.target
-
-        lines.extend(
-            [
-                target_type,
-                b"\x20",
-                name,
-                b"\x00",
-                ("%d:" % len(target_id)).encode(),
-                target_id,
-            ]
-        )
-
-    if unresolved:
-        raise ValueError(
-            "Branch aliases unresolved: %s"
-            % ", ".join("%r -> %r" % x for x in unresolved),
-            unresolved,
-        )
-
-    return format_git_object_from_parts("snapshot", lines)
-
-
 def origin_identifier(origin):
     """Return the intrinsic identifier for an origin.
 
@@ -648,61 +330,6 @@ def raw_extrinsic_metadata_identifier(metadata: Dict[str, Any]) -> str:
     return hash_to_hex(model.RawExtrinsicMetadata.from_dict(metadata).id)
 
 
-def raw_extrinsic_metadata_git_object(metadata: model.RawExtrinsicMetadata) -> bytes:
-    """Formats the git_object of a raw_extrinsic_metadata object.
-    See :func:`raw_extrinsic_metadata_identifier` for details
-    on the format."""
-    if isinstance(metadata, dict):
-        # For backward compatibility
-        metadata = model.RawExtrinsicMetadata.from_dict(metadata)
-
-    # equivalent to using math.floor(dt.timestamp()) to round down,
-    # as int(dt.timestamp()) rounds toward zero,
-    # which would map two seconds on the 0 timestamp.
-    #
-    # This should never be an issue in practice as Software Heritage didn't
-    # start collecting metadata before 2015.
-    timestamp = (
-        metadata.discovery_date.astimezone(datetime.timezone.utc)
-        .replace(microsecond=0)
-        .timestamp()
-    )
-    assert timestamp.is_integer()
-
-    headers = [
-        (b"target", str(metadata.target).encode()),
-        (b"discovery_date", str(int(timestamp)).encode("ascii")),
-        (
-            b"authority",
-            f"{metadata.authority.type.value} {metadata.authority.url}".encode(),
-        ),
-        (b"fetcher", f"{metadata.fetcher.name} {metadata.fetcher.version}".encode(),),
-        (b"format", metadata.format.encode()),
-    ]
-
-    for key in (
-        "origin",
-        "visit",
-        "snapshot",
-        "release",
-        "revision",
-        "path",
-        "directory",
-    ):
-        if getattr(metadata, key, None) is not None:
-            value: bytes
-            if key == "path":
-                value = getattr(metadata, key)
-            else:
-                value = str(getattr(metadata, key)).encode()
-
-            headers.append((key.encode("ascii"), value))
-
-    return format_git_object_from_headers(
-        "raw_extrinsic_metadata", headers, metadata.metadata
-    )
-
-
 def extid_identifier(extid: Dict[str, Any]) -> str:
     """Return the intrinsic identifier for an ExtID object.
 
@@ -729,18 +356,3 @@ def extid_identifier(extid: Dict[str, Any]) -> str:
     """
 
     return hash_to_hex(model.ExtID.from_dict(extid).id)
-
-
-def extid_git_object(extid: model.ExtID) -> bytes:
-    headers = [
-        (b"extid_type", extid.extid_type.encode("ascii")),
-    ]
-    extid_version = extid.extid_version
-    if extid_version != 0:
-        headers.append((b"extid_version", str(extid_version).encode("ascii")))
-
-    headers.extend(
-        [(b"extid", extid.extid), (b"target", str(extid.target).encode("ascii")),]
-    )
-
-    return format_git_object_from_headers("extid", headers)
diff --git a/swh/model/model.py b/swh/model/model.py
index 1b02525fb4f070e77c871c0669649577ea71977e..d409a551f59c07fcf1d17305f0d3cc2c139cd9b4 100644
--- a/swh/model/model.py
+++ b/swh/model/model.py
@@ -15,7 +15,7 @@ import dateutil.parser
 import iso8601
 from typing_extensions import Final
 
-from . import identifiers
+from . import git_objects
 from .collections import ImmutableDict
 from .hashutil import DEFAULT_ALGORITHMS, MultiHash
 from .swhids import CoreSWHID
@@ -492,7 +492,7 @@ class Snapshot(HashableObject, BaseModel):
     id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"")
 
     def compute_hash(self) -> bytes:
-        git_object = identifiers.snapshot_git_object(self)
+        git_object = git_objects.snapshot_git_object(self)
         return hashlib.new("sha1", git_object).digest()
 
     @classmethod
@@ -533,7 +533,7 @@ class Release(HashableObject, BaseModel):
     id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"")
 
     def compute_hash(self) -> bytes:
-        git_object = identifiers.release_git_object(self)
+        git_object = git_objects.release_git_object(self)
         return hashlib.new("sha1", git_object).digest()
 
     @author.validator
@@ -628,7 +628,7 @@ class Revision(HashableObject, BaseModel):
             object.__setattr__(self, "metadata", metadata)
 
     def compute_hash(self) -> bytes:
-        git_object = identifiers.revision_git_object(self)
+        git_object = git_objects.revision_git_object(self)
         return hashlib.new("sha1", git_object).digest()
 
     @classmethod
@@ -686,7 +686,7 @@ class Directory(HashableObject, BaseModel):
     id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"")
 
     def compute_hash(self) -> bytes:
-        git_object = identifiers.directory_git_object(self)
+        git_object = git_objects.directory_git_object(self)
         return hashlib.new("sha1", git_object).digest()
 
     @classmethod
@@ -1016,7 +1016,7 @@ class RawExtrinsicMetadata(HashableObject, BaseModel):
     id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"")
 
     def compute_hash(self) -> bytes:
-        git_object = identifiers.raw_extrinsic_metadata_git_object(self)
+        git_object = git_objects.raw_extrinsic_metadata_git_object(self)
         return hashlib.new("sha1", git_object).digest()
 
     @origin.validator
@@ -1217,5 +1217,5 @@ class ExtID(HashableObject, BaseModel):
         )
 
     def compute_hash(self) -> bytes:
-        git_object = identifiers.extid_git_object(self)
+        git_object = git_objects.extid_git_object(self)
         return hashlib.new("sha1", git_object).digest()