/bangkokhotel/lib/python2.5/site-packages/pip-1.2.1-py2.5.egg/pip/vcs/git.py
Python | 221 lines | 195 code | 12 blank | 14 comment | 16 complexity | ff9a8a150d5c3cd3517c7ce7b6f10125 MD5 | raw file
1import tempfile
2import re
3import os.path
4from pip.util import call_subprocess
5from pip.util import display_path, rmtree
6from pip.vcs import vcs, VersionControl
7from pip.log import logger
8from pip.backwardcompat import url2pathname, urlparse
9urlsplit = urlparse.urlsplit
10urlunsplit = urlparse.urlunsplit
11
12
13class Git(VersionControl):
14 name = 'git'
15 dirname = '.git'
16 repo_name = 'clone'
17 schemes = ('git', 'git+http', 'git+https', 'git+ssh', 'git+git', 'git+file')
18 bundle_file = 'git-clone.txt'
19 guide = ('# This was a Git repo; to make it a repo again run:\n'
20 'git init\ngit remote add origin %(url)s -f\ngit checkout %(rev)s\n')
21
22 def __init__(self, url=None, *args, **kwargs):
23
24 # Works around an apparent Git bug
25 # (see http://article.gmane.org/gmane.comp.version-control.git/146500)
26 if url:
27 scheme, netloc, path, query, fragment = urlsplit(url)
28 if scheme.endswith('file'):
29 initial_slashes = path[:-len(path.lstrip('/'))]
30 newpath = initial_slashes + url2pathname(path).replace('\\', '/').lstrip('/')
31 url = urlunsplit((scheme, netloc, newpath, query, fragment))
32 after_plus = scheme.find('+') + 1
33 url = scheme[:after_plus] + urlunsplit((scheme[after_plus:], netloc, newpath, query, fragment))
34
35 super(Git, self).__init__(url, *args, **kwargs)
36
37 def parse_vcs_bundle_file(self, content):
38 url = rev = None
39 for line in content.splitlines():
40 if not line.strip() or line.strip().startswith('#'):
41 continue
42 url_match = re.search(r'git\s*remote\s*add\s*origin(.*)\s*-f', line)
43 if url_match:
44 url = url_match.group(1).strip()
45 rev_match = re.search(r'^git\s*checkout\s*-q\s*(.*)\s*', line)
46 if rev_match:
47 rev = rev_match.group(1).strip()
48 if url and rev:
49 return url, rev
50 return None, None
51
52 def export(self, location):
53 """Export the Git repository at the url to the destination location"""
54 temp_dir = tempfile.mkdtemp('-export', 'pip-')
55 self.unpack(temp_dir)
56 try:
57 if not location.endswith('/'):
58 location = location + '/'
59 call_subprocess(
60 [self.cmd, 'checkout-index', '-a', '-f', '--prefix', location],
61 filter_stdout=self._filter, show_stdout=False, cwd=temp_dir)
62 finally:
63 rmtree(temp_dir)
64
65 def check_rev_options(self, rev, dest, rev_options):
66 """Check the revision options before checkout to compensate that tags
67 and branches may need origin/ as a prefix.
68 Returns the SHA1 of the branch or tag if found.
69 """
70 revisions = self.get_tag_revs(dest)
71 revisions.update(self.get_branch_revs(dest))
72
73 origin_rev = 'origin/%s' % rev
74 if origin_rev in revisions:
75 # remote branch
76 return [revisions[origin_rev]]
77 elif rev in revisions:
78 # a local tag or branch name
79 return [revisions[rev]]
80 else:
81 logger.warn("Could not find a tag or branch '%s', assuming commit." % rev)
82 return rev_options
83
84 def switch(self, dest, url, rev_options):
85 call_subprocess(
86 [self.cmd, 'config', 'remote.origin.url', url], cwd=dest)
87 call_subprocess(
88 [self.cmd, 'checkout', '-q'] + rev_options, cwd=dest)
89
90 self.update_submodules(dest)
91
92 def update(self, dest, rev_options):
93 # First fetch changes from the default remote
94 call_subprocess([self.cmd, 'fetch', '-q'], cwd=dest)
95 # Then reset to wanted revision (maby even origin/master)
96 if rev_options:
97 rev_options = self.check_rev_options(rev_options[0], dest, rev_options)
98 call_subprocess([self.cmd, 'reset', '--hard', '-q'] + rev_options, cwd=dest)
99 #: update submodules
100 self.update_submodules(dest)
101
102 def obtain(self, dest):
103 url, rev = self.get_url_rev()
104 if rev:
105 rev_options = [rev]
106 rev_display = ' (to %s)' % rev
107 else:
108 rev_options = ['origin/master']
109 rev_display = ''
110 if self.check_destination(dest, url, rev_options, rev_display):
111 logger.notify('Cloning %s%s to %s' % (url, rev_display, display_path(dest)))
112 call_subprocess([self.cmd, 'clone', '-q', url, dest])
113 #: repo may contain submodules
114 self.update_submodules(dest)
115 if rev:
116 rev_options = self.check_rev_options(rev, dest, rev_options)
117 # Only do a checkout if rev_options differs from HEAD
118 if not self.get_revision(dest).startswith(rev_options[0]):
119 call_subprocess([self.cmd, 'checkout', '-q'] + rev_options, cwd=dest)
120
121 def get_url(self, location):
122 url = call_subprocess(
123 [self.cmd, 'config', 'remote.origin.url'],
124 show_stdout=False, cwd=location)
125 return url.strip()
126
127 def get_revision(self, location):
128 current_rev = call_subprocess(
129 [self.cmd, 'rev-parse', 'HEAD'], show_stdout=False, cwd=location)
130 return current_rev.strip()
131
132 def get_tag_revs(self, location):
133 tags = self._get_all_tag_names(location)
134 tag_revs = {}
135 for line in tags.splitlines():
136 tag = line.strip()
137 rev = self._get_revision_from_rev_parse(tag, location)
138 tag_revs[tag] = rev.strip()
139 return tag_revs
140
141 def get_branch_revs(self, location):
142 branches = self._get_all_branch_names(location)
143 branch_revs = {}
144 for line in branches.splitlines():
145 if '(no branch)' in line:
146 continue
147 line = line.split('->')[0].strip()
148 # actual branch case
149 branch = "".join(b for b in line.split() if b != '*')
150 rev = self._get_revision_from_rev_parse(branch, location)
151 branch_revs[branch] = rev.strip()
152 return branch_revs
153
154 def get_src_requirement(self, dist, location, find_tags):
155 repo = self.get_url(location)
156 if not repo.lower().startswith('git:'):
157 repo = 'git+' + repo
158 egg_project_name = dist.egg_name().split('-', 1)[0]
159 if not repo:
160 return None
161 current_rev = self.get_revision(location)
162 tag_revs = self.get_tag_revs(location)
163 branch_revs = self.get_branch_revs(location)
164
165 if current_rev in tag_revs:
166 # It's a tag
167 full_egg_name = '%s-%s' % (egg_project_name, tag_revs[current_rev])
168 elif (current_rev in branch_revs and
169 branch_revs[current_rev] != 'origin/master'):
170 # It's the head of a branch
171 full_egg_name = '%s-%s' % (
172 egg_project_name,
173 branch_revs[current_rev].replace('origin/', '')
174 )
175 else:
176 full_egg_name = '%s-dev' % egg_project_name
177
178 return '%s@%s#egg=%s' % (repo, current_rev, full_egg_name)
179
180 def get_url_rev(self):
181 """
182 Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'.
183 That's required because although they use SSH they sometimes doesn't
184 work with a ssh:// scheme (e.g. Github). But we need a scheme for
185 parsing. Hence we remove it again afterwards and return it as a stub.
186 """
187 if not '://' in self.url:
188 assert not 'file:' in self.url
189 self.url = self.url.replace('git+', 'git+ssh://')
190 url, rev = super(Git, self).get_url_rev()
191 url = url.replace('ssh://', '')
192 else:
193 url, rev = super(Git, self).get_url_rev()
194
195 return url, rev
196
197 def _get_all_tag_names(self, location):
198 return call_subprocess([self.cmd, 'tag', '-l'],
199 show_stdout=False,
200 raise_on_returncode=False,
201 cwd=location)
202
203 def _get_all_branch_names(self, location):
204 remote_branches = call_subprocess([self.cmd, 'branch', '-r'],
205 show_stdout=False, cwd=location)
206 local_branches = call_subprocess([self.cmd, 'branch', '-l'],
207 show_stdout=False, cwd=location)
208 return remote_branches + local_branches
209
210 def _get_revision_from_rev_parse(self, name, location):
211 return call_subprocess([self.cmd, 'rev-parse', name],
212 show_stdout=False, cwd=location)
213
214 def update_submodules(self, location):
215 if not os.path.exists(os.path.join(location, '.gitmodules')):
216 return
217 call_subprocess([self.cmd, 'submodule', 'init', '-q'], cwd=location)
218 call_subprocess([self.cmd, 'submodule', 'update', '--recursive', '-q'],
219 cwd=location)
220
221vcs.register(Git)