New upstream version 3.0.3
TANIGUCHI Takaki
4 years ago
0 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: GitPython |
2 | Version: 3.0.1 | |
2 | Version: 3.0.3 | |
3 | 3 | Summary: Python Git Library |
4 | 4 | Home-page: https://github.com/gitpython-developers/GitPython |
5 | 5 | Author: Sebastian Thiel, Michael Trier |
0 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: GitPython |
2 | Version: 3.0.1 | |
2 | Version: 3.0.3 | |
3 | 3 | Summary: Python Git Library |
4 | 4 | Home-page: https://github.com/gitpython-developers/GitPython |
5 | 5 | Author: Sebastian Thiel, Michael Trier |
0 | 0 | ========= |
1 | 1 | Changelog |
2 | 2 | ========= |
3 | ||
4 | 3.0.3 - Bugfixes | |
5 | ============================================= | |
6 | ||
7 | see the following for (most) details: | |
8 | https://github.com/gitpython-developers/gitpython/milestone/30?closed=1 | |
9 | ||
10 | 3.0.2 - Bugfixes | |
11 | ============================================= | |
12 | ||
13 | * fixes an issue with installation | |
3 | 14 | |
4 | 15 | 3.0.1 - Bugfixes and performance improvements |
5 | 16 | ============================================= |
11 | 11 | import os.path as osp |
12 | 12 | |
13 | 13 | |
14 | __version__ = '3.0.1' | |
14 | __version__ = '3.0.3' | |
15 | 15 | |
16 | 16 | |
17 | 17 | #{ Initialization |
18 | 18 | def _init_externals(): |
19 | 19 | """Initialize external projects by putting them into the path""" |
20 | if __version__ == '3.0.1': | |
20 | if __version__ == '3.0.3': | |
21 | 21 | sys.path.insert(0, osp.join(osp.dirname(__file__), 'ext', 'gitdb')) |
22 | 22 | |
23 | 23 | try: |
483 | 483 | buf = enc.strip() |
484 | 484 | while buf: |
485 | 485 | if buf[0:10] == b"encoding ": |
486 | self.encoding = buf[buf.find(' ') + 1:].decode('ascii') | |
486 | self.encoding = buf[buf.find(' ') + 1:].decode( | |
487 | self.encoding, 'ignore') | |
487 | 488 | elif buf[0:7] == b"gpgsig ": |
488 | 489 | sig = buf[buf.find(b' ') + 1:] + b"\n" |
489 | 490 | is_next_header = False |
497 | 498 | break |
498 | 499 | sig += sigbuf[1:] |
499 | 500 | # end read all signature |
500 | self.gpgsig = sig.rstrip(b"\n").decode('ascii') | |
501 | self.gpgsig = sig.rstrip(b"\n").decode(self.encoding, 'ignore') | |
501 | 502 | if is_next_header: |
502 | 503 | continue |
503 | 504 | buf = readline().strip() |
863 | 863 | rmtree(wtd) |
864 | 864 | except Exception as ex: |
865 | 865 | if HIDE_WINDOWS_KNOWN_ERRORS: |
866 | raise SkipTest("FIXME: fails with: PermissionError\n %s", ex) | |
867 | else: | |
868 | raise | |
866 | raise SkipTest("FIXME: fails with: PermissionError\n {}".format(ex)) | |
867 | raise | |
869 | 868 | # END delete tree if possible |
870 | 869 | # END handle force |
871 | 870 |
155 | 155 | if flags & cls.DELETED: |
156 | 156 | from_ref = None |
157 | 157 | else: |
158 | from_ref = Reference.from_path(remote.repo, from_ref_string) | |
158 | if from_ref_string == "(delete)": | |
159 | from_ref = None | |
160 | else: | |
161 | from_ref = Reference.from_path(remote.repo, from_ref_string) | |
159 | 162 | |
160 | 163 | # commit handling, could be message or commit info |
161 | 164 | old_commit = None |
447 | 450 | return '<git.%s "%s">' % (self.__class__.__name__, self.name) |
448 | 451 | |
449 | 452 | def __eq__(self, other): |
450 | return self.name == other.name | |
453 | return isinstance(other, type(self)) and self.name == other.name | |
451 | 454 | |
452 | 455 | def __ne__(self, other): |
453 | 456 | return not (self == other) |
1011 | 1011 | :param to_path: Path to which the repository should be cloned to |
1012 | 1012 | :param progress: See 'git.remote.Remote.push'. |
1013 | 1013 | :param env: Optional dictionary containing the desired environment variables. |
1014 | Note: Provided variables will be used to update the execution | |
1015 | environment for `git`. If some variable is not specified in `env` | |
1016 | and is defined in `os.environ`, value from `os.environ` will be used. | |
1017 | If you want to unset some variable, consider providing empty string | |
1018 | as its value. | |
1014 | 1019 | :param multi_options: See ``clone`` method |
1015 | 1020 | :param kwargs: see the ``clone`` method |
1016 | 1021 | :return: Repo instance pointing to the cloned directory""" |
37 | 37 | def tearDown(self): |
38 | 38 | for lfp in glob.glob(_tc_lock_fpaths): |
39 | 39 | if osp.isfile(lfp): |
40 | raise AssertionError('Previous TC left hanging git-lock file: %s', lfp) | |
40 | raise AssertionError('Previous TC left hanging git-lock file: {}'.format(lfp)) | |
41 | 41 | |
42 | 42 | def _to_memcache(self, file_path): |
43 | 43 | with open(file_path, "rb") as fp: |
133 | 133 | |
134 | 134 | def test_persistent_cat_file_command(self): |
135 | 135 | # read header only |
136 | import subprocess as sp | |
137 | 136 | hexsha = "b2339455342180c7cc1e9bba3e9f181f7baa5167" |
138 | g = self.git.cat_file(batch_check=True, istream=sp.PIPE, as_process=True) | |
137 | g = self.git.cat_file( | |
138 | batch_check=True, istream=subprocess.PIPE, as_process=True | |
139 | ) | |
139 | 140 | g.stdin.write(b"b2339455342180c7cc1e9bba3e9f181f7baa5167\n") |
140 | 141 | g.stdin.flush() |
141 | 142 | obj_info = g.stdout.readline() |
142 | 143 | |
143 | 144 | # read header + data |
144 | g = self.git.cat_file(batch=True, istream=sp.PIPE, as_process=True) | |
145 | g = self.git.cat_file( | |
146 | batch=True, istream=subprocess.PIPE, as_process=True | |
147 | ) | |
145 | 148 | g.stdin.write(b"b2339455342180c7cc1e9bba3e9f181f7baa5167\n") |
146 | 149 | g.stdin.flush() |
147 | 150 | obj_info_two = g.stdout.readline() |
159 | 162 | |
160 | 163 | # same can be achieved using the respective command functions |
161 | 164 | hexsha, typename, size = self.git.get_object_header(hexsha) |
162 | hexsha, typename_two, size_two, data = self.git.get_object_data(hexsha) # @UnusedVariable | |
165 | hexsha, typename_two, size_two, _ = self.git.get_object_data(hexsha) | |
163 | 166 | self.assertEqual(typename, typename_two) |
164 | 167 | self.assertEqual(size, size_two) |
165 | 168 | |
263 | 266 | remote.fetch() |
264 | 267 | except GitCommandError as err: |
265 | 268 | if sys.version_info[0] < 3 and is_darwin: |
266 | self.assertIn('ssh-orig, ' in str(err)) | |
269 | self.assertIn('ssh-orig', str(err)) | |
267 | 270 | self.assertEqual(err.status, 128) |
268 | 271 | else: |
269 | 272 | self.assertIn('FOO', str(err)) |
896 | 896 | _make_hook( |
897 | 897 | index.repo.git_dir, |
898 | 898 | 'commit-msg', |
899 | 'echo -n " {}" >> "$1"'.format(from_hook_message) | |
899 | 'printf " {}" >> "$1"'.format(from_hook_message) | |
900 | 900 | ) |
901 | 901 | new_commit = index.commit(commit_message) |
902 | 902 | self.assertEqual(new_commit.message, u"{} {}".format(commit_message, from_hook_message)) |
52 | 52 | # Keep it for debugging |
53 | 53 | self._seen_lines.append(line) |
54 | 54 | rval = super(TestRemoteProgress, self)._parse_progress_line(line) |
55 | assert len(line) > 1, "line %r too short" % line | |
56 | 55 | return rval |
57 | 56 | |
58 | 57 | def line_dropped(self, line): |
385 | 384 | progress.make_assertion() |
386 | 385 | self._do_test_push_result(res, remote) |
387 | 386 | |
387 | # rejected stale delete | |
388 | force_with_lease = "%s:0000000000000000000000000000000000000000" % new_head.path | |
389 | res = remote.push(":%s" % new_head.path, force_with_lease=force_with_lease) | |
390 | self.assertTrue(res[0].flags & PushInfo.ERROR) | |
391 | self.assertTrue(res[0].flags & PushInfo.REJECTED) | |
392 | self.assertIsNone(res[0].local_ref) | |
393 | self._do_test_push_result(res, remote) | |
394 | ||
388 | 395 | # delete new branch on the remote end and locally |
389 | 396 | res = remote.push(":%s" % new_head.path) |
390 | 397 | self._do_test_push_result(res, remote) |
89 | 89 | def tearDown(self): |
90 | 90 | for lfp in glob.glob(_tc_lock_fpaths): |
91 | 91 | if osp.isfile(lfp): |
92 | raise AssertionError('Previous TC left hanging git-lock file: %s', lfp) | |
92 | raise AssertionError('Previous TC left hanging git-lock file: {}'.format(lfp)) | |
93 | 93 | import gc |
94 | 94 | gc.collect() |
95 | 95 |
97 | 97 | func(path) # Will scream if still not possible to delete. |
98 | 98 | except Exception as ex: |
99 | 99 | if HIDE_WINDOWS_KNOWN_ERRORS: |
100 | raise SkipTest("FIXME: fails with: PermissionError\n %s", ex) | |
101 | else: | |
102 | raise | |
100 | raise SkipTest("FIXME: fails with: PermissionError\n {}".format(ex)) | |
101 | raise | |
103 | 102 | |
104 | 103 | return shutil.rmtree(path, False, onerror) |
105 | 104 | |
379 | 378 | |
380 | 379 | - Lines that do not contain progress info are stored in :attr:`other_lines`. |
381 | 380 | - Lines that seem to contain an error (i.e. start with error: or fatal:) are stored |
382 | in :attr:`error_lines`. | |
383 | ||
384 | :return: list(line, ...) list of lines that could not be processed""" | |
381 | in :attr:`error_lines`.""" | |
385 | 382 | # handle |
386 | 383 | # Counting objects: 4, done. |
387 | # Compressing objects: 50% (1/2) \rCompressing objects: 100% (2/2) \rCompressing objects: 100% (2/2), done. | |
384 | # Compressing objects: 50% (1/2) | |
385 | # Compressing objects: 100% (2/2) | |
386 | # Compressing objects: 100% (2/2), done. | |
388 | 387 | self._cur_line = line = line.decode('utf-8') if isinstance(line, bytes) else line |
389 | 388 | if len(self.error_lines) > 0 or self._cur_line.startswith(('error:', 'fatal:')): |
390 | 389 | self.error_lines.append(self._cur_line) |
391 | return [] | |
392 | ||
393 | sub_lines = line.split('\r') | |
394 | failed_lines = [] | |
395 | for sline in sub_lines: | |
396 | # find escape characters and cut them away - regex will not work with | |
397 | # them as they are non-ascii. As git might expect a tty, it will send them | |
398 | last_valid_index = None | |
399 | for i, c in enumerate(reversed(sline)): | |
400 | if ord(c) < 32: | |
401 | # its a slice index | |
402 | last_valid_index = -i - 1 | |
403 | # END character was non-ascii | |
404 | # END for each character in sline | |
405 | if last_valid_index is not None: | |
406 | sline = sline[:last_valid_index] | |
407 | # END cut away invalid part | |
408 | sline = sline.rstrip() | |
409 | ||
410 | cur_count, max_count = None, None | |
411 | match = self.re_op_relative.match(sline) | |
412 | if match is None: | |
413 | match = self.re_op_absolute.match(sline) | |
414 | ||
415 | if not match: | |
416 | self.line_dropped(sline) | |
417 | failed_lines.append(sline) | |
418 | continue | |
419 | # END could not get match | |
420 | ||
421 | op_code = 0 | |
422 | remote, op_name, percent, cur_count, max_count, message = match.groups() # @UnusedVariable | |
423 | ||
424 | # get operation id | |
425 | if op_name == "Counting objects": | |
426 | op_code |= self.COUNTING | |
427 | elif op_name == "Compressing objects": | |
428 | op_code |= self.COMPRESSING | |
429 | elif op_name == "Writing objects": | |
430 | op_code |= self.WRITING | |
431 | elif op_name == 'Receiving objects': | |
432 | op_code |= self.RECEIVING | |
433 | elif op_name == 'Resolving deltas': | |
434 | op_code |= self.RESOLVING | |
435 | elif op_name == 'Finding sources': | |
436 | op_code |= self.FINDING_SOURCES | |
437 | elif op_name == 'Checking out files': | |
438 | op_code |= self.CHECKING_OUT | |
439 | else: | |
440 | # Note: On windows it can happen that partial lines are sent | |
441 | # Hence we get something like "CompreReceiving objects", which is | |
442 | # a blend of "Compressing objects" and "Receiving objects". | |
443 | # This can't really be prevented, so we drop the line verbosely | |
444 | # to make sure we get informed in case the process spits out new | |
445 | # commands at some point. | |
446 | self.line_dropped(sline) | |
447 | # Note: Don't add this line to the failed lines, as we have to silently | |
448 | # drop it | |
449 | self.other_lines.extend(failed_lines) | |
450 | return failed_lines | |
451 | # END handle op code | |
452 | ||
453 | # figure out stage | |
454 | if op_code not in self._seen_ops: | |
455 | self._seen_ops.append(op_code) | |
456 | op_code |= self.BEGIN | |
457 | # END begin opcode | |
458 | ||
459 | if message is None: | |
460 | message = '' | |
461 | # END message handling | |
462 | ||
463 | message = message.strip() | |
464 | if message.endswith(self.DONE_TOKEN): | |
465 | op_code |= self.END | |
466 | message = message[:-len(self.DONE_TOKEN)] | |
467 | # END end message handling | |
468 | message = message.strip(self.TOKEN_SEPARATOR) | |
469 | ||
470 | self.update(op_code, | |
471 | cur_count and float(cur_count), | |
472 | max_count and float(max_count), | |
473 | message) | |
474 | # END for each sub line | |
475 | self.other_lines.extend(failed_lines) | |
476 | return failed_lines | |
390 | return | |
391 | ||
392 | # find escape characters and cut them away - regex will not work with | |
393 | # them as they are non-ascii. As git might expect a tty, it will send them | |
394 | last_valid_index = None | |
395 | for i, c in enumerate(reversed(line)): | |
396 | if ord(c) < 32: | |
397 | # its a slice index | |
398 | last_valid_index = -i - 1 | |
399 | # END character was non-ascii | |
400 | # END for each character in line | |
401 | if last_valid_index is not None: | |
402 | line = line[:last_valid_index] | |
403 | # END cut away invalid part | |
404 | line = line.rstrip() | |
405 | ||
406 | cur_count, max_count = None, None | |
407 | match = self.re_op_relative.match(line) | |
408 | if match is None: | |
409 | match = self.re_op_absolute.match(line) | |
410 | ||
411 | if not match: | |
412 | self.line_dropped(line) | |
413 | self.other_lines.append(line) | |
414 | return | |
415 | # END could not get match | |
416 | ||
417 | op_code = 0 | |
418 | remote, op_name, percent, cur_count, max_count, message = match.groups() # @UnusedVariable | |
419 | ||
420 | # get operation id | |
421 | if op_name == "Counting objects": | |
422 | op_code |= self.COUNTING | |
423 | elif op_name == "Compressing objects": | |
424 | op_code |= self.COMPRESSING | |
425 | elif op_name == "Writing objects": | |
426 | op_code |= self.WRITING | |
427 | elif op_name == 'Receiving objects': | |
428 | op_code |= self.RECEIVING | |
429 | elif op_name == 'Resolving deltas': | |
430 | op_code |= self.RESOLVING | |
431 | elif op_name == 'Finding sources': | |
432 | op_code |= self.FINDING_SOURCES | |
433 | elif op_name == 'Checking out files': | |
434 | op_code |= self.CHECKING_OUT | |
435 | else: | |
436 | # Note: On windows it can happen that partial lines are sent | |
437 | # Hence we get something like "CompreReceiving objects", which is | |
438 | # a blend of "Compressing objects" and "Receiving objects". | |
439 | # This can't really be prevented, so we drop the line verbosely | |
440 | # to make sure we get informed in case the process spits out new | |
441 | # commands at some point. | |
442 | self.line_dropped(line) | |
443 | # Note: Don't add this line to the other lines, as we have to silently | |
444 | # drop it | |
445 | return | |
446 | # END handle op code | |
447 | ||
448 | # figure out stage | |
449 | if op_code not in self._seen_ops: | |
450 | self._seen_ops.append(op_code) | |
451 | op_code |= self.BEGIN | |
452 | # END begin opcode | |
453 | ||
454 | if message is None: | |
455 | message = '' | |
456 | # END message handling | |
457 | ||
458 | message = message.strip() | |
459 | if message.endswith(self.DONE_TOKEN): | |
460 | op_code |= self.END | |
461 | message = message[:-len(self.DONE_TOKEN)] | |
462 | # END end message handling | |
463 | message = message.strip(self.TOKEN_SEPARATOR) | |
464 | ||
465 | self.update(op_code, | |
466 | cur_count and float(cur_count), | |
467 | max_count and float(max_count), | |
468 | message) | |
477 | 469 | |
478 | 470 | def new_message_handler(self): |
479 | 471 | """ |