Skip to content

Commit f068cdc

Browse files
committed
Initially working implementation of short-sha parsing and interpretation, thanks to new gitdb functionality
1 parent 9059525 commit f068cdc

File tree

5 files changed

+80
-48
lines changed

5 files changed

+80
-48
lines changed

lib/git/db.py

+24-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
"""Module with our own gitdb implementation - it uses the git command"""
1+
"""Module with our own gitdb implementation - it uses the git command"""
2+
from exc import GitCommandError
3+
24
from gitdb.base import (
35
OInfo,
46
OStream
57
)
68

7-
from gitdb.util import bin_to_hex
8-
9+
from gitdb.util import (
10+
bin_to_hex,
11+
hex_to_bin
12+
)
913
from gitdb.db import GitDB
1014
from gitdb.db import LooseObjectDB
1115

@@ -35,3 +39,20 @@ def stream(self, sha):
3539
t = self._git.stream_object_data(bin_to_hex(sha))
3640
return OStream(*t)
3741

42+
43+
# { Interface
44+
45+
def partial_to_complete_sha_hex(partial_hexsha):
46+
""":return: Full binary 20 byte sha from the given partial hexsha
47+
:raise AmbiguousObjectName:
48+
:raise BadObject:
49+
:note: currently we only raise BadObject as git does not communicate
50+
AmbiguousObjects separately"""
51+
try:
52+
hexsha, typename, size = self._git.get_object_header(partial_hexsha)
53+
return hex_to_bin(hexsha)
54+
except GitCommandError:
55+
raise BadObject(partial_hexsha)
56+
# END handle exceptions
57+
58+
#} END interface

lib/git/ext/gitdb

Submodule gitdb updated from 46bf471 to ac7d475

lib/git/repo/base.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ class Repo(object):
5858
# precompiled regex
5959
re_whitespace = re.compile(r'\s+')
6060
re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$')
61-
re_hexsha_shortened = re.compile('^[0-9A-Fa-f]{7,40}$')
62-
re_hexsha_domain = re.compile('^[0-9A-Fa-f]{1,40}$')
61+
re_hexsha_shortened = re.compile('^[0-9A-Fa-f]{4,40}$')
6362
re_author_committer_start = re.compile(r'^(author|committer)')
6463
re_tab_full_line = re.compile(r'^\t(.*)$')
6564

lib/git/repo/fun.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
join,
88
isdir,
99
isfile,
10-
hex_to_bin
10+
hex_to_bin,
11+
bin_to_hex
1112
)
12-
1313
from string import digits
1414

1515
__all__ = ('rev_parse', 'is_git_dir', 'touch')
@@ -30,6 +30,18 @@ def is_git_dir(d):
3030
os.readlink(headref).startswith('refs'))
3131
return False
3232

33+
34+
def short_to_long(odb, hexsha):
35+
""":return: long hexadecimal sha1 from the given less-than-40 byte hexsha
36+
or None if no candidate could be found.
37+
:param hexsha: hexsha with less than 40 byte"""
38+
try:
39+
return bin_to_hex(odb.partial_to_complete_sha_hex(hexsha))
40+
except BadObject:
41+
return None
42+
# END exception handling
43+
44+
3345
def name_to_object(repo, name):
3446
""":return: object specified by the given name, hexshas ( short and long )
3547
as well as references are supported"""
@@ -39,7 +51,7 @@ def name_to_object(repo, name):
3951
if repo.re_hexsha_shortened.match(name):
4052
if len(name) != 40:
4153
# find long sha for short sha
42-
raise NotImplementedError("short sha parsing")
54+
hexsha = short_to_long(repo.odb, name)
4355
else:
4456
hexsha = name
4557
# END handle short shas
@@ -55,11 +67,6 @@ def name_to_object(repo, name):
5567

5668
# tried everything ? fail
5769
if hexsha is None:
58-
# it could also be a very short ( less than 7 ) hexsha, which
59-
# wasnt tested in the first run
60-
if len(name) < 7 and repo.re_hexsha_domain.match(name):
61-
raise NotImplementedError()
62-
# END try short name
6370
raise BadObject(name)
6471
# END assert hexsha was found
6572

test/git/test_repo.py

+39-34
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,45 @@
33
#
44
# This module is part of GitPython and is released under
55
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
6-
7-
import os, sys
86
from test.testlib import *
97
from git import *
108
from git.util import join_path_native
9+
from git.exc import BadObject
10+
from gitdb.util import hex_to_bin
11+
12+
import os, sys
1113
import tempfile
1214
import shutil
1315
from cStringIO import StringIO
14-
from git.exc import BadObject
16+
1517

1618
class TestRepo(TestBase):
1719

1820
@raises(InvalidGitRepositoryError)
19-
def _test_new_should_raise_on_invalid_repo_location(self):
21+
def test_new_should_raise_on_invalid_repo_location(self):
2022
Repo(tempfile.gettempdir())
2123

