fetch загружает изменения (коммиты, ветки, теги) из удаленного репозитория в локальный, но не объединяет их с текущей веткой. При этом удаленные ветки будут доступны как remote/branch-name.
pull выполняет две операции: сначала fetch, а затем merge (по умолчанию) или rebase (при соответствующей настройке) полученных изменений в текущую локальную ветку. То есть, pull загружает изменения и сразу применяет их.
Ключевое отличие: fetch только загружает, pull загружает и интегрирует.