Show More
@@ -130,13 +130,13 b' class CompareController(BaseRepoControll' | |||||
130 |
|
130 | |||
131 | else: |
|
131 | else: | |
132 | so, se = org_repo.run_git_command( |
|
132 | so, se = org_repo.run_git_command( | |
133 |
'log --reverse --pretty= |
|
133 | ['log', '--reverse', '--pretty=format:%H', | |
134 |
|
|
134 | '-s', '%s..%s' % (org_rev, other_rev)] | |
135 | ) |
|
135 | ) | |
136 | other_changesets = [org_repo.get_changeset(cs) |
|
136 | other_changesets = [org_repo.get_changeset(cs) | |
137 | for cs in re.findall(r'[0-9a-fA-F]{40}', so)] |
|
137 | for cs in re.findall(r'[0-9a-fA-F]{40}', so)] | |
138 | so, se = org_repo.run_git_command( |
|
138 | so, se = org_repo.run_git_command( | |
139 |
'merge-base |
|
139 | ['merge-base', org_rev, other_rev] | |
140 | ) |
|
140 | ) | |
141 | ancestor = re.findall(r'[0-9a-fA-F]{40}', so)[0] |
|
141 | ancestor = re.findall(r'[0-9a-fA-F]{40}', so)[0] | |
142 | org_changesets = [] |
|
142 | org_changesets = [] |
@@ -447,21 +447,21 b' def handle_git_receive(repo_path, revs, ' | |||||
447 | repo._repo.refs.set_symbolic_ref('HEAD', |
|
447 | repo._repo.refs.set_symbolic_ref('HEAD', | |
448 | 'refs/heads/%s' % push_ref['name']) |
|
448 | 'refs/heads/%s' % push_ref['name']) | |
449 |
|
449 | |||
450 |
cmd = |
|
450 | cmd = ['for-each-ref', '--format=%(refname)','refs/heads/*'] | |
451 | heads = repo.run_git_command(cmd)[0] |
|
451 | heads = repo.run_git_command(cmd)[0] | |
|
452 | cmd = ['log', push_ref['new_rev'], | |||
|
453 | '--reverse', '--pretty=format:%H', '--not'] | |||
452 | heads = heads.replace(push_ref['ref'], '') |
|
454 | heads = heads.replace(push_ref['ref'], '') | |
453 | heads = ' '.join(map(lambda c: c.strip('\n').strip(), |
|
455 | for l in heads.splitlines(): | |
454 |
|
|
456 | cmd.append(l.strip()) | |
455 | cmd = (('log %(new_rev)s' % push_ref) + |
|
|||
456 | ' --reverse --pretty=format:"%H" --not ' + heads) |
|
|||
457 | git_revs += repo.run_git_command(cmd)[0].splitlines() |
|
457 | git_revs += repo.run_git_command(cmd)[0].splitlines() | |
458 |
|
458 | |||
459 | elif push_ref['new_rev'] == EmptyChangeset().raw_id: |
|
459 | elif push_ref['new_rev'] == EmptyChangeset().raw_id: | |
460 | #delete branch case |
|
460 | #delete branch case | |
461 | git_revs += ['delete_branch=>%s' % push_ref['name']] |
|
461 | git_revs += ['delete_branch=>%s' % push_ref['name']] | |
462 | else: |
|
462 | else: | |
463 |
cmd = |
|
463 | cmd = ['log', '%(old_rev)s..%(new_rev)s' % push_ref, | |
464 |
' |
|
464 | '--reverse', '--pretty=format:%H'] | |
465 | git_revs += repo.run_git_command(cmd)[0].splitlines() |
|
465 | git_revs += repo.run_git_command(cmd)[0].splitlines() | |
466 |
|
466 | |||
467 | elif _type == 'tags': |
|
467 | elif _type == 'tags': |
@@ -82,13 +82,12 b' class GitRepository(object):' | |||||
82 | server_advert = '# service=%s' % git_command |
|
82 | server_advert = '# service=%s' % git_command | |
83 | packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower() |
|
83 | packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower() | |
84 | _git_path = kallithea.CONFIG.get('git_path', 'git') |
|
84 | _git_path = kallithea.CONFIG.get('git_path', 'git') | |
|
85 | cmd = [_git_path, git_command[4:], | |||
|
86 | '--stateless-rpc', '--advertise-refs', self.content_path] | |||
|
87 | log.debug('handling cmd %s', cmd) | |||
85 | try: |
|
88 | try: | |
86 | out = subprocessio.SubprocessIOChunker( |
|
89 | out = subprocessio.SubprocessIOChunker(cmd, | |
87 | r'%s %s --stateless-rpc --advertise-refs "%s"' % ( |
|
90 | starting_values=[packet_len + server_advert + '0000'] | |
88 | _git_path, git_command[4:], self.content_path), |
|
|||
89 | starting_values=[ |
|
|||
90 | packet_len + server_advert + '0000' |
|
|||
91 | ] |
|
|||
92 | ) |
|
91 | ) | |
93 | except EnvironmentError, e: |
|
92 | except EnvironmentError, e: | |
94 | log.error(traceback.format_exc()) |
|
93 | log.error(traceback.format_exc()) | |
@@ -118,21 +117,17 b' class GitRepository(object):' | |||||
118 | else: |
|
117 | else: | |
119 | inputstream = environ['wsgi.input'] |
|
118 | inputstream = environ['wsgi.input'] | |
120 |
|
119 | |||
|
120 | gitenv = dict(os.environ) | |||
|
121 | # forget all configs | |||
|
122 | gitenv['GIT_CONFIG_NOGLOBAL'] = '1' | |||
|
123 | cmd = [_git_path, git_command[4:], '--stateless-rpc', self.content_path] | |||
|
124 | log.debug('handling cmd %s', cmd) | |||
121 | try: |
|
125 | try: | |
122 | gitenv = os.environ |
|
|||
123 | # forget all configs |
|
|||
124 | gitenv['GIT_CONFIG_NOGLOBAL'] = '1' |
|
|||
125 | opts = dict( |
|
|||
126 | env=gitenv, |
|
|||
127 | cwd=self.content_path, |
|
|||
128 | ) |
|
|||
129 | cmd = r'%s %s --stateless-rpc "%s"' % (_git_path, git_command[4:], |
|
|||
130 | self.content_path), |
|
|||
131 | log.debug('handling cmd %s' % cmd) |
|
|||
132 | out = subprocessio.SubprocessIOChunker( |
|
126 | out = subprocessio.SubprocessIOChunker( | |
133 | cmd, |
|
127 | cmd, | |
134 | inputstream=inputstream, |
|
128 | inputstream=inputstream, | |
135 |
|
|
129 | env=gitenv, | |
|
130 | cwd=self.content_path, | |||
136 | ) |
|
131 | ) | |
137 | except EnvironmentError, e: |
|
132 | except EnvironmentError, e: | |
138 | log.error(traceback.format_exc()) |
|
133 | log.error(traceback.format_exc()) |
@@ -799,7 +799,7 b' def check_git_version():' | |||||
799 | if 'git' not in BACKENDS: |
|
799 | if 'git' not in BACKENDS: | |
800 | return None |
|
800 | return None | |
801 |
|
801 | |||
802 | stdout, stderr = GitRepository._run_git_command('--version', _bare=True, |
|
802 | stdout, stderr = GitRepository._run_git_command(['--version'], _bare=True, | |
803 | _safe=True) |
|
803 | _safe=True) | |
804 |
|
804 | |||
805 | m = re.search("\d+.\d+.\d+", stdout) |
|
805 | m = re.search("\d+.\d+.\d+", stdout) |
@@ -189,7 +189,7 b' class GitChangeset(BaseChangeset):' | |||||
189 | """ |
|
189 | """ | |
190 | rev_filter = settings.GIT_REV_FILTER |
|
190 | rev_filter = settings.GIT_REV_FILTER | |
191 | so, se = self.repository.run_git_command( |
|
191 | so, se = self.repository.run_git_command( | |
192 |
|
|
192 | ['rev-list', rev_filter, '--children'] | |
193 | ) |
|
193 | ) | |
194 |
|
194 | |||
195 | children = [] |
|
195 | children = [] | |
@@ -288,12 +288,12 b' class GitChangeset(BaseChangeset):' | |||||
288 | f_path = safe_str(path) |
|
288 | f_path = safe_str(path) | |
289 |
|
289 | |||
290 | if limit: |
|
290 | if limit: | |
291 | cmd = 'log -n %s --pretty="format: %%H" -s %s -- "%s"' % ( |
|
291 | cmd = ['log', '-n', str(safe_int(limit, 0)), | |
292 |
|
|
292 | '--pretty=format:%H', '-s', cs_id, '--', f_path] | |
293 |
|
293 | |||
294 | else: |
|
294 | else: | |
295 | cmd = 'log --pretty="format: %%H" -s %s -- "%s"' % ( |
|
295 | cmd = ['log', | |
296 |
|
|
296 | '--pretty=format:%H', '-s', cs_id, '--', f_path] | |
297 | so, se = self.repository.run_git_command(cmd) |
|
297 | so, se = self.repository.run_git_command(cmd) | |
298 | ids = re.findall(r'[0-9a-fA-F]{40}', so) |
|
298 | ids = re.findall(r'[0-9a-fA-F]{40}', so) | |
299 | return [self.repository.get_changeset(sha) for sha in ids] |
|
299 | return [self.repository.get_changeset(sha) for sha in ids] | |
@@ -321,7 +321,7 b' class GitChangeset(BaseChangeset):' | |||||
321 | generally not good. Should be replaced with algorithm iterating |
|
321 | generally not good. Should be replaced with algorithm iterating | |
322 | commits. |
|
322 | commits. | |
323 | """ |
|
323 | """ | |
324 |
cmd = 'blame |
|
324 | cmd = ['blame', '-l', '--root', '-r', self.id, '--', path] | |
325 | # -l ==> outputs long shas (and we need all 40 characters) |
|
325 | # -l ==> outputs long shas (and we need all 40 characters) | |
326 | # --root ==> doesn't put '^' character for boundaries |
|
326 | # --root ==> doesn't put '^' character for boundaries | |
327 | # -r sha ==> blames for the given revision |
|
327 | # -r sha ==> blames for the given revision | |
@@ -471,8 +471,8 b' class GitChangeset(BaseChangeset):' | |||||
471 | def _diff_name_status(self): |
|
471 | def _diff_name_status(self): | |
472 | output = [] |
|
472 | output = [] | |
473 | for parent in self.parents: |
|
473 | for parent in self.parents: | |
474 |
cmd = 'diff |
|
474 | cmd = ['diff', '--name-status', parent.raw_id, self.raw_id, | |
475 | self.raw_id) |
|
475 | '--encoding=utf8'] | |
476 | so, se = self.repository.run_git_command(cmd) |
|
476 | so, se = self.repository.run_git_command(cmd) | |
477 | output.append(so.strip()) |
|
477 | output.append(so.strip()) | |
478 | return '\n'.join(output) |
|
478 | return '\n'.join(output) |
@@ -18,18 +18,6 b' import urllib2' | |||||
18 | import logging |
|
18 | import logging | |
19 | import posixpath |
|
19 | import posixpath | |
20 | import string |
|
20 | import string | |
21 | import sys |
|
|||
22 | if sys.platform == "win32": |
|
|||
23 | from subprocess import list2cmdline |
|
|||
24 | def quote(s): |
|
|||
25 | return list2cmdline([s]) |
|
|||
26 | else: |
|
|||
27 | try: |
|
|||
28 | # Python <=2.7 |
|
|||
29 | from pipes import quote |
|
|||
30 | except ImportError: |
|
|||
31 | # Python 3.3+ |
|
|||
32 | from shlex import quote |
|
|||
33 |
|
21 | |||
34 | from dulwich.objects import Tag |
|
22 | from dulwich.objects import Tag | |
35 | from dulwich.repo import Repo, NotGitRepository |
|
23 | from dulwich.repo import Repo, NotGitRepository | |
@@ -135,10 +123,7 b' class GitRepository(BaseRepository):' | |||||
135 | del opts['_safe'] |
|
123 | del opts['_safe'] | |
136 | safe_call = True |
|
124 | safe_call = True | |
137 |
|
125 | |||
138 | _str_cmd = False |
|
126 | assert isinstance(cmd, list), cmd | |
139 | if isinstance(cmd, basestring): |
|
|||
140 | cmd = [cmd] |
|
|||
141 | _str_cmd = True |
|
|||
142 |
|
127 | |||
143 | gitenv = os.environ |
|
128 | gitenv = os.environ | |
144 | # need to clean fix GIT_DIR ! |
|
129 | # need to clean fix GIT_DIR ! | |
@@ -148,13 +133,11 b' class GitRepository(BaseRepository):' | |||||
148 |
|
133 | |||
149 | _git_path = settings.GIT_EXECUTABLE_PATH |
|
134 | _git_path = settings.GIT_EXECUTABLE_PATH | |
150 | cmd = [_git_path] + _copts + cmd |
|
135 | cmd = [_git_path] + _copts + cmd | |
151 | if _str_cmd: |
|
|||
152 | cmd = ' '.join(cmd) |
|
|||
153 |
|
136 | |||
154 | try: |
|
137 | try: | |
155 | _opts = dict( |
|
138 | _opts = dict( | |
156 | env=gitenv, |
|
139 | env=gitenv, | |
157 |
shell= |
|
140 | shell=False, | |
158 | ) |
|
141 | ) | |
159 | _opts.update(opts) |
|
142 | _opts.update(opts) | |
160 | p = subprocessio.SubprocessIOChunker(cmd, **_opts) |
|
143 | p = subprocessio.SubprocessIOChunker(cmd, **_opts) | |
@@ -268,7 +251,7 b' class GitRepository(BaseRepository):' | |||||
268 | return [] |
|
251 | return [] | |
269 |
|
252 | |||
270 | rev_filter = settings.GIT_REV_FILTER |
|
253 | rev_filter = settings.GIT_REV_FILTER | |
271 |
cmd = 'rev-list |
|
254 | cmd = ['rev-list', rev_filter, '--reverse', '--date-order'] | |
272 | try: |
|
255 | try: | |
273 | so, se = self.run_git_command(cmd) |
|
256 | so, se = self.run_git_command(cmd) | |
274 | except RepositoryError: |
|
257 | except RepositoryError: | |
@@ -551,22 +534,16 b' class GitRepository(BaseRepository):' | |||||
551 |
|
534 | |||
552 | # %H at format means (full) commit hash, initial hashes are retrieved |
|
535 | # %H at format means (full) commit hash, initial hashes are retrieved | |
553 | # in ascending date order |
|
536 | # in ascending date order | |
554 |
cmd |
|
537 | cmd = ['log', '--date-order', '--reverse', '--pretty=format:%H'] | |
555 | cmd_params = {} |
|
|||
556 | if start_date: |
|
538 | if start_date: | |
557 | cmd_template += ' --since "$since"' |
|
539 | cmd += ['--since', start_date.strftime('%m/%d/%y %H:%M:%S')] | |
558 | cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S') |
|
|||
559 | if end_date: |
|
540 | if end_date: | |
560 | cmd_template += ' --until "$until"' |
|
541 | cmd += ['--until', end_date.strftime('%m/%d/%y %H:%M:%S')] | |
561 | cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S') |
|
|||
562 | if branch_name: |
|
542 | if branch_name: | |
563 |
cmd |
|
543 | cmd.append(branch_name) | |
564 | cmd_params['branch_name'] = branch_name |
|
|||
565 | else: |
|
544 | else: | |
566 |
|
|
545 | cmd.append(settings.GIT_REV_FILTER) | |
567 | cmd_template += ' %s' % (rev_filter) |
|
|||
568 |
|
546 | |||
569 | cmd = string.Template(cmd_template).safe_substitute(**cmd_params) |
|
|||
570 | revs = self.run_git_command(cmd)[0].splitlines() |
|
547 | revs = self.run_git_command(cmd)[0].splitlines() | |
571 | start_pos = 0 |
|
548 | start_pos = 0 | |
572 | end_pos = len(revs) |
|
549 | end_pos = len(revs) | |
@@ -622,14 +599,14 b' class GitRepository(BaseRepository):' | |||||
622 |
|
599 | |||
623 | if rev1 == self.EMPTY_CHANGESET: |
|
600 | if rev1 == self.EMPTY_CHANGESET: | |
624 | rev2 = self.get_changeset(rev2).raw_id |
|
601 | rev2 = self.get_changeset(rev2).raw_id | |
625 |
cmd = |
|
602 | cmd = ['show'] + flags + [rev2] | |
626 | else: |
|
603 | else: | |
627 | rev1 = self.get_changeset(rev1).raw_id |
|
604 | rev1 = self.get_changeset(rev1).raw_id | |
628 | rev2 = self.get_changeset(rev2).raw_id |
|
605 | rev2 = self.get_changeset(rev2).raw_id | |
629 |
cmd = |
|
606 | cmd = ['diff'] + flags + [rev1, rev2] | |
630 |
|
607 | |||
631 | if path: |
|
608 | if path: | |
632 |
cmd += ' |
|
609 | cmd += ['--', path] | |
633 |
|
610 | |||
634 | stdout, stderr = self.run_git_command(cmd) |
|
611 | stdout, stderr = self.run_git_command(cmd) | |
635 | # TODO: don't ignore stderr |
|
612 | # TODO: don't ignore stderr | |
@@ -663,8 +640,7 b' class GitRepository(BaseRepository):' | |||||
663 | cmd.append('--bare') |
|
640 | cmd.append('--bare') | |
664 | elif not update_after_clone: |
|
641 | elif not update_after_clone: | |
665 | cmd.append('--no-checkout') |
|
642 | cmd.append('--no-checkout') | |
666 |
cmd += ['--', |
|
643 | cmd += ['--', url, self.path] | |
667 | cmd = ' '.join(cmd) |
|
|||
668 | # If error occurs run_git_command raises RepositoryError already |
|
644 | # If error occurs run_git_command raises RepositoryError already | |
669 | self.run_git_command(cmd) |
|
645 | self.run_git_command(cmd) | |
670 |
|
646 | |||
@@ -673,8 +649,7 b' class GitRepository(BaseRepository):' | |||||
673 | Tries to pull changes from external location. |
|
649 | Tries to pull changes from external location. | |
674 | """ |
|
650 | """ | |
675 | url = self._get_url(url) |
|
651 | url = self._get_url(url) | |
676 |
cmd = ['pull', |
|
652 | cmd = ['pull', '--ff-only', url] | |
677 | cmd = ' '.join(cmd) |
|
|||
678 | # If error occurs run_git_command raises RepositoryError already |
|
653 | # If error occurs run_git_command raises RepositoryError already | |
679 | self.run_git_command(cmd) |
|
654 | self.run_git_command(cmd) | |
680 |
|
655 | |||
@@ -683,13 +658,11 b' class GitRepository(BaseRepository):' | |||||
683 | Tries to pull changes from external location. |
|
658 | Tries to pull changes from external location. | |
684 | """ |
|
659 | """ | |
685 | url = self._get_url(url) |
|
660 | url = self._get_url(url) | |
686 |
so, se = self.run_git_command('ls-remote |
|
661 | so, se = self.run_git_command(['ls-remote', '-h', url]) | |
687 | refs = [] |
|
662 | cmd = ['fetch', url, '--'] | |
688 | for line in (x for x in so.splitlines()): |
|
663 | for line in (x for x in so.splitlines()): | |
689 | sha, ref = line.split('\t') |
|
664 | sha, ref = line.split('\t') | |
690 |
|
|
665 | cmd.append('+%s:%s' % (ref, ref)) | |
691 | refs = ' '.join(('+%s:%s' % (r, r) for r in refs)) |
|
|||
692 | cmd = '''fetch %s -- %s''' % (quote(url), refs) |
|
|||
693 | self.run_git_command(cmd) |
|
666 | self.run_git_command(cmd) | |
694 |
|
667 | |||
695 | def _update_server_info(self): |
|
668 | def _update_server_info(self): |
@@ -342,11 +342,9 b' class SubprocessIOChunker(object):' | |||||
342 | input_streamer.start() |
|
342 | input_streamer.start() | |
343 | inputstream = input_streamer.output |
|
343 | inputstream = input_streamer.output | |
344 |
|
344 | |||
345 | _shell = kwargs.get('shell', True) |
|
345 | # Note: fragile cmd mangling has been removed for use in Kallithea | |
346 |
|
|
346 | assert isinstance(cmd, list), cmd | |
347 | cmd = ' '.join(cmd) |
|
|||
348 |
|
347 | |||
349 | kwargs['shell'] = _shell |
|
|||
350 | _p = subprocess.Popen(cmd, bufsize=-1, |
|
348 | _p = subprocess.Popen(cmd, bufsize=-1, | |
351 | stdin=inputstream, |
|
349 | stdin=inputstream, | |
352 | stdout=subprocess.PIPE, |
|
350 | stdout=subprocess.PIPE, |
@@ -682,34 +682,34 b' class GitSpecificWithRepoTest(_BackendTe' | |||||
682 | 'base') |
|
682 | 'base') | |
683 |
|
683 | |||
684 | def test_workdir_get_branch(self): |
|
684 | def test_workdir_get_branch(self): | |
685 |
self.repo.run_git_command('checkout |
|
685 | self.repo.run_git_command(['checkout', '-b', 'production']) | |
686 | # Regression test: one of following would fail if we don't check |
|
686 | # Regression test: one of following would fail if we don't check | |
687 | # .git/HEAD file |
|
687 | # .git/HEAD file | |
688 |
self.repo.run_git_command('checkout |
|
688 | self.repo.run_git_command(['checkout', 'production']) | |
689 | self.assertEqual(self.repo.workdir.get_branch(), 'production') |
|
689 | self.assertEqual(self.repo.workdir.get_branch(), 'production') | |
690 |
self.repo.run_git_command('checkout |
|
690 | self.repo.run_git_command(['checkout', 'master']) | |
691 | self.assertEqual(self.repo.workdir.get_branch(), 'master') |
|
691 | self.assertEqual(self.repo.workdir.get_branch(), 'master') | |
692 |
|
692 | |||
693 | def test_get_diff_runs_git_command_with_hashes(self): |
|
693 | def test_get_diff_runs_git_command_with_hashes(self): | |
694 | self.repo.run_git_command = mock.Mock(return_value=['', '']) |
|
694 | self.repo.run_git_command = mock.Mock(return_value=['', '']) | |
695 | self.repo.get_diff(0, 1) |
|
695 | self.repo.get_diff(0, 1) | |
696 | self.repo.run_git_command.assert_called_once_with( |
|
696 | self.repo.run_git_command.assert_called_once_with( | |
697 |
'diff |
|
697 | ['diff', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40', | |
698 |
|
|
698 | self.repo._get_revision(0), self.repo._get_revision(1)]) | |
699 |
|
699 | |||
700 | def test_get_diff_runs_git_command_with_str_hashes(self): |
|
700 | def test_get_diff_runs_git_command_with_str_hashes(self): | |
701 | self.repo.run_git_command = mock.Mock(return_value=['', '']) |
|
701 | self.repo.run_git_command = mock.Mock(return_value=['', '']) | |
702 | self.repo.get_diff(self.repo.EMPTY_CHANGESET, 1) |
|
702 | self.repo.get_diff(self.repo.EMPTY_CHANGESET, 1) | |
703 | self.repo.run_git_command.assert_called_once_with( |
|
703 | self.repo.run_git_command.assert_called_once_with( | |
704 |
'show |
|
704 | ['show', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40', | |
705 |
|
|
705 | self.repo._get_revision(1)]) | |
706 |
|
706 | |||
707 | def test_get_diff_runs_git_command_with_path_if_its_given(self): |
|
707 | def test_get_diff_runs_git_command_with_path_if_its_given(self): | |
708 | self.repo.run_git_command = mock.Mock(return_value=['', '']) |
|
708 | self.repo.run_git_command = mock.Mock(return_value=['', '']) | |
709 | self.repo.get_diff(0, 1, 'foo') |
|
709 | self.repo.get_diff(0, 1, 'foo') | |
710 | self.repo.run_git_command.assert_called_once_with( |
|
710 | self.repo.run_git_command.assert_called_once_with( | |
711 |
'diff |
|
711 | ['diff', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40', | |
712 |
|
|
712 | self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo']) | |
713 |
|
713 | |||
714 |
|
714 | |||
715 | class GitRegressionTest(_BackendTestMixin, unittest.TestCase): |
|
715 | class GitRegressionTest(_BackendTestMixin, unittest.TestCase): |
General Comments 0
You need to be logged in to leave comments.
Login now