2224
@raises(NoSuchPathError)
23-
def _test_new_should_raise_on_non_existant_path(self):
25+
def test_new_should_raise_on_non_existant_path(self):
2426
Repo("repos/foobar")
2527

26-
def _test_repo_creation_from_different_paths(self):
28+
def test_repo_creation_from_different_paths(self):
2729
r_from_gitdir = Repo(self.rorepo.git_dir)
2830
assert r_from_gitdir.git_dir == self.rorepo.git_dir
2931
assert r_from_gitdir.git_dir.endswith('.git')
3032
assert not self.rorepo.git.working_dir.endswith('.git')
3133
assert r_from_gitdir.git.working_dir == self.rorepo.git.working_dir
3234

33-
def _test_description(self):
35+
def test_description(self):
3436
txt = "Test repository"
3537
self.rorepo.description = txt
3638
assert_equal(self.rorepo.description, txt)
3739

38-
def _test_heads_should_return_array_of_head_objects(self):
40+
def test_heads_should_return_array_of_head_objects(self):
3941
for head in self.rorepo.heads:
4042
assert_equal(Head, head.__class__)
4143

42-
def _test_heads_should_populate_head_data(self):
44+
def test_heads_should_populate_head_data(self):
4345
for head in self.rorepo.heads:
4446
assert head.name
4547
assert isinstance(head.commit,Commit)
@@ -48,7 +50,7 @@ def _test_heads_should_populate_head_data(self):
4850
assert isinstance(self.rorepo.heads.master, Head)
4951
assert isinstance(self.rorepo.heads['master'], Head)
5052

51-
def _test_tree_from_revision(self):
53+
def test_tree_from_revision(self):
5254
tree = self.rorepo.tree('0.1.6')
5355
assert len(tree.hexsha) == 40
5456
assert tree.type == "tree"
@@ -57,7 +59,7 @@ def _test_tree_from_revision(self):
5759
# try from invalid revision that does not exist
5860
self.failUnlessRaises(ValueError, self.rorepo.tree, 'hello world')
5961

60-
def _test_commits(self):
62+
def test_commits(self):
6163
mc = 10
6264
commits = list(self.rorepo.iter_commits('0.1.6', max_count=mc))
6365
assert len(commits) == mc
@@ -79,7 +81,7 @@ def _test_commits(self):
7981
c = commits[1]
8082
assert isinstance(c.parents, tuple)
8183

82-
def _test_trees(self):
84+
def test_trees(self):
8385
mc = 30
8486
num_trees = 0
8587
for tree in self.rorepo.iter_trees('0.1.5', max_count=mc):
@@ -89,7 +91,7 @@ def _test_trees(self):
8991
assert num_trees == mc
9092

9193

92-
def _test_empty_repo(self, repo):
94+
def _assert_empty_repo(self, repo):
9395
# test all kinds of things with an empty, freshly initialized repo.
9496
# It should throw good errors
9597

@@ -117,7 +119,7 @@ def _test_empty_repo(self, repo):
117119
# END test repos with working tree
118120

119121

120-
def _test_init(self):
122+
def test_init(self):
121123
prev_cwd = os.getcwd()
122124
os.chdir(tempfile.gettempdir())
123125
git_dir_rela = "repos/foo/bar.git"
@@ -131,12 +133,12 @@ def _test_init(self):
131133
assert r.bare == True
132134
assert os.path.isdir(r.git_dir)
133135

134-
self._test_empty_repo(r)
136+
self._assert_empty_repo(r)
135137

136138
# test clone
137139
clone_path = path + "_clone"
138140
rc = r.clone(clone_path)
139-
self._test_empty_repo(rc)
141+
self._assert_empty_repo(rc)
140142

141143
shutil.rmtree(git_dir_abs)
142144
try:
@@ -153,7 +155,7 @@ def _test_init(self):
153155
r = Repo.init(bare=False)
154156
r.bare == False
155157

156-
self._test_empty_repo(r)
158+
self._assert_empty_repo(r)
157159
finally:
158160
try:
159161
shutil.rmtree(del_dir_abs)
@@ -162,17 +164,17 @@ def _test_init(self):
162164
os.chdir(prev_cwd)
163165
# END restore previous state
164166

165-
def _test_bare_property(self):
167+
def test_bare_property(self):
166168
self.rorepo.bare
167169

168-
def _test_daemon_export(self):
170+
def test_daemon_export(self):
169171
orig_val = self.rorepo.daemon_export
170172
self.rorepo.daemon_export = not orig_val
171173
assert self.rorepo.daemon_export == ( not orig_val )
172174
self.rorepo.daemon_export = orig_val
173175
assert self.rorepo.daemon_export == orig_val
174176

175-
def _test_alternates(self):
177+
def test_alternates(self):
176178
cur_alternates = self.rorepo.alternates
177179
# empty alternates
178180
self.rorepo.alternates = []
@@ -182,15 +184,15 @@ def _test_alternates(self):
182184
assert alts == self.rorepo.alternates
183185
self.rorepo.alternates = cur_alternates
184186

185-
def _test_repr(self):
187+
def test_repr(self):
186188
path = os.path.join(os.path.abspath(GIT_REPO), '.git')
187189
assert_equal('<git.Repo "%s">' % path, repr(self.rorepo))
188190

189-
def _test_is_dirty_with_bare_repository(self):
191+
def test_is_dirty_with_bare_repository(self):
190192
self.rorepo._bare = True
191193
assert_false(self.rorepo.is_dirty())
192194

193-
def _test_is_dirty(self):
195+
def test_is_dirty(self):
194196
self.rorepo._bare = False
195197
for index in (0,1):
196198
for working_tree in (0,1):
@@ -202,23 +204,23 @@ def _test_is_dirty(self):
202204
self.rorepo._bare = True
203205
assert self.rorepo.is_dirty() == False
204206

205-
def _test_head(self):
207+
def test_head(self):
206208
assert self.rorepo.head.reference.object == self.rorepo.active_branch.object
207209

208-
def _test_index(self):
210+
def test_index(self):
209211
index = self.rorepo.index
210212
assert isinstance(index, IndexFile)
211213

212-
def _test_tag(self):
214+
def test_tag(self):
213215
assert self.rorepo.tag('refs/tags/0.1.5').commit
214216

215-
def _test_archive(self):
217+
def test_archive(self):
216218
tmpfile = os.tmpfile()
217219
self.rorepo.archive(tmpfile, '0.1.5')
218220
assert tmpfile.tell()
219221

220222
@patch_object(Git, '_call_process')
221-
def _test_should_display_blame_information(self, git):
223+
def test_should_display_blame_information(self, git):
222224
git.return_value = fixture('blame')
223225
b = self.rorepo.blame( 'master', 'lib/git.py')
224226
assert_equal(13, len(b))
@@ -244,7 +246,7 @@ def _test_should_display_blame_information(self, git):
244246
assert_true( isinstance( tlist[0], basestring ) )
245247
assert_true( len( tlist ) < sum( len(t) for t in tlist ) ) # test for single-char bug
246248

247-
def _test_untracked_files(self):
249+
def test_untracked_files(self):
248250
base = self.rorepo.working_tree_dir
249251
files = ( join_path_native(base, "__test_myfile"),
250252
join_path_native(base, "__test_other_file") )
@@ -270,13 +272,13 @@ def _test_untracked_files(self):
270272

271273
assert len(self.rorepo.untracked_files) == (num_recently_untracked - len(files))
272274

273-
def _test_config_reader(self):
275+
def test_config_reader(self):
274276
reader = self.rorepo.config_reader() # all config files
275277
assert reader.read_only
276278
reader = self.rorepo.config_reader("repository") # single config file
277279
assert reader.read_only
278280

279-
def _test_config_writer(self):
281+
def test_config_writer(self):
280282
for config_level in self.rorepo.config_level:
281283
try:
282284
writer = self.rorepo.config_writer(config_level)
@@ -287,7 +289,7 @@ def _test_config_writer(self):
287289
pass
288290
# END for each config level
289291

290-
def _test_creation_deletion(self):
292+
def test_creation_deletion(self):
291293
# just a very quick test to assure it generally works. There are
292294
# specialized cases in the test_refs module
293295
head = self.rorepo.create_head("new_head", "HEAD~1")
@@ -299,12 +301,12 @@ def _test_creation_deletion(self):
299301
remote = self.rorepo.create_remote("new_remote", "git@server:repo.git")
300302
self.rorepo.delete_remote(remote)
301303

302-
def _test_comparison_and_hash(self):
304+
def test_comparison_and_hash(self):
303305
# this is only a preliminary test, more testing done in test_index
304306
assert self.rorepo == self.rorepo and not (self.rorepo != self.rorepo)
305307
assert len(set((self.rorepo, self.rorepo))) == 1
306308

307-
def _test_git_cmd(self):
309+
def test_git_cmd(self):
308310
# test CatFileContentStream, just to be very sure we have no fencepost errors
309311
# last \n is the terminating newline that it expects
310312
l1 = "0123456789\n"
@@ -442,6 +444,9 @@ def _assert_rev_parse(self, name):
442444
def test_rev_parse(self):
443445
rev_parse = self.rorepo.rev_parse
444446

447+
# try special case: This one failed beforehand
448+
assert self.rorepo.odb.partial_to_complete_sha_hex("33ebe") == hex_to_bin("33ebe7acec14b25c5f84f35a664803fcab2f7781")
449+
445450
# start from reference
446451
num_resolved = 0
447452
for ref in Reference.iter_items(self.rorepo):

0 commit comments

Comments
 (0)