... |
... |
@@ -71,8 +71,8 @@ git - stage files from a git repository |
71
|
71
|
"""
|
72
|
72
|
|
73
|
73
|
import os
|
|
74
|
+import errno
|
74
|
75
|
import re
|
75
|
|
-import shutil
|
76
|
76
|
from collections import Mapping
|
77
|
77
|
from io import StringIO
|
78
|
78
|
|
... |
... |
@@ -119,11 +119,21 @@ class GitMirror(SourceFetcher): |
119
|
119
|
fail="Failed to clone git repository {}".format(url),
|
120
|
120
|
fail_temporarily=True)
|
121
|
121
|
|
|
122
|
+ # Attempt atomic rename into destination, this will fail if
|
|
123
|
+ # another process beat us to the punch
|
122
|
124
|
try:
|
123
|
|
- shutil.move(tmpdir, self.mirror)
|
124
|
|
- except (shutil.Error, OSError) as e:
|
125
|
|
- raise SourceError("{}: Failed to move cloned git repository {} from '{}' to '{}'"
|
126
|
|
- .format(self.source, url, tmpdir, self.mirror)) from e
|
|
125
|
+ os.rename(tmpdir, self.mirror)
|
|
126
|
+ except OSError as e:
|
|
127
|
+
|
|
128
|
+ # When renaming and the destination repo already exists, os.rename()
|
|
129
|
+ # will fail with ENOTEMPTY, since an empty directory will be silently
|
|
130
|
+ # replaced
|
|
131
|
+ if e.errno == errno.ENOTEMPTY:
|
|
132
|
+ self.source.status("{}: Discarding duplicate clone of {}"
|
|
133
|
+ .format(self.source, url))
|
|
134
|
+ else:
|
|
135
|
+ raise SourceError("{}: Failed to move cloned git repository {} from '{}' to '{}': {}"
|
|
136
|
+ .format(self.source, url, tmpdir, self.mirror, e)) from e
|
127
|
137
|
|
128
|
138
|
def _fetch(self, alias_override=None):
|
129
|
139
|
url = self.source.translate_url(self.url, alias_override=alias_override)
|