Skip to content

Commit cfd2121

Browse files
committed
Added submodule type and handling through Tree listing.
1 parent 7bfca5e commit cfd2121

File tree

3 files changed

+145
-7
lines changed

3 files changed

+145
-7
lines changed

lib/git/commit.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def __init__(self, repo, id, tree=None, author=None, authored_date=None,
7171
if parents is not None:
7272
self.parents = [Commit(repo, p) for p in parents]
7373
if tree is not None:
74-
self.tree = Tree(repo, id=tree)
74+
self.tree = Tree(repo, id=tree, commit_context = self.id)
7575

7676
def __bake__(self):
7777
"""

lib/git/submodule.py

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# head.py
2+
# Copyright (C) 2008-2010 Michael Trier ([email protected]) and contributors
3+
#
4+
# This module is part of GitPython and is released under
5+
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
6+
7+
import re
8+
9+
class Submodule(object):
10+
"""
11+
A Submodule is a named reference to a Commit on another Repo.
12+
Every Submodule instance contains a name and local and remote paths.
13+
14+
Submodules are very close in behavior to HEAD pointer. It just sits on
15+
top of a structure, in this case, at the end of folders tree, and says
16+
something about that ending.
17+
18+
Examples::
19+
>>> repo = Repo("/path/to/repo")
20+
>>> s = repo.commit('master').tree['lib']['mysubmodule_folder']
21+
>>> s.name
22+
'mysubmodule_folder'
23+
>>> s.path
24+
'/lib/mysubmodule_folder'
25+
>>> s.url
26+
'http://example.com/path/to/repo.git'
27+
>>> s.id
28+
"1c09f116cbc2cb4100fb6935bb162daa4723f455"
29+
"""
30+
31+
def __init__(self, repo=None, id=None, mode=None, name='',
32+
commit_context=None, path=''):
33+
"""
34+
Initialize a newly instanced Submodule
35+
36+
'repo'
37+
Pointer to Repo object instance.
38+
'id'
39+
Is the Sha of the commit on a remote server. This object does NOT
40+
(usually) exist on this, current repo.
41+
'mode'
42+
A black hole at this time. Trying to keep the input args
43+
similar between Tree, Blob and Subprocess instantiation classes.
44+
'name'
45+
This is just the last segment in the submodule's full local path.
46+
It's the name of the actual folder to which a submodule is tied.
47+
'mode'
48+
A black hole at this time. Trying to keep the input args
49+
similar between Tree, Blob and Subprocess instantiation classes.
50+
'commit_context'
51+
A string with ID of the commit that was the root for the tree
52+
structure that lead us to this folder (Tree object) that contains
53+
this submodule reference.
54+
See comments in Tree object code for background.
55+
'path'
56+
This is the "longer" version of "name" argument. It includes all
57+
the parent folders we passed on the way from root of the commit to
58+
this point in the folder tree.
59+
Submodules in the .gitmodules are referenced by their full path
60+
and contents of this argument are used to retrieve the URI of the
61+
remote repo tied to this full local path.
62+
Example: "/lib/vendor/vendors_repoA"
63+
"""
64+
self.repo = repo
65+
self.id = id
66+
self.path = path
67+
self.name = name
68+
self._commit_context = commit_context
69+
self._cached_URI = None
70+
71+
def getURI(self, commit_context = None):
72+
'''Returns the remote repo URI for the submodule.
73+
74+
This data is NOT stored in the blob or anywhere close. It's in a
75+
.gitmodules file in the root Tree of SOME commit.
76+
77+
We need to know what commit to look into to look for .gitmodules.
78+
79+
We try to retain the "origin" commit ID within the object when we
80+
traverse the Tree chain if it started with a particular commit.
81+
82+
When this does not work, or if you want to override the behavior,
83+
pass the string with commit's ID to the commit_context argument.
84+
'''
85+
if not self._cached_URI and ( commit_context or self._commit_context ):
86+
_b = self.repo.commit(commit_context or self._commit_context).tree.get('.gitmodules')
87+
if _b:
88+
_m = re.findall(
89+
r'\[submodule "[^\t]+?\s+path\s*=\s*([^\t]+)\s+url\s*=\s*([^\t]+)'
90+
,'\t'.join(_b.data.splitlines())
91+
)
92+
for _e in _m:
93+
if _e[0] == self.path.strip('/'):
94+
self._cached_URI = _e[1].strip().strip('"').strip("'")
95+
break
96+
return self._cached_URI
97+
98+
@property
99+
def url(self):
100+
return self.getURI()
101+
102+
def __repr__(self):
103+
return '<git.Submodule "%s">' % self.id

lib/git/tree.py

+41-6
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,47 @@
77
import os
88
from lazy import LazyMixin
99
import blob
10+
import submodule
1011

1112
class Tree(LazyMixin):
12-
def __init__(self, repo, id, mode=None, name=None):
13+
def __init__(self, repo, id, mode=None, name=None, commit_context = None, path = ''):
1314
LazyMixin.__init__(self)
1415
self.repo = repo
1516
self.id = id
1617
self.mode = mode
1718
self.name = name
19+
# commit_context (A string with ID of the commit) is a "crutch" that
20+
# allows us to look up details for submodules, should we find any in
21+
# this particular tree.
22+
# Trees don't have a reference to parent (commit, other tree).
23+
# They can have infinite amounts of parents.
24+
# However, we need to know what commit got us to this particular
25+
# tree if we want to know the URI of the submodule.
26+
# The commit ID of the repo pointed out by submodule is here, in the tree.
27+
# However, the only way to know what URI that submodule refers to is
28+
# to read .gitmodules file that's in the top-most tree of SOME commit.
29+
# Each commit can have a different version of .gitmodule, but through
30+
# tree chain lead to the same Tree instance where the submodule is rooted.
31+
#
32+
# There is a short-cut. If submodule is placed in top-most Tree in a
33+
# commit (i.e. submodule's path value is "mysubmodule") the .gitmodules
34+
# file will be in the same exact tree. YEY! we just read that and know
35+
# the submodule's URI. Shortcut is gone when submodule is nested in the
36+
# commit like so: "commonfolder/otherfolder/mysubmodule" In this case,
37+
# commit's root tree will have "Tree 'commonfolder'" which will have
38+
# "Tree "otherfolder", which will have "Submodule 'mysubmodule'"
39+
# By the time we get to "Tree 'otherfolder'" we don't know where to
40+
# look for ".gitmodules". This is what commit_context is for.
41+
# The only way you get a value here if you either set it by hand, or
42+
# traverse the Tree chain that started with CommitInstance.tree, which
43+
# populates the context upon Tree instantiation.
44+
self.commit_context = commit_context
45+
# path is the friend commit_context. since trees don't have links to
46+
# parents, we have no clue what the "full local path" of a child
47+
# submodule would be. Submodules are listed as "name" in trees and
48+
# as "folder/folder/name" in .gitmodules. path helps us keep up with the
49+
# the folder changes.
50+
self.path = path
1851
self._contents = None
1952

2053
def __bake__(self):
@@ -26,12 +59,12 @@ def __bake__(self):
2659
# Read the tree contents.
2760
self._contents = {}
2861
for line in self.repo.git.ls_tree(self.id).splitlines():
29-
obj = self.content_from_string(self.repo, line)
62+
obj = self.content_from_string(self.repo, line, commit_context = self.commit_context, path = self.path)
3063
if obj is not None:
3164
self._contents[obj.name] = obj
3265

3366
@staticmethod
34-
def content_from_string(repo, text):
67+
def content_from_string(repo, text, commit_context = None, path=''):
3568
"""
3669
Parse a content item and create the appropriate object
3770
@@ -50,11 +83,13 @@ def content_from_string(repo, text):
5083
return None
5184

5285
if typ == "tree":
53-
return Tree(repo, id=id, mode=mode, name=name)
86+
return Tree(repo, id=id, mode=mode, name=name,
87+
commit_context = commit_context, path='/'.join([path,name]))
5488
elif typ == "blob":
5589
return blob.Blob(repo, id=id, mode=mode, name=name)
56-
elif typ == "commit":
57-
return None
90+
elif typ == "commit" and mode == '160000':
91+
return submodule.Submodule(repo, id=id, name=name,
92+
commit_context = commit_context, path='/'.join([path,name]))
5893
else:
5994
raise(TypeError, "Invalid type: %s" % typ)
6095

0 commit comments

Comments
 (0)