Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit aec58a9

Browse filesBrowse files
vathpelaByron
authored andcommitted
Repo: handle worktrees better
This makes Repo("foo") work when foo/.git is a file of the form created by "git worktree add", i.e. it's a text file that says: gitdir: /home/me/project/.git/worktrees/bar and where /home/me/project/.git/ is the nominal gitdir, but /home/me/project/.git/worktrees/bar has this worktree's HEAD etc and a "gitdir" file that contains the path of foo/.git . Signed-off-by: Peter Jones <pjones@redhat.com>
1 parent 4bd708d commit aec58a9
Copy full SHA for aec58a9

6 files changed

+105-19Lines changed: 105 additions & 19 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎AUTHORS‎

Copy file name to clipboardExpand all lines: AUTHORS
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ Contributors are:
1818
-Bernard `Guyzmo` Pratz <guyzmo+gitpython+pub@m0g.net>
1919
-Timothy B. Hartman <tbhartman _at_ gmail.com>
2020
-Konstantin Popov <konstantin.popov.89 _at_ yandex.ru>
21+
-Peter Jones <pjones _at_ redhat.com>
2122

2223
Portions derived from other open source works and are clearly marked.
Collapse file

‎git/refs/symbolic.py‎

Copy file name to clipboardExpand all lines: git/refs/symbolic.py
+24-3Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,12 @@ def abspath(self):
7575

7676
@classmethod
7777
def _get_packed_refs_path(cls, repo):
78-
return osp.join(repo.git_dir, 'packed-refs')
78+
try:
79+
commondir = open(osp.join(repo.git_dir, 'commondir'), 'rt').readlines()[0].strip()
80+
except (OSError, IOError):
81+
commondir = '.'
82+
repodir = osp.join(repo.git_dir, commondir)
83+
return osp.join(repodir, 'packed-refs')
7984

8085
@classmethod
8186
def _iter_packed_refs(cls, repo):
@@ -122,13 +127,13 @@ def dereference_recursive(cls, repo, ref_path):
122127
# END recursive dereferencing
123128

124129
@classmethod
125-
def _get_ref_info(cls, repo, ref_path):
130+
def _get_ref_info_helper(cls, repo, repodir, ref_path):
126131
"""Return: (str(sha), str(target_ref_path)) if available, the sha the file at
127132
rela_path points to, or None. target_ref_path is the reference we
128133
point to, or None"""
129134
tokens = None
130135
try:
131-
with open(osp.join(repo.git_dir, ref_path), 'rt') as fp:
136+
with open(osp.join(repodir, ref_path), 'rt') as fp:
132137
value = fp.read().rstrip()
133138
# Don't only split on spaces, but on whitespace, which allows to parse lines like
134139
# 60b64ef992065e2600bfef6187a97f92398a9144 branch 'master' of git-server:/path/to/repo
@@ -159,6 +164,22 @@ def _get_ref_info(cls, repo, ref_path):
159164

160165
raise ValueError("Failed to parse reference information from %r" % ref_path)
161166

167+
@classmethod
168+
def _get_ref_info(cls, repo, ref_path):
169+
"""Return: (str(sha), str(target_ref_path)) if available, the sha the file at
170+
rela_path points to, or None. target_ref_path is the reference we
171+
point to, or None"""
172+
try:
173+
return cls._get_ref_info_helper(repo, repo.git_dir, ref_path)
174+
except ValueError:
175+
try:
176+
commondir = open(osp.join(repo.git_dir, 'commondir'), 'rt').readlines()[0].strip()
177+
except (OSError, IOError):
178+
commondir = '.'
179+
180+
repodir = osp.join(repo.git_dir, commondir)
181+
return cls._get_ref_info_helper(repo, repodir, ref_path)
182+
162183
def _get_object(self):
163184
"""
164185
:return:
Collapse file

‎git/repo/base.py‎

Copy file name to clipboardExpand all lines: git/repo/base.py
+8-3Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from git.util import Actor, finalize_process, decygpath, hex_to_bin
3333
import os.path as osp
3434

35-
from .fun import rev_parse, is_git_dir, find_submodule_git_dir, touch
35+
from .fun import rev_parse, is_git_dir, find_submodule_git_dir, touch, find_worktree_git_dir
3636
import gc
3737
import gitdb
3838

@@ -138,10 +138,15 @@ def __init__(self, path=None, odbt=DefaultDBType, search_parent_directories=Fals
138138
self._working_tree_dir = os.getenv('GIT_WORK_TREE', os.path.dirname(self.git_dir))
139139
break
140140

141-
sm_gitpath = find_submodule_git_dir(osp.join(curpath, '.git'))
141+
dotgit = osp.join(curpath, '.git')
142+
sm_gitpath = find_submodule_git_dir(dotgit)
142143
if sm_gitpath is not None:
143144
self.git_dir = osp.normpath(sm_gitpath)
144-
sm_gitpath = find_submodule_git_dir(osp.join(curpath, '.git'))
145+
146+
sm_gitpath = find_submodule_git_dir(dotgit)
147+
if sm_gitpath is None:
148+
sm_gitpath = find_worktree_git_dir(dotgit)
149+
145150
if sm_gitpath is not None:
146151
self.git_dir = _expand_path(sm_gitpath)
147152
self._working_tree_dir = curpath
Collapse file

‎git/repo/fun.py‎

Copy file name to clipboardExpand all lines: git/repo/fun.py
+21-1Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Package with general repository related functions"""
22
import os
3+
import stat
34
from string import digits
45

56
from git.compat import xrange
@@ -17,7 +18,7 @@
1718

1819

1920
__all__ = ('rev_parse', 'is_git_dir', 'touch', 'find_submodule_git_dir', 'name_to_object', 'short_to_long', 'deref_tag',
20-
'to_commit')
21+
'to_commit', 'find_worktree_git_dir')
2122

2223

2324
def touch(filename):
@@ -47,6 +48,25 @@ def is_git_dir(d):
4748
return False
4849

4950

51+
def find_worktree_git_dir(dotgit):
52+
"""Search for a gitdir for this worktree."""
53+
try:
54+
statbuf = os.stat(dotgit)
55+
except OSError:
56+
return None
57+
if not stat.S_ISREG(statbuf.st_mode):
58+
return None
59+
60+
try:
61+
lines = open(dotgit, 'r').readlines()
62+
for key, value in [line.strip().split(': ') for line in lines]:
63+
if key == 'gitdir':
64+
return value
65+
except ValueError:
66+
pass
67+
return None
68+
69+
5070
def find_submodule_git_dir(d):
5171
"""Search for a submodule repo."""
5272
if is_git_dir(d):
Collapse file

‎git/test/test_fun.py‎

Copy file name to clipboardExpand all lines: git/test/test_fun.py
+37-5Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
from io import BytesIO
22
from stat import S_IFDIR, S_IFREG, S_IFLNK
3+
from os import stat
4+
import os.path as osp
5+
36
try:
4-
from unittest import skipIf
7+
from unittest import skipIf, SkipTest
58
except ImportError:
6-
from unittest2 import skipIf
9+
from unittest2 import skipIf, SkipTest
710

11+
from git import Git
812
from git.compat import PY3
913
from git.index import IndexFile
1014
from git.index.fun import (
@@ -14,13 +18,18 @@
1418
traverse_tree_recursive,
1519
traverse_trees_recursive,
1620
tree_to_stream,
17-
tree_entries_from_data
21+
tree_entries_from_data,
22+
)
23+
from git.repo.fun import (
24+
find_worktree_git_dir
1825
)
1926
from git.test.lib import (
27+
assert_true,
2028
TestBase,
21-
with_rw_repo
29+
with_rw_repo,
30+
with_rw_directory
2231
)
23-
from git.util import bin_to_hex
32+
from git.util import bin_to_hex, cygpath, join_path_native
2433
from gitdb.base import IStream
2534
from gitdb.typ import str_tree_type
2635

@@ -254,6 +263,29 @@ def test_tree_traversal_single(self):
254263
assert entries
255264
# END for each commit
256265

266+
@with_rw_directory
267+
def test_linked_worktree_traversal(self, rw_dir):
268+
"""Check that we can identify a linked worktree based on a .git file"""
269+
git = Git(rw_dir)
270+
if git.version_info[:3] < (2, 5, 1):
271+
raise SkipTest("worktree feature unsupported")
272+
273+
rw_master = self.rorepo.clone(join_path_native(rw_dir, 'master_repo'))
274+
branch = rw_master.create_head('aaaaaaaa')
275+
worktree_path = join_path_native(rw_dir, 'worktree_repo')
276+
if Git.is_cygwin():
277+
worktree_path = cygpath(worktree_path)
278+
rw_master.git.worktree('add', worktree_path, branch.name)
279+
280+
dotgit = osp.join(worktree_path, ".git")
281+
statbuf = stat(dotgit)
282+
assert_true(statbuf.st_mode & S_IFREG)
283+
284+
gitdir = find_worktree_git_dir(dotgit)
285+
self.assertIsNotNone(gitdir)
286+
statbuf = stat(gitdir)
287+
assert_true(statbuf.st_mode & S_IFDIR)
288+
257289
@skipIf(PY3, 'odd types returned ... maybe figure it out one day')
258290
def test_tree_entries_from_data_with_failing_name_decode_py2(self):
259291
r = tree_entries_from_data(b'100644 \x9f\0aaa')
Collapse file

‎git/test/test_repo.py‎

Copy file name to clipboardExpand all lines: git/test/test_repo.py
+14-7Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
NoSuchPathError,
2323
Head,
2424
Commit,
25+
Object,
2526
Tree,
2627
IndexFile,
2728
Git,
@@ -911,22 +912,28 @@ def test_is_ancestor(self):
911912
self.assertRaises(GitCommandError, repo.is_ancestor, i, j)
912913

913914
@with_rw_directory
914-
def test_work_tree_unsupported(self, rw_dir):
915+
def test_git_work_tree_dotgit(self, rw_dir):
916+
"""Check that we find .git as a worktree file and find the worktree
917+
based on it."""
915918
git = Git(rw_dir)
916919
if git.version_info[:3] < (2, 5, 1):
917920
raise SkipTest("worktree feature unsupported")
918921

919922
rw_master = self.rorepo.clone(join_path_native(rw_dir, 'master_repo'))
920-
rw_master.git.checkout('HEAD~10')
923+
branch = rw_master.create_head('aaaaaaaa')
921924
worktree_path = join_path_native(rw_dir, 'worktree_repo')
922925
if Git.is_cygwin():
923926
worktree_path = cygpath(worktree_path)
924-
try:
925-
rw_master.git.worktree('add', worktree_path, 'master')
926-
except Exception as ex:
927-
raise AssertionError(ex, "It's ok if TC not running from `master`.")
927+
rw_master.git.worktree('add', worktree_path, branch.name)
928+
929+
# this ensures that we can read the repo's gitdir correctly
930+
repo = Repo(worktree_path)
931+
self.assertIsInstance(repo, Repo)
928932

929-
self.failUnlessRaises(InvalidGitRepositoryError, Repo, worktree_path)
933+
# this ensures we're able to actually read the refs in the tree, which
934+
# means we can read commondir correctly.
935+
commit = repo.head.commit
936+
self.assertIsInstance(commit, Object)
930937

931938
@with_rw_directory
932939
def test_git_work_tree_env(self, rw_dir):

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.