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