| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 4b4a514 commit 1e2b461
15 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,6 +5,7 @@ CHANGES | |||
| 5 | 5 | 0.2 Beta 2 | |
| 6 | 6 | =========== | |
| 7 | 7 | * Commit objects now carry the 'encoding' information of their message. It wasn't parsed previously, and defaults to UTF-8 | |
| 8 | + * Commit.create_from_tree now uses a pure-python implementation, mimicing git-commit-tree | ||
| 8 | 9 | ||
| 9 | 10 | 0.2 | |
| 10 | 11 | ===== | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -323,12 +323,7 @@ def execute(self, command, | |||
| 323 | 323 | stdout_value = proc.stdout.read().rstrip() # strip trailing "\n" | |
| 324 | 324 | else: | |
| 325 | 325 | max_chunk_size = 1024*64 | |
| 326 | - while True: | ||
| 327 | - chunk = proc.stdout.read(max_chunk_size) | ||
| 328 | - output_stream.write(chunk) | ||
| 329 | - if len(chunk) < max_chunk_size: | ||
| 330 | - break | ||
| 331 | - # END reading output stream | ||
| 326 | + stream_copy(proc.stdout, output_stream, max_chunk_size) | ||
| 332 | 327 | stdout_value = output_stream | |
| 333 | 328 | # END stdout handling | |
| 334 | 329 | stderr_value = proc.stderr.read().rstrip() # strip trailing "\n" | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -125,8 +125,8 @@ def data_stream(self): | |||
| 125 | 125 | Returns | |
| 126 | 126 | File Object compatible stream to the uncompressed raw data of the object | |
| 127 | 127 | """ | |
| 128 | - sha, type, size, stream = self.repo.git.stream_object_data(self.sha) | ||
| 129 | - return stream | ||
| 128 | + proc = self.repo.git.cat_file(self.type, self.sha, as_process=True) | ||
| 129 | + return utils.ProcessStreamAdapter(proc, "stdout") | ||
| 130 | 130 | ||
| 131 | 131 | def stream_data(self, ostream): | |
| 132 | 132 | """ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -91,15 +91,6 @@ def __init__(self, repo, sha, tree=None, author=None, authored_date=None, author | |||
| 91 | 91 | """ | |
| 92 | 92 | super(Commit,self).__init__(repo, sha) | |
| 93 | 93 | self._set_self_from_args_(locals()) | |
| 94 | - | ||
| 95 | - if parents is not None: | ||
| 96 | - cls = type(self) | ||
| 97 | - self.parents = tuple(cls(repo, p) for p in parents if not isinstance(p, cls)) | ||
| 98 | - # END for each parent to convert | ||
| 99 | - | ||
| 100 | - if self.sha and tree is not None: | ||
| 101 | - self.tree = Tree(repo, tree, path='') | ||
| 102 | - # END id to tree conversion | ||
| 103 | 94 | ||
| 104 | 95 | @classmethod | |
| 105 | 96 | def _get_intermediate_items(cls, commit): | |
@@ -350,7 +341,12 @@ def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False): | |||
| 350 | 341 | committer, committer_time, committer_offset, | |
| 351 | 342 | message, parent_commits, conf_encoding) | |
| 352 | 343 | ||
| 353 | - # serialize ! | ||
| 344 | + stream = StringIO() | ||
| 345 | + new_commit._serialize(stream) | ||
| 346 | + streamlen = stream.tell() | ||
| 347 | + stream.seek(0) | ||
| 348 | + | ||
| 349 | + new_commit.sha = repo.odb.to_object(cls.type, streamlen, stream, sha_as_hex=True) | ||
| 354 | 350 | ||
| 355 | 351 | if head: | |
| 356 | 352 | try: | |
@@ -377,8 +373,28 @@ def __repr__(self): | |||
| 377 | 373 | #{ Serializable Implementation | |
| 378 | 374 | ||
| 379 | 375 | def _serialize(self, stream): | |
| 380 | - # for now, this is very inefficient and in fact shouldn't be used like this | ||
| 381 | - return super(Commit, self)._serialize(stream) | ||
| 376 | + write = stream.write | ||
| 377 | + write("tree %s\n" % self.tree) | ||
| 378 | + for p in self.parents: | ||
| 379 | + write("parent %s\n" % p) | ||
| 380 | + | ||
| 381 | + a = self.author | ||
| 382 | + c = self.committer | ||
| 383 | + fmt = "%s %s <%s> %s %s\n" | ||
| 384 | + write(fmt % ("author", a.name, a.email, | ||
| 385 | + self.authored_date, | ||
| 386 | + utils.altz_to_utctz_str(self.author_tz_offset))) | ||
| 387 | + | ||
| 388 | + write(fmt % ("committer", c.name, c.email, | ||
| 389 | + self.committed_date, | ||
| 390 | + utils.altz_to_utctz_str(self.committer_tz_offset))) | ||
| 391 | + | ||
| 392 | + if self.encoding != self.default_encoding: | ||
| 393 | + write("encoding %s\n" % self.encoding) | ||
| 394 | + | ||
| 395 | + write("\n") | ||
| 396 | + write(self.message) | ||
| 397 | + return self | ||
| 382 | 398 | ||
| 383 | 399 | def _deserialize(self, stream): | |
| 384 | 400 | """:param from_rev_list: if true, the stream format is coming from the rev-list command | |
@@ -416,7 +432,7 @@ def _deserialize(self, stream): | |||
| 416 | 432 | ||
| 417 | 433 | # a stream from our data simply gives us the plain message | |
| 418 | 434 | # The end of our message stream is marked with a newline that we strip | |
| 419 | - self.message = stream.read()[:-1] | ||
| 435 | + self.message = stream.read() | ||
| 420 | 436 | return self | |
| 421 | 437 | ||
| 422 | 438 | #} END serializable implementation | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -16,7 +16,8 @@ | |||
| 16 | 16 | import os | |
| 17 | 17 | ||
| 18 | 18 | __all__ = ('get_object_type_by_name', 'get_user_id', 'parse_date', 'parse_actor_and_date', | |
| 19 | - 'ProcessStreamAdapter', 'Traversable') | ||
| 19 | + 'ProcessStreamAdapter', 'Traversable', 'altz_to_utctz_str', 'utctz_to_altz', | ||
| 20 | + 'verify_utctz') | ||
| 20 | 21 | ||
| 21 | 22 | def get_object_type_by_name(object_type_name): | |
| 22 | 23 | """ | |
@@ -57,14 +58,24 @@ def get_user_id(): | |||
| 57 | 58 | return "%s@%s" % (username, platform.node()) | |
| 58 | 59 | ||
| 59 | 60 | ||
| 60 | - def _utc_tz_to_altz(utctz): | ||
| 61 | + def utctz_to_altz(utctz): | ||
| 61 | 62 | """we convert utctz to the timezone in seconds, it is the format time.altzone | |
| 62 | 63 | returns. Git stores it as UTC timezon which has the opposite sign as well, | |
| 63 | 64 | which explains the -1 * ( that was made explicit here ) | |
| 64 | 65 | :param utctz: git utc timezone string, i.e. +0200""" | |
| 65 | 66 | return -1 * int(float(utctz)/100*3600) | |
| 67 | + | ||
| 68 | + def altz_to_utctz_str(altz): | ||
| 69 | + """As above, but inverses the operation, returning a string that can be used | ||
| 70 | + in commit objects""" | ||
| 71 | + utci = -1 * int((altz / 3600)*100) | ||
| 72 | + utcs = str(abs(utci)) | ||
| 73 | + utcs = "0"*(4-len(utcs)) + utcs | ||
| 74 | + prefix = (utci < 0 and '-') or '+' | ||
| 75 | + return prefix + utcs | ||
| 76 | + | ||
| 66 | 77 | ||
| 67 | - def _verify_utctz(offset): | ||
| 78 | + def verify_utctz(offset): | ||
| 68 | 79 | """:raise ValueError: if offset is incorrect | |
| 69 | 80 | :return: offset""" | |
| 70 | 81 | fmt_exc = ValueError("Invalid timezone offset format: %s" % offset) | |
@@ -97,11 +108,11 @@ def parse_date(string_date): | |||
| 97 | 108 | if string_date.count(' ') == 1 and string_date.rfind(':') == -1: | |
| 98 | 109 | timestamp, offset = string_date.split() | |
| 99 | 110 | timestamp = int(timestamp) | |
| 100 | - return timestamp, _utc_tz_to_altz(_verify_utctz(offset)) | ||
| 111 | + return timestamp, utctz_to_altz(verify_utctz(offset)) | ||
| 101 | 112 | else: | |
| 102 | 113 | offset = "+0000" # local time by default | |
| 103 | 114 | if string_date[-5] in '-+': | |
| 104 | - offset = _verify_utctz(string_date[-5:]) | ||
| 115 | + offset = verify_utctz(string_date[-5:]) | ||
| 105 | 116 | string_date = string_date[:-6] # skip space as well | |
| 106 | 117 | # END split timezone info | |
| 107 | 118 | ||
@@ -139,7 +150,7 @@ def parse_date(string_date): | |||
| 139 | 150 | fstruct = time.struct_time((dtstruct.tm_year, dtstruct.tm_mon, dtstruct.tm_mday, | |
| 140 | 151 | tstruct.tm_hour, tstruct.tm_min, tstruct.tm_sec, | |
| 141 | 152 | dtstruct.tm_wday, dtstruct.tm_yday, tstruct.tm_isdst)) | |
| 142 | - return int(time.mktime(fstruct)), _utc_tz_to_altz(offset) | ||
| 153 | + return int(time.mktime(fstruct)), utctz_to_altz(offset) | ||
| 143 | 154 | except ValueError: | |
| 144 | 155 | continue | |
| 145 | 156 | # END exception handling | |
@@ -167,7 +178,7 @@ def parse_actor_and_date(line): | |||
| 167 | 178 | """ | |
| 168 | 179 | m = _re_actor_epoch.search(line) | |
| 169 | 180 | actor, epoch, offset = m.groups() | |
| 170 | - return (Actor._from_string(actor), int(epoch), _utc_tz_to_altz(offset)) | ||
| 181 | + return (Actor._from_string(actor), int(epoch), utctz_to_altz(offset)) | ||
| 171 | 182 | ||
| 172 | 183 | ||
| 173 | 184 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -137,7 +137,7 @@ def initialize(self, size=0): | |||
| 137 | 137 | # END handle size | |
| 138 | 138 | ||
| 139 | 139 | # read header | |
| 140 | - maxb = 8192 | ||
| 140 | + maxb = 512 # should really be enough, cgit uses 8192 I believe | ||
| 141 | 141 | self._s = maxb | |
| 142 | 142 | hdr = self.read(maxb) | |
| 143 | 143 | hdrend = hdr.find("\0") | |
@@ -172,20 +172,24 @@ def read(self, size=-1): | |||
| 172 | 172 | # Our performance now depends on StringIO. This way we don't need two large | |
| 173 | 173 | # buffers in peak times, but only one large one in the end which is | |
| 174 | 174 | # the return buffer | |
| 175 | - if size > self.max_read_size: | ||
| 176 | - sio = StringIO() | ||
| 177 | - while size: | ||
| 178 | - read_size = min(self.max_read_size, size) | ||
| 179 | - data = self.read(read_size) | ||
| 180 | - sio.write(data) | ||
| 181 | - size -= len(data) | ||
| 182 | - if len(data) < read_size: | ||
| 183 | - break | ||
| 184 | - # END data loop | ||
| 185 | - sio.seek(0) | ||
| 186 | - return sio.getvalue() | ||
| 187 | - # END handle maxread | ||
| 175 | + # NO: We don't do it - if the user thinks its best, he is right. If he | ||
| 176 | + # has trouble, he will start reading in chunks. According to our tests | ||
| 177 | + # its still faster if we read 10 Mb at once instead of chunking it. | ||
| 188 | 178 | ||
| 179 | + # if size > self.max_read_size: | ||
| 180 | + # sio = StringIO() | ||
| 181 | + # while size: | ||
| 182 | + # read_size = min(self.max_read_size, size) | ||
| 183 | + # data = self.read(read_size) | ||
| 184 | + # sio.write(data) | ||
| 185 | + # size -= len(data) | ||
| 186 | + # if len(data) < read_size: | ||
| 187 | + # break | ||
| 188 | + # # END data loop | ||
| 189 | + # sio.seek(0) | ||
| 190 | + # return sio.getvalue() | ||
| 191 | + # # END handle maxread | ||
| 192 | + # | ||
| 189 | 193 | # deplete the buffer, then just continue using the decompress object | |
| 190 | 194 | # which has an own buffer. We just need this to transparently parse the | |
| 191 | 195 | # header from the zlib stream | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,12 +4,6 @@ | |||
| 4 | 4 | # This module is part of GitPython and is released under | |
| 5 | 5 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
| 6 | 6 | ||
| 7 | - import os | ||
| 8 | - import sys | ||
| 9 | - import re | ||
| 10 | - import gzip | ||
| 11 | - import StringIO | ||
| 12 | - | ||
| 13 | 7 | from errors import InvalidGitRepositoryError, NoSuchPathError | |
| 14 | 8 | from cmd import Git | |
| 15 | 9 | from actor import Actor | |
@@ -19,6 +13,15 @@ | |||
| 19 | 13 | from config import GitConfigParser | |
| 20 | 14 | from remote import Remote | |
| 21 | 15 | ||
| 16 | + from odb.db import LooseObjectDB | ||
| 17 | + | ||
| 18 | + import os | ||
| 19 | + import sys | ||
| 20 | + import re | ||
| 21 | + import gzip | ||
| 22 | + import StringIO | ||
| 23 | + | ||
| 24 | + | ||
| 22 | 25 | def touch(filename): | |
| 23 | 26 | fp = open(filename, "a") | |
| 24 | 27 | fp.close() | |
@@ -53,7 +56,7 @@ class Repo(object): | |||
| 53 | 56 | 'git_dir' is the .git repository directoy, which is always set. | |
| 54 | 57 | """ | |
| 55 | 58 | DAEMON_EXPORT_FILE = 'git-daemon-export-ok' | |
| 56 | - __slots__ = ( "working_dir", "_working_tree_dir", "git_dir", "_bare", "git" ) | ||
| 59 | + __slots__ = ( "working_dir", "_working_tree_dir", "git_dir", "_bare", "git", "odb" ) | ||
| 57 | 60 | ||
| 58 | 61 | # precompiled regex | |
| 59 | 62 | re_whitespace = re.compile(r'\s+') | |
@@ -65,27 +68,22 @@ class Repo(object): | |||
| 65 | 68 | # represents the configuration level of a configuration file | |
| 66 | 69 | config_level = ("system", "global", "repository") | |
| 67 | 70 | ||
| 68 | - def __init__(self, path=None): | ||
| 69 | - """ | ||
| 70 | - Create a new Repo instance | ||
| 71 | - | ||
| 72 | - ``path`` | ||
| 73 | - is the path to either the root git directory or the bare git repo | ||
| 71 | + def __init__(self, path=None, odbt = LooseObjectDB): | ||
| 72 | + """ Create a new Repo instance | ||
| 74 | 73 | ||
| 75 | - Examples:: | ||
| 74 | + :param path: is the path to either the root git directory or the bare git repo:: | ||
| 76 | 75 | ||
| 77 | 76 | repo = Repo("/Users/mtrier/Development/git-python") | |
| 78 | 77 | repo = Repo("/Users/mtrier/Development/git-python.git") | |
| 79 | 78 | repo = Repo("~/Development/git-python.git") | |
| 80 | 79 | repo = Repo("$REPOSITORIES/Development/git-python.git") | |
| 81 | - | ||
| 82 | - Raises | ||
| 83 | - InvalidGitRepositoryError or NoSuchPathError | ||
| 84 | - | ||
| 85 | - Returns | ||
| 86 | - ``git.Repo`` | ||
| 87 | - """ | ||
| 88 | - | ||
| 80 | + | ||
| 81 | + :param odbt: Object DataBase type - a type which is constructed by providing | ||
| 82 | + the directory containing the database objects, i.e. .git/objects. It will | ||
| 83 | + be used to access all object data | ||
| 84 | + :raise InvalidGitRepositoryError: | ||
| 85 | + :raise NoSuchPathError: | ||
| 86 | + :return: git.Repo """ | ||
| 89 | 87 | epath = os.path.abspath(os.path.expandvars(os.path.expanduser(path or os.getcwd()))) | |
| 90 | 88 | ||
| 91 | 89 | if not os.path.exists(epath): | |
@@ -130,6 +128,7 @@ def __init__(self, path=None): | |||
| 130 | 128 | ||
| 131 | 129 | self.working_dir = self._working_tree_dir or self.git_dir | |
| 132 | 130 | self.git = Git(self.working_dir) | |
| 131 | + self.odb = odbt(os.path.join(self.git_dir, 'objects')) | ||
| 133 | 132 | ||
| 134 | 133 | def __eq__(self, rhs): | |
| 135 | 134 | if isinstance(rhs, Repo): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -27,6 +27,21 @@ def make_sha(source=''): | |||
| 27 | 27 | sha1 = sha.sha(source) | |
| 28 | 28 | return sha1 | |
| 29 | 29 | ||
| 30 | + def stream_copy(source, destination, chunk_size=512*1024): | ||
| 31 | + """Copy all data from the source stream into the destination stream in chunks | ||
| 32 | + of size chunk_size | ||
| 33 | + :return: amount of bytes written""" | ||
| 34 | + br = 0 | ||
| 35 | + while True: | ||
| 36 | + chunk = source.read(chunk_size) | ||
| 37 | + destination.write(chunk) | ||
| 38 | + br += len(chunk) | ||
| 39 | + if len(chunk) < chunk_size: | ||
| 40 | + break | ||
| 41 | + # END reading output stream | ||
| 42 | + return br | ||
| 43 | + | ||
| 44 | + | ||
| 30 | 45 | def join_path(a, *p): | |
| 31 | 46 | """Join path tokens together similar to os.path.join, but always use | |
| 32 | 47 | '/' instead of possibly '\' on windows.""" | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,8 @@ | |||
| 1 | 1 | """Contains library functions""" | |
| 2 | 2 | import os | |
| 3 | 3 | from test.testlib import * | |
| 4 | + import shutil | ||
| 5 | + import tempfile | ||
| 4 | 6 | ||
| 5 | 7 | from git import ( | |
| 6 | 8 | Repo | |
@@ -25,7 +27,7 @@ def resolve_or_fail(env_var): | |||
| 25 | 27 | ||
| 26 | 28 | #{ Base Classes | |
| 27 | 29 | ||
| 28 | - class TestBigRepoReadOnly(TestBase): | ||
| 30 | + class TestBigRepoR(TestBase): | ||
| 29 | 31 | """TestCase providing access to readonly 'big' repositories using the following | |
| 30 | 32 | member variables: | |
| 31 | 33 | ||
@@ -40,7 +42,24 @@ class TestBigRepoReadOnly(TestBase): | |||
| 40 | 42 | ||
| 41 | 43 | @classmethod | |
| 42 | 44 | def setUpAll(cls): | |
| 43 | - super(TestBigRepoReadOnly, cls).setUpAll() | ||
| 44 | - cls.gitrepo = Repo(resolve_or_fail(k_env_git_repo)) | ||
| 45 | + super(TestBigRepoR, cls).setUpAll() | ||
| 46 | + cls.gitrorepo = Repo(resolve_or_fail(k_env_git_repo)) | ||
| 45 | 47 | ||
| 48 | + | ||
| 49 | + class TestBigRepoRW(TestBigRepoR): | ||
| 50 | + """As above, but provides a big repository that we can write to. | ||
| 51 | + | ||
| 52 | + Provides ``self.gitrwrepo``""" | ||
| 53 | + | ||
| 54 | + @classmethod | ||
| 55 | + def setUpAll(cls): | ||
| 56 | + super(TestBigRepoRW, cls).setUpAll() | ||
| 57 | + dirname = tempfile.mktemp() | ||
| 58 | + os.mkdir(dirname) | ||
| 59 | + cls.gitrwrepo = cls.gitrorepo.clone(dirname, shared=True, bare=True) | ||
| 60 | + | ||
| 61 | + @classmethod | ||
| 62 | + def tearDownAll(cls): | ||
| 63 | + shutil.rmtree(cls.gitrwrepo.working_tree_dir) | ||
| 64 | + | ||
| 46 | 65 | #} END base classes | |
| Back | FazBrowse Home | New Git URL |
0 commit comments