File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 1717# -----------------------------------------------------------------------------
1818
1919
20+ from pathlib import Path
21+
2022from codestory .core .git .git_const import EMPTYTREEHASH
2123from codestory .core .git .git_interface import GitInterface
2224
@@ -302,6 +304,23 @@ def is_bare_repository(self) -> bool:
302304 res = self .git .run_git_text_out (["rev-parse" , "--is-bare-repository" ])
303305 return res .strip () == "true" if res else False
304306
307+ def get_repo_lock (self ) -> str | None :
308+ """Checks if the repository is locked by another git process.
309+
310+ Returns the path to the lock file if it exists, otherwise None.
311+ """
312+ # index.lock is the most common git lock.
313+ # We use rev-parse --git-path to correctly handle worktrees and submodules.
314+ res = self .git .run_git_text_out (["rev-parse" , "--git-path" , "index.lock" ])
315+ if res :
316+ lock_path = Path (res .strip ())
317+ # Handle both absolute and relative paths from git
318+ if not lock_path .is_absolute ():
319+ lock_path = self .git .repo_path / lock_path
320+ if lock_path .exists ():
321+ return str (lock_path )
322+ return None
323+
305324 def try_get_parent_hash (
306325 self , commit_hash : str , empty_on_fail : bool = False
307326 ) -> str | None :
Original file line number Diff line number Diff line change @@ -261,6 +261,29 @@ def validate_git_repository(git_commands: GitCommands) -> None:
261261 if not os .path .exists (os .path .join (str (git_commands .git .repo_path ), ".git" )):
262262 raise GitError ("Not a git repository" )
263263
264+ # Check if the repository is locked
265+ validate_repo_not_locked (git_commands )
266+
267+
268+ def validate_repo_not_locked (git_commands : GitCommands ) -> None :
269+ """Validate that the repository is not locked by another git process.
270+
271+ Args:
272+ git_commands: Git commands to run
273+
274+ Raises:
275+ GitError: If the repository is locked
276+ """
277+ lock_file = git_commands .get_repo_lock ()
278+ if lock_file :
279+ raise GitError (
280+ "Another git process seems to be running in this repository, e.g.\n "
281+ "an editor opened by 'git commit'. Please make sure all processes\n "
282+ "are terminated then try again. If it still fails, a git process\n "
283+ "may have crashed in this repository earlier:\n "
284+ f"remove the file '{ lock_file } ' manually to continue."
285+ )
286+
264287
265288def validate_default_branch (git_commands : GitCommands ) -> None :
266289 """Validate that we are on a branch (not in detached HEAD state).
Original file line number Diff line number Diff line change @@ -91,6 +91,20 @@ def test_validate_git_repository_success(mock_git_commands):
9191
9292 # Should not raise when .git exists
9393 with patch ("os.path.exists" , return_value = True ):
94+ mock_git_commands .get_repo_lock .return_value = None
95+ validate_git_repository (mock_git_commands )
96+
97+
98+ def test_validate_git_repository_locked (mock_git_commands ):
99+ mock_git_commands .is_git_repo .return_value = True
100+ mock_git_commands .git = Mock ()
101+ mock_git_commands .git .repo_path = "/fake"
102+ mock_git_commands .get_repo_lock .return_value = "/fake/.git/index.lock"
103+
104+ with (
105+ patch ("os.path.exists" , return_value = True ),
106+ pytest .raises (GitError , match = "Another git process seems to be running" ),
107+ ):
94108 validate_git_repository (mock_git_commands )
95109
96110
You can’t perform that action at this time.
0 commit comments