diff --git a/README.rst b/README.rst
index 744f9d32a694b7e26252fc5bd2144e1374de7350_UkVBRE1FLnJzdA==..6bc30f991fc52d7d2559547fab83132fc792ee63_UkVBRE1FLnJzdA== 100644
--- a/README.rst
+++ b/README.rst
@@ -333,12 +333,15 @@
 ``hggit.mapsavefrequency``
 --------------------------
 
-Controls how often the mapping between Git and Mercurial commit hashes
-gets saved when importing or exporting changesets. Set this to a number
-greater than 0 to save the mapping after converting that many commits.
-This can help when the conversion encounters an error partway through a
-large batch of changes. Defaults to 0, so that the mapping is saved once
-at the end.
+By default, hg-git only saves the results of a conversion at the end.
+Use this option to enable resuming long-running pulls and pushes. Set
+this to a number greater than 0 to allow resuming after converting
+that many commits. This can help when the conversion encounters an
+error partway through a large batch of changes. Otherwise, an error or
+interruption will roll back the transaction, similar to regular
+Mercurial.
+
+Defaults to 0, so that the mapping is saved once at the end.
 
 Please note that this is meaningless for an initial clone, as any
 error or interruption will delete the destination. So instead of
diff --git a/hggit/git_handler.py b/hggit/git_handler.py
index 744f9d32a694b7e26252fc5bd2144e1374de7350_aGdnaXQvZ2l0X2hhbmRsZXIucHk=..6bc30f991fc52d7d2559547fab83132fc792ee63_aGdnaXQvZ2l0X2hhbmRsZXIucHk= 100644
--- a/hggit/git_handler.py
+++ b/hggit/git_handler.py
@@ -824,6 +824,18 @@
     def get_git_incoming(self, refs):
         return git2hg.find_incoming(self.git.object_store, self._map_git, refs)
 
+    def get_transaction(self, desc=b"hg-git"):
+        """obtain a transaction specific for the repository
+
+        this ensures that we only save the map on close
+
+        """
+        tr = self.repo.transaction(desc)
+
+        tr.addfinalize(b'hg-git-save', lambda tr: self.save_map(self.map_file))
+
+        return tr
+
     def import_git_objects(self, remote_name, refs):
         result = self.get_git_incoming(refs)
         commits = result.commits
@@ -836,13 +848,18 @@
             self.ui.status(_(b"no changes found\n"))
 
         mapsavefreq = self.ui.configint(b'hggit', b'mapsavefrequency')
-        with self.ui.makeprogress(b'importing', unit=b'commits', total=total) as progress:
-            for i, csha in enumerate(commits, 1):
-                progress.increment()
-                commit = commit_cache[csha]
-                self.import_git_commit(commit)
-                if mapsavefreq and i % mapsavefreq == 0:
-                    self.save_map(self.map_file)
+        chunksize = max(mapsavefreq or total, 1)
+        progress = self.ui.makeprogress(b'importing', unit=b'commits', total=total)
+
+        self.ui.note(b"processing commits in batches of %d\n" % chunksize)
+
+        with progress, self.repo.lock():
+            for offset in range(0, total, chunksize):
+                # speed up conversion by batching commits in a transaction
+                with self.get_transaction(b"gimport"):
+                    for csha in commits[offset:offset + chunksize]:
+                        progress.increment()
+                        self.import_git_commit(commit_cache[csha])
 
         # TODO if the tags cache is used, remove any dangling tag references
         return total
diff --git a/tests/test-transactions.t b/tests/test-transactions.t
index 744f9d32a694b7e26252fc5bd2144e1374de7350_dGVzdHMvdGVzdC10cmFuc2FjdGlvbnMudA==..6bc30f991fc52d7d2559547fab83132fc792ee63_dGVzdHMvdGVzdC10cmFuc2FjdGlvbnMudA== 100644
--- a/tests/test-transactions.t
+++ b/tests/test-transactions.t
@@ -68,6 +68,8 @@
   $ ABORT_AFTER=99 hg pull
   pulling from $TESTTMP/gitrepo
   importing git objects into hg
+  transaction abort!
+  rollback completed
   abort: aborted after 99 commits!
   [255]
   $ hg log -l 10 -T '{rev} {gitnode}\n'
@@ -71,25 +73,9 @@
   abort: aborted after 99 commits!
   [255]
   $ hg log -l 10 -T '{rev} {gitnode}\n'
-  99 
-  98 
-  97 
-  96 
-  95 
-  94 
-  93 
-  92 
-  91 
-  90 
-
-Reset the repository
-
-  $ hg strip --no-backup 'all()'
-  $ hg gclear
-  clearing out the git cache data
 
 How does map save interval work?
 
   $ EXIT_AFTER=15 hg --config hggit.mapsavefrequency=10 pull
   pulling from $TESTTMP/gitrepo
   importing git objects into hg
@@ -90,9 +76,11 @@
 
 How does map save interval work?
 
   $ EXIT_AFTER=15 hg --config hggit.mapsavefrequency=10 pull
   pulling from $TESTTMP/gitrepo
   importing git objects into hg
+  transaction abort!
+  rollback completed
   interrupted!
   [255]
   $ hg log -l 10 -T '{rev} {gitnode}\n'
@@ -96,13 +84,7 @@
   interrupted!
   [255]
   $ hg log -l 10 -T '{rev} {gitnode}\n'
-  15 
-  14 
-  13 
-  12 
-  11 
-  10 
   9 1d6b9d3de3098d28bb786d18849f5790a08a9a08
   8 42da70ed92bbecf9f348ba59c93646be723d0bf2
   7 17e841146e5744b81af9959634d82c20a5d7df52
   6 c31065bf97bf014815e37cdfbdef2c32c687f314
@@ -105,7 +87,13 @@
   9 1d6b9d3de3098d28bb786d18849f5790a08a9a08
   8 42da70ed92bbecf9f348ba59c93646be723d0bf2
   7 17e841146e5744b81af9959634d82c20a5d7df52
   6 c31065bf97bf014815e37cdfbdef2c32c687f314
+  5 fcf21b8e0520ec1cced1d7593d13f9ee54721269
+  4 46acd02d0352e4b92bd6a099bb0490305d847a18
+  3 61eeda444b37b8aa3892d5f04c66c5441d21dd66
+  2 e55db11bb0472791c7af3fc636772174cdea4a36
+  1 17a2672b3c24c02d568f99d8d55ccae2bf362d5c
+  0 4e195b4c6e77604b70a8ad3b01306adbb9b1c7e7
 
 Reset the repository
 
@@ -118,6 +106,8 @@
   $ EXIT_AFTER=15 hg --config hggit.mapsavefrequency=10 pull
   pulling from $TESTTMP/gitrepo
   importing git objects into hg
+  transaction abort!
+  rollback completed
   interrupted!
   [255]
   $ hg log -l 10 -T '{rev} {gitnode}\n'
@@ -121,13 +111,7 @@
   interrupted!
   [255]
   $ hg log -l 10 -T '{rev} {gitnode}\n'
-  15 
-  14 
-  13 
-  12 
-  11 
-  10 
   9 1d6b9d3de3098d28bb786d18849f5790a08a9a08
   8 42da70ed92bbecf9f348ba59c93646be723d0bf2
   7 17e841146e5744b81af9959634d82c20a5d7df52
   6 c31065bf97bf014815e37cdfbdef2c32c687f314
@@ -130,7 +114,13 @@
   9 1d6b9d3de3098d28bb786d18849f5790a08a9a08
   8 42da70ed92bbecf9f348ba59c93646be723d0bf2
   7 17e841146e5744b81af9959634d82c20a5d7df52
   6 c31065bf97bf014815e37cdfbdef2c32c687f314
+  5 fcf21b8e0520ec1cced1d7593d13f9ee54721269
+  4 46acd02d0352e4b92bd6a099bb0490305d847a18
+  3 61eeda444b37b8aa3892d5f04c66c5441d21dd66
+  2 e55db11bb0472791c7af3fc636772174cdea4a36
+  1 17a2672b3c24c02d568f99d8d55ccae2bf362d5c
+  0 4e195b4c6e77604b70a8ad3b01306adbb9b1c7e7
   $ cd ..
   $ rm -rf hgrepo
 
@@ -142,6 +132,8 @@
   > --config extensions.breakage=$TESTDIR/testlib/ext-break-git-import.py \
   > clone gitrepo hgrepo
   importing git objects into hg
+  transaction abort!
+  rollback completed
   interrupted!
   [255]
   $ hg id hgrepo