summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaushik Lingarkar <kaushikl@qti.qualcomm.com>2025-11-03 19:58:26 -0800
committerLUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2025-12-05 10:35:46 -0800
commit50c6226075258736c3e869242345a59ed7064da4 (patch)
treec7173b22519ed515e23c386e6a800be5d9603377
parent1e4b2887a760e84f3b453c9caadcae67b701eac9 (diff)
downloadgit-repo-50c6226075258736c3e869242345a59ed7064da4.tar.gz
Prevent leftover bare gitdirs after failed sync attempts
The gitdir for a project may be left in a state with bare=true due to a previous failed sync. In this state, during a subsequent sync attempt, repo will skip initializing the gitdir (since the directory already exists) and directly attempt to checkout the worktree, which will fail because the project is bare. To reduce the chance of this happening, initialize the gitdir in a temp directory and move it once it is ready. Bug: 457478027 Change-Id: I4767494a3a54e7734174eae3a0d939fa9d174288 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/524203 Tested-by: Kaushik Lingarkar <kaushikl@qti.qualcomm.com> Commit-Queue: Kaushik Lingarkar <kaushikl@qti.qualcomm.com> Reviewed-by: Mike Frysinger <vapier@google.com> Reviewed-by: Gavin Mak <gavinmak@google.com>
-rw-r--r--project.py70
1 files changed, 53 insertions, 17 deletions
diff --git a/project.py b/project.py
index d3c53c1b8..d90a53415 100644
--- a/project.py
+++ b/project.py
@@ -12,6 +12,7 @@
12# See the License for the specific language governing permissions and 12# See the License for the specific language governing permissions and
13# limitations under the License. 13# limitations under the License.
14 14
15import datetime
15import errno 16import errno
16import filecmp 17import filecmp
17import glob 18import glob
@@ -3073,8 +3074,13 @@ class Project:
3073 raise GitError(f"{self.name} merge {head} ", project=self.name) 3074 raise GitError(f"{self.name} merge {head} ", project=self.name)
3074 3075
3075 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False): 3076 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
3077 # Prefix for temporary directories created during gitdir initialization.
3078 TMP_GITDIR_PREFIX = ".tmp-project-initgitdir-"
3076 init_git_dir = not os.path.exists(self.gitdir) 3079 init_git_dir = not os.path.exists(self.gitdir)
3077 init_obj_dir = not os.path.exists(self.objdir) 3080 init_obj_dir = not os.path.exists(self.objdir)
3081 tmp_gitdir = None
3082 curr_gitdir = self.gitdir
3083 curr_config = self.config
3078 try: 3084 try:
3079 # Initialize the bare repository, which contains all of the objects. 3085 # Initialize the bare repository, which contains all of the objects.
3080 if init_obj_dir: 3086 if init_obj_dir:
@@ -3094,27 +3100,33 @@ class Project:
3094 # well. 3100 # well.
3095 if self.objdir != self.gitdir: 3101 if self.objdir != self.gitdir:
3096 if init_git_dir: 3102 if init_git_dir:
3097 os.makedirs(self.gitdir) 3103 os.makedirs(os.path.dirname(self.gitdir), exist_ok=True)
3104 tmp_gitdir = tempfile.mkdtemp(
3105 prefix=TMP_GITDIR_PREFIX,
3106 dir=os.path.dirname(self.gitdir),
3107 )
3108 curr_config = GitConfig.ForRepository(
3109 gitdir=tmp_gitdir, defaults=self.manifest.globalConfig
3110 )
3111 curr_gitdir = tmp_gitdir
3098 3112
3099 if init_obj_dir or init_git_dir: 3113 if init_obj_dir or init_git_dir:
3100 self._ReferenceGitDir( 3114 self._ReferenceGitDir(
3101 self.objdir, self.gitdir, copy_all=True 3115 self.objdir, curr_gitdir, copy_all=True
3102 ) 3116 )
3103 try: 3117 try:
3104 self._CheckDirReference(self.objdir, self.gitdir) 3118 self._CheckDirReference(self.objdir, curr_gitdir)
3105 except GitError as e: 3119 except GitError as e:
3106 if force_sync: 3120 if force_sync:
3107 logger.error(
3108 "Retrying clone after deleting %s", self.gitdir
3109 )
3110 try: 3121 try:
3111 platform_utils.rmtree(os.path.realpath(self.gitdir)) 3122 rm_dirs = (
3112 if self.worktree and os.path.exists( 3123 tmp_gitdir,
3113 os.path.realpath(self.worktree) 3124 self.gitdir,
3114 ): 3125 self.worktree,
3115 platform_utils.rmtree( 3126 )
3116 os.path.realpath(self.worktree) 3127 for d in rm_dirs:
3117 ) 3128 if d and os.path.exists(d):
3129 platform_utils.rmtree(os.path.realpath(d))
3118 return self._InitGitDir( 3130 return self._InitGitDir(
3119 mirror_git=mirror_git, 3131 mirror_git=mirror_git,
3120 force_sync=False, 3132 force_sync=False,
@@ -3165,18 +3177,21 @@ class Project:
3165 m = self.manifest.manifestProject.config 3177 m = self.manifest.manifestProject.config
3166 for key in ["user.name", "user.email"]: 3178 for key in ["user.name", "user.email"]:
3167 if m.Has(key, include_defaults=False): 3179 if m.Has(key, include_defaults=False):
3168 self.config.SetString(key, m.GetString(key)) 3180 curr_config.SetString(key, m.GetString(key))
3169 if not self.manifest.EnableGitLfs: 3181 if not self.manifest.EnableGitLfs:
3170 self.config.SetString( 3182 curr_config.SetString(
3171 "filter.lfs.smudge", "git-lfs smudge --skip -- %f" 3183 "filter.lfs.smudge", "git-lfs smudge --skip -- %f"
3172 ) 3184 )
3173 self.config.SetString( 3185 curr_config.SetString(
3174 "filter.lfs.process", "git-lfs filter-process --skip" 3186 "filter.lfs.process", "git-lfs filter-process --skip"
3175 ) 3187 )
3176 self.config.SetBoolean( 3188 curr_config.SetBoolean(
3177 "core.bare", True if self.manifest.IsMirror else None 3189 "core.bare", True if self.manifest.IsMirror else None
3178 ) 3190 )
3179 3191
3192 if tmp_gitdir:
3193 platform_utils.rename(tmp_gitdir, self.gitdir)
3194 tmp_gitdir = None
3180 if not init_obj_dir: 3195 if not init_obj_dir:
3181 # The project might be shared (obj_dir already initialized), but 3196 # The project might be shared (obj_dir already initialized), but
3182 # such information is not available here. Instead of passing it, 3197 # such information is not available here. Instead of passing it,
@@ -3193,6 +3208,27 @@ class Project:
3193 if init_git_dir and os.path.exists(self.gitdir): 3208 if init_git_dir and os.path.exists(self.gitdir):
3194 platform_utils.rmtree(self.gitdir) 3209 platform_utils.rmtree(self.gitdir)
3195 raise 3210 raise
3211 finally:
3212 # Clean up the temporary directory created during the process,
3213 # as well as any stale ones left over from previous attempts.
3214 if tmp_gitdir and os.path.exists(tmp_gitdir):
3215 platform_utils.rmtree(tmp_gitdir)
3216
3217 age_threshold = datetime.timedelta(days=1)
3218 now = datetime.datetime.now()
3219 for tmp_dir in glob.glob(
3220 os.path.join(
3221 os.path.dirname(self.gitdir), f"{TMP_GITDIR_PREFIX}*"
3222 )
3223 ):
3224 try:
3225 mtime = datetime.datetime.fromtimestamp(
3226 os.path.getmtime(tmp_dir)
3227 )
3228 if now - mtime > age_threshold:
3229 platform_utils.rmtree(tmp_dir)
3230 except OSError:
3231 pass
3196 3232
3197 def _UpdateHooks(self, quiet=False): 3233 def _UpdateHooks(self, quiet=False):
3198 if os.path.exists(self.objdir): 3234 if os.path.exists(self.objdir):