##// 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 else:
131 else:
132 so, se = org_repo.run_git_command(
132 so, se = org_repo.run_git_command(
133 'log --reverse --pretty="format: %%H" -s %s..%s'
133 ['log', '--reverse', '--pretty=format:%H',
134 % (org_rev, other_rev)
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 %s %s' % (org_rev, other_rev)
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 = "for-each-ref --format='%(refname)' 'refs/heads/*'"
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 heads.splitlines()))
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 = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
463 cmd = ['log', '%(old_rev)s..%(new_rev)s' % push_ref,
464 ' --reverse --pretty=format:"%H"')
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 **opts
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 "rev-list %s --children" % (rev_filter)
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 safe_int(limit, 0), cs_id, f_path)
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 cs_id, f_path)
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 -l --root -r %s -- "%s"' % (self.id, path)
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 --name-status %s %s --encoding=utf8' % (parent.raw_id,
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=True,
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 %s --reverse --date-order' % (rev_filter)
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_template = 'log --date-order --reverse --pretty=format:"%H"'
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_template += ' $branch_name'
543 cmd.append(branch_name)
564 cmd_params['branch_name'] = branch_name
565 else:
544 else:
566 rev_filter = settings.GIT_REV_FILTER
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 = ' '.join(['show'] + flags + [rev2])
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 = ' '.join(['diff'] + flags + [rev1, rev2])
606 cmd = ['diff'] + flags + [rev1, rev2]
630
607
631 if path:
608 if path:
632 cmd += ' -- "%s"' % path
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 += ['--', quote(url), quote(self.path)]
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', "--ff-only", quote(url)]
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 -h %s' % quote(url))
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 refs.append(ref)
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 if isinstance(cmd, (list, tuple)):
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 -b production')
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 production')
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 master')
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 -U%s --full-index --binary -p -M --abbrev=40 %s %s' %
697 ['diff', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
698 (3, self.repo._get_revision(0), self.repo._get_revision(1)))
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 -U%s --full-index --binary -p -M --abbrev=40 %s' %
704 ['show', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
705 (3, self.repo._get_revision(1)))
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 -U%s --full-index --binary -p -M --abbrev=40 %s %s -- "foo"'
711 ['diff', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
712 % (3, self.repo._get_revision(0), self.repo._get_revision(1)))
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