Properly escape execute commands and handle environment variables cross-platform
Updated 4 months, 2 weeks ago
| Christian Hammond | Reviewers | ||
| trunk | reviewboard | ||
| 423, 543, 582, 594 | |||
| None | Review Board SVN | ||
We were relying on some really bad assumptions in post-review. First, as of r1415, we were assuming all parameters to diff should be quoted, which broke post-review with Perforce when it expected a value that could be cast to an integer. We now represent all commands as lists so that we can use subprocess's support for auto-escaping/quoting of a command and parameters. This should remove the hacks we use and prevent future problems. Second, we were assuming that we could call "env" to set an environment variable for Subversion. This of course doesn't work on Windows. We should have instead used subprocess.Popen's native support for setting the environment.
Couldn't test other SCMTools due to not having a setup here, but I used this code to post this review request.
| /trunk/reviewboard/contrib/tools/post-review | |||
|---|---|---|---|
| Revision 1453 | New Change | ||
| ... | 538 lines hidden [Expand] | ||
| 539 | 539 | ||
| 540 | CVS repositories do not support branches of branches in a way that |
540 | CVS repositories do not support branches of branches in a way that |
| 541 | makes parent diffs possible, so we never return a parent diff |
541 | makes parent diffs possible, so we never return a parent diff |
| 542 | (the second value in the tuple). |
542 | (the second value in the tuple). |
| 543 | """ |
543 | """ |
| 544 | return (self.do_diff(' '.join(files)), None) |
544 | return (self.do_diff(files), None) |
| 545 | 545 | ||
| 546 | def diff_between_revisions(self, revision_range): |
546 | def diff_between_revisions(self, revision_range): |
| 547 | """ |
547 | """ |
| 548 | Performs a diff between 2 revisions of a CVS repository. |
548 | Performs a diff between 2 revisions of a CVS repository. |
| 549 | """ |
549 | """ |
| 550 | rev_str = '' |
550 | revs = [] |
| 551 | 551 | ||
| 552 | for rev in revision_range.split(":"): |
552 | for rev in revision_range.split(":"): |
| 553 | rev_str += "-r %s" % rev |
553 | revs += ["-r", rev] |
| 554 | 554 | ||
| 555 | return self.do_diff(rev_str) |
555 | return self.do_diff(revs) |
| 556 | 556 | ||
| 557 | def do_diff(self, params): |
557 | def do_diff(self, params): |
| 558 | """ |
558 | """ |
| 559 | Performs the actual diff operation through cvs diff, handling |
559 | Performs the actual diff operation through cvs diff, handling |
| 560 | fake errors generated by CVS. |
560 | fake errors generated by CVS. |
| 561 | """ |
561 | """ |
| 562 | # Diff returns "1" if differences were found. |
562 | # Diff returns "1" if differences were found. |
| 563 | return execute('cvs diff -uN %s' % params, |
563 | return execute(["cvs", "diff", "-uN"] + params, |
| 564 | extra_ignore_errors=(1,)) |
564 | extra_ignore_errors=(1,)) |
| 565 | 565 | ||
| 566 | 566 | ||
| 567 | class SVNClient(SCMClient): |
567 | class SVNClient(SCMClient): |
| 568 | """ |
568 | """ |
| 569 | A wrapper around the svn Subversion tool that fetches repository |
569 | A wrapper around the svn Subversion tool that fetches repository |
| 570 | information and generates compatible diffs. |
570 | information and generates compatible diffs. |
| 571 | """ |
571 | """ |
| 572 | def get_repository_info(self): |
572 | def get_repository_info(self): |
| 573 | if not check_install('svn help'): |
573 | if not check_install('svn help'): |
| 574 | return None |
574 | return None |
| 575 | 575 | ||
| 576 | data = execute('env LANG=en_US.UTF-8 svn info', ignore_errors=True) |
576 | data = execute(["svn", "info"], |
| 577 | 577 | env={'LANG': 'en_US.UTF-8'}, |
|
| 578 | ignore_errors=True) |
||
| 578 | m = re.search(r'^Repository Root: (.+)$', data, re.M) |
579 | m = re.search(r'^Repository Root: (.+)$', data, re.M) |
| 579 | if not m: |
580 | if not m: |
| 580 | return None |
581 | return None |
| 581 | 582 | ||
| 582 | path = m.group(1) |
583 | path = m.group(1) |
| ... | 21 lines hidden [Expand] | ||
| 604 | 605 | ||
| 605 | return self.scan_for_server_property(repository_info) |
606 | return self.scan_for_server_property(repository_info) |
| 606 | 607 | ||
| 607 | def scan_for_server_property(self, repository_info): |
608 | def scan_for_server_property(self, repository_info): |
| 608 | def get_url_prop(path): |
609 | def get_url_prop(path): |
| 609 | url = execute("env LANG=en_US.UTF-8 svn propget reviewboard:url %s" % path).strip() |
610 | url = execute(["svn", "propget", "reviewboard:url", path], |
| 611 | env={'LANG': 'en_US.UTF-8'}).strip() |
||
| 610 | return url or None |
612 | return url or None |
| 611 | 613 | ||
| 612 | for path in walk_parents(os.getcwd()): |
614 | for path in walk_parents(os.getcwd()): |
| 613 | if not os.path.exists(os.path.join(path, ".svn")): |
615 | if not os.path.exists(os.path.join(path, ".svn")): |
| 614 | break |
616 | break |
| ... | 10 lines hidden [Expand] | ||
| 625 | 627 | ||
| 626 | SVN repositories do not support branches of branches in a way that |
628 | SVN repositories do not support branches of branches in a way that |
| 627 | makes parent diffs possible, so we never return a parent diff |
629 | makes parent diffs possible, so we never return a parent diff |
| 628 | (the second value in the tuple). |
630 | (the second value in the tuple). |
| 629 | """ |
631 | """ |
| 630 | return (self.do_diff('env LANG=en_US.UTF-8 svn diff --diff-cmd=diff %s' % ' '.join(files)), |
632 | return (self.do_diff(["svn", "diff", "--diff-cmd=diff"] + files), |
| 631 | None) |
633 | None) |
| 632 | 634 | ||
| 633 | def diff_between_revisions(self, revision_range): |
635 | def diff_between_revisions(self, revision_range): |
| 634 | """ |
636 | """ |
| 635 | Performs a diff between 2 revisions of a Subversion repository. |
637 | Performs a diff between 2 revisions of a Subversion repository. |
| 636 | """ |
638 | """ |
| 637 | return self.do_diff('env LANG=en_US.UTF-8 svn diff --diff-cmd=diff -r %s' % revision_range) |
639 | return self.do_diff(["svn", "diff", "--diff-cmd=diff", "-r", |
| 640 | revision_range]) |
||
| 638 | 641 | ||
| 639 | def do_diff(self, cmd): |
642 | def do_diff(self, cmd): |
| 640 | """ |
643 | """ |
| 641 | Performs the actual diff operation, handling renames and converting |
644 | Performs the actual diff operation, handling renames and converting |
| 642 | paths to absolute. |
645 | paths to absolute. |
| 643 | """ |
646 | """ |
| 644 | diff = execute(cmd, split_lines=True) |
647 | diff = execute(cmd, env={'LANG': 'en_US.UTF-8'}, split_lines=True) |
| 645 | diff = self.handle_renames(diff) |
648 | diff = self.handle_renames(diff) |
| 646 | diff = self.convert_to_absolute_paths(diff) |
649 | diff = self.convert_to_absolute_paths(diff) |
| 647 | 650 | ||
| 648 | return ''.join(diff) |
651 | return ''.join(diff) |
| 649 | 652 | ||
| ... | 66 lines hidden [Expand] | ||
| 716 | return result |
719 | return result |
| 717 | 720 | ||
| 718 | def svn_info(self, path): |
721 | def svn_info(self, path): |
| 719 | """Return a dict which is the result of 'svn info' at a given path.""" |
722 | """Return a dict which is the result of 'svn info' at a given path.""" |
| 720 | svninfo = {} |
723 | svninfo = {} |
| 721 | for info in execute("env LANG=en_US.UTF-8 svn info '%s'" % path).splitlines(): |
724 | for info in execute(["svn", "info", path], |
| 725 | env={'LANG': 'en_US.UTF-8'}, |
||
| 726 | split_lines=True): |
||
| 722 | parts = info.split(": ", 1) |
727 | parts = info.split(": ", 1) |
| 723 | if len(parts) == 2: |
728 | if len(parts) == 2: |
| 724 | key, value = parts |
729 | key, value = parts |
| 725 | svninfo[key] = value |
730 | svninfo[key] = value |
| 726 | 731 | ||
| ... | 30 lines hidden [Expand] | ||
| 757 | """ |
762 | """ |
| 758 | def get_repository_info(self): |
763 | def get_repository_info(self): |
| 759 | if not check_install('p4 help'): |
764 | if not check_install('p4 help'): |
| 760 | return None |
765 | return None |
| 761 | 766 | ||
| 762 | data = execute('p4 info', ignore_errors=True) |
767 | data = execute(["p4", "info"], ignore_errors=True) |
| 763 | 768 | ||
| 764 | m = re.search(r'^Server address: (.+)$', data, re.M) |
769 | m = re.search(r'^Server address: (.+)$', data, re.M) |
| 765 | if not m: |
770 | if not m: |
| 766 | return None |
771 | return None |
| 767 | 772 | ||
| ... | 28 lines hidden [Expand] | ||
| 796 | except ValueError: |
801 | except ValueError: |
| 797 | die("You must enter a valid change number") |
802 | die("You must enter a valid change number") |
| 798 | 803 | ||
| 799 | debug("Generating diff for changenum %s" % changenum) |
804 | debug("Generating diff for changenum %s" % changenum) |
| 800 | 805 | ||
| 801 | description = execute('p4 describe -s %d' % changenum).splitlines() |
806 | description = execute(["p4", "describe", "-s", str(changenum)], |
| 807 | split_lines=True) |
||
| 802 | if '*pending*' in description[0]: |
808 | if '*pending*' in description[0]: |
| 803 | cl_is_pending = True |
809 | cl_is_pending = True |
| 804 | 810 | ||
| 805 | # Get the file list |
811 | # Get the file list |
| 806 | for line_num, line in enumerate(description): |
812 | for line_num, line in enumerate(description): |
| ... | 78 lines hidden [Expand] | ||
| 885 | old_file = tmp_diff_from_filename |
891 | old_file = tmp_diff_from_filename |
| 886 | changetype_short = "D" |
892 | changetype_short = "D" |
| 887 | else: |
893 | else: |
| 888 | die("Unknown change type '%s' for %s" % (changetype, depot_path)) |
894 | die("Unknown change type '%s' for %s" % (changetype, depot_path)) |
| 889 | 895 | ||
| 890 | diff_cmd = 'diff -urNp "%s" "%s"' % (old_file, new_file) |
896 | diff_cmd = ["diff", "-urNp", old_file, new_file] |
| 891 | # Diff returns "1" if differences were found. |
897 | # Diff returns "1" if differences were found. |
| 892 | dl = execute(diff_cmd, extra_ignore_errors=(1,2)).splitlines(True) |
898 | dl = execute(diff_cmd, extra_ignore_errors=(1,2)).splitlines(True) |
| 893 | 899 | ||
| 894 | if local_name.startswith(cwd): |
900 | if local_name.startswith(cwd): |
| 895 | local_path = local_name[len(cwd) + 1:] |
901 | local_path = local_name[len(cwd) + 1:] |
| ... | 62 lines hidden [Expand] | ||
| 958 | Grabs a file from Perforce and writes it to a temp file. We do this |
964 | Grabs a file from Perforce and writes it to a temp file. We do this |
| 959 | wrather than telling p4 print to write it out in order to work around |
965 | wrather than telling p4 print to write it out in order to work around |
| 960 | a permissions bug on Windows. |
966 | a permissions bug on Windows. |
| 961 | """ |
967 | """ |
| 962 | debug('Writing "%s" to "%s"' % (depot_path, tmpfile)) |
968 | debug('Writing "%s" to "%s"' % (depot_path, tmpfile)) |
| 963 | data = execute('p4 print -q "%s"' % depot_path) |
969 | data = execute(["p4", "print", "-q", depot_path]) |
| 964 | 970 | ||
| 965 | f = open(tmpfile, "w") |
971 | f = open(tmpfile, "w") |
| 966 | f.write(data) |
972 | f.write(data) |
| 967 | f.close() |
973 | f.close() |
| 968 | 974 | ||
| 969 | def _depot_to_local(self, depot_path): |
975 | def _depot_to_local(self, depot_path): |
| 970 | """ |
976 | """ |
| 971 | Given a path in the depot return the path on the local filesystem to |
977 | Given a path in the depot return the path on the local filesystem to |
| 972 | the same file. |
978 | the same file. |
| 973 | """ |
979 | """ |
| 974 | # $ p4 where //user/bvanzant/main/testing |
980 | # $ p4 where //user/bvanzant/main/testing |
| 975 | # //user/bvanzant/main/testing //bvanzant:test05:home/testing /home/bvanzant/home-versioned/testing |
981 | # //user/bvanzant/main/testing //bvanzant:test05:home/testing /home/bvanzant/home-versioned/testing |
| 976 | cmd = 'p4 where "%s"' % (depot_path,) |
982 | where_output = execute(["p4", "where", depot_path], split_lines=True) |
| 977 | where_output = execute(cmd).splitlines() |
||
| 978 | # Take only the last line from the where command. If you have a |
983 | # Take only the last line from the where command. If you have a |
| 979 | # multi-line view mapping with exclusions, Perforce will display |
984 | # multi-line view mapping with exclusions, Perforce will display |
| 980 | # the exclusions in order, with the last line showing the actual |
985 | # the exclusions in order, with the last line showing the actual |
| 981 | # location. |
986 | # location. |
| 982 | last_line = where_output[-1] |
987 | last_line = where_output[-1] |
| ... | 9 lines hidden [Expand] | ||
| 992 | """ |
997 | """ |
| 993 | def get_repository_info(self): |
998 | def get_repository_info(self): |
| 994 | if not check_install('hg --help'): |
999 | if not check_install('hg --help'): |
| 995 | return None |
1000 | return None |
| 996 | 1001 | ||
| 997 | data = execute('hg root', ignore_errors=True) |
1002 | data = execute(["hg", "root"], ignore_errors=True) |
| 998 | if data.startswith('abort:'): |
1003 | if data.startswith('abort:'): |
| 999 | # hg aborted => no mercurial repository here. |
1004 | # hg aborted => no mercurial repository here. |
| 1000 | return None |
1005 | return None |
| 1001 | 1006 | ||
| 1002 | # Elsewhere, hg root output give us the repository path. |
1007 | # Elsewhere, hg root output give us the repository path. |
| ... | 26 lines hidden [Expand] | ||
| 1029 | """ |
1034 | """ |
| 1030 | Performs a diff across all modified files in a Mercurial repository. |
1035 | Performs a diff across all modified files in a Mercurial repository. |
| 1031 | """ |
1036 | """ |
| 1032 | # We don't support parent diffs with Mercurial yet, so return None |
1037 | # We don't support parent diffs with Mercurial yet, so return None |
| 1033 | # for the parent diff. |
1038 | # for the parent diff. |
| 1034 | return (execute('hg diff %s' % ' '.join(files)), None) |
1039 | return (execute(["hg", "diff"] + files), None) |
| 1035 | 1040 | ||
| 1036 | def diff_between_revisions(self, revision_range): |
1041 | def diff_between_revisions(self, revision_range): |
| 1037 | """ |
1042 | """ |
| 1038 | Performs a diff between 2 revisions of a Mercurial repository. |
1043 | Performs a diff between 2 revisions of a Mercurial repository. |
| 1039 | """ |
1044 | """ |
| 1040 | r1, r2 = revision_range.split(':') |
1045 | r1, r2 = revision_range.split(':') |
| 1041 | return execute('hg diff -r %s -r %s' % (r1, r2)) |
1046 | return execute(["hg", "diff", "-r", r1, "-r", r2]) |
| 1042 | 1047 | ||
| 1043 | 1048 | ||
| 1044 | class GitClient(SCMClient): |
1049 | class GitClient(SCMClient): |
| 1045 | """ |
1050 | """ |
| 1046 | A wrapper around git that fetches repository information and generates |
1051 | A wrapper around git that fetches repository information and generates |
| 1047 | compatible diffs. This will attempt to generate a diff suitable for the |
1052 | compatible diffs. This will attempt to generate a diff suitable for the |
| 1048 | remote repository, whether git, SVN or Perforce. |
1053 | remote repository, whether git, SVN or Perforce. |
| 1049 | """ |
1054 | """ |
| 1050 | def get_repository_info(self): |
1055 | def get_repository_info(self): |
| 1051 | if not check_install('git --help'): |
1056 | if not check_install('git --help'): |
| 1052 | return None |
1057 | return None |
| 1053 | 1058 | ||
| 1054 | git_dir = execute('git rev-parse --git-dir', ignore_errors=True).strip() |
1059 | git_dir = execute(["git", "rev-parse", "--git-dir"], |
| 1060 | ignore_errors=True).strip() |
||
| 1055 | 1061 | ||
| 1056 | if git_dir.startswith("fatal:") or not os.path.isdir(git_dir): |
1062 | if git_dir.startswith("fatal:") or not os.path.isdir(git_dir): |
| 1057 | return None |
1063 | return None |
| 1058 | 1064 | ||
| 1059 | # We know we have something we can work with. Let's find out |
1065 | # We know we have something we can work with. Let's find out |
| 1060 | # what it is. We'll try SVN first. |
1066 | # what it is. We'll try SVN first. |
| 1061 | data = execute("git svn info", ignore_errors=True) |
1067 | data = execute(["git", "svn", "info"], ignore_errors=True) |
| 1062 | 1068 | ||
| 1063 | m = re.search(r'^Repository Root: (.+)$', data, re.M) |
1069 | m = re.search(r'^Repository Root: (.+)$', data, re.M) |
| 1064 | if m: |
1070 | if m: |
| 1065 | path = m.group(1) |
1071 | path = m.group(1) |
| 1066 | m = re.search(r'^URL: (.+)$', data, re.M) |
1072 | m = re.search(r'^URL: (.+)$', data, re.M) |
| ... | 6 lines hidden [Expand] | ||
| 1073 | else: |
1079 | else: |
| 1074 | # Versions of git-svn before 1.5.4 don't (appear to) support |
1080 | # Versions of git-svn before 1.5.4 don't (appear to) support |
| 1075 | # 'git svn info'. If we fail because of an older git install, |
1081 | # 'git svn info'. If we fail because of an older git install, |
| 1076 | # here, figure out what version of git is installed and give |
1082 | # here, figure out what version of git is installed and give |
| 1077 | # the user a hint about what to do next. |
1083 | # the user a hint about what to do next. |
| 1078 | version = execute("git svn --version", ignore_errors=True) |
1084 | version = execute(["git", "svn", "--version"], ignore_errors=True) |
| 1079 | version_parts = re.search('version (\d+)\.(\d+)\.(\d+)', |
1085 | version_parts = re.search('version (\d+)\.(\d+)\.(\d+)', |
| 1080 | version) |
1086 | version) |
| 1081 | if (version_parts and |
1087 | if (version_parts and |
| 1082 | not self.is_valid_version((int(version_parts.group(1)), |
1088 | not self.is_valid_version((int(version_parts.group(1)), |
| 1083 | int(version_parts.group(2)), |
1089 | int(version_parts.group(2)), |
| ... | 24 lines hidden [Expand] | ||
| 1108 | def scan_for_server(self, repository_info): |
1114 | def scan_for_server(self, repository_info): |
| 1109 | # Scan first for dot files, since it's faster and will cover the |
1115 | # Scan first for dot files, since it's faster and will cover the |
| 1110 | # user's $HOME/.reviewboardrc |
1116 | # user's $HOME/.reviewboardrc |
| 1111 | 1117 | ||
| 1112 | # TODO: Maybe support a server per remote later? Is that useful? |
1118 | # TODO: Maybe support a server per remote later? Is that useful? |
| 1113 | url = execute("git config --get reviewboard.url").strip() |
1119 | url = execute(["git", "config", "--get", "reviewboard.url"]).strip() |
| 1114 | if url: |
1120 | if url: |
| 1115 | return url |
1121 | return url |
| 1116 | 1122 | ||
| 1117 | if self.type == "svn": |
1123 | if self.type == "svn": |
| 1118 | # Try using the reviewboard:url property on the SVN repo, if it |
1124 | # Try using the reviewboard:url property on the SVN repo, if it |
| ... | 23 lines hidden [Expand] | ||
| 1142 | 1148 | ||
| 1143 | def make_diff(self, parent_branch, source_branch=""): |
1149 | def make_diff(self, parent_branch, source_branch=""): |
| 1144 | """ |
1150 | """ |
| 1145 | Performs a diff on a particular branch range. |
1151 | Performs a diff on a particular branch range. |
| 1146 | """ |
1152 | """ |
| 1147 | diff_lines = execute("git diff --no-color --no-prefix -r -u %s..%s" % |
1153 | diff_lines = execute(["git", "diff", "--no-color", "--no-prefix", |
| 1148 | (parent_branch, source_branch), |
1154 | "-r", "-u", "%s..%s" % (parent_branch, |
| 1155 | source_branch)], |
||
| 1149 | split_lines=True) |
1156 | split_lines=True) |
| 1150 | 1157 | ||
| 1151 | if self.type == "svn": |
1158 | if self.type == "svn": |
| 1152 | return self.make_svn_diff(parent_branch, diff_lines) |
1159 | return self.make_svn_diff(parent_branch, diff_lines) |
| 1153 | 1160 | ||
| ... | 4 lines hidden [Expand] | ||
| 1158 | """ |
1165 | """ |
| 1159 | Formats the output of git diff such that it's in a form that |
1166 | Formats the output of git diff such that it's in a form that |
| 1160 | svn diff would generate. This is needed so the SVNTool in Review |
1167 | svn diff would generate. This is needed so the SVNTool in Review |
| 1161 | Board can properly parse this diff. |
1168 | Board can properly parse this diff. |
| 1162 | """ |
1169 | """ |
| 1163 | rev = execute("git-svn find-rev master").strip() |
1170 | rev = execute(["git-svn", "find-rev", "master"]).strip() |
| 1164 | 1171 | ||
| 1165 | if not rev: |
1172 | if not rev: |
| 1166 | return None |
1173 | return None |
| 1167 | 1174 | ||
| 1168 | diff_data = "" |
1175 | diff_data = "" |
| ... | 72 lines hidden [Expand] | ||
| 1241 | return True |
1248 | return True |
| 1242 | except OSError: |
1249 | except OSError: |
| 1243 | return False |
1250 | return False |
| 1244 | 1251 | ||
| 1245 | 1252 | ||
| 1246 | def execute(command, split_lines=False, ignore_errors=False, |
1253 | def execute(command, env=None, split_lines=False, ignore_errors=False, |
| 1247 | extra_ignore_errors=()): |
1254 | extra_ignore_errors=()): |
| 1248 | """ |
1255 | """ |
| 1249 | Utility function to execute a command and return the output. |
1256 | Utility function to execute a command and return the output. |
| 1250 | """ |
1257 | """ |
| 1258 | # Internally, subprocess.Popen calls this anyway, but since we're |
||
| 1259 | # showing it on the command line, we may as well do it ourselves. |
||
| 1260 | if isinstance(command, list): |
||
| 1261 | command = subprocess.list2cmdline(command) |
||
| 1262 | |||
| 1251 | debug(command) |
1263 | debug(command) |
| 1252 | 1264 | ||
| 1253 | if sys.platform.startswith('win'): |
1265 | if sys.platform.startswith('win'): |
| 1254 | p = subprocess.Popen(command, |
1266 | p = subprocess.Popen(command, |
| 1255 | stdin=subprocess.PIPE, |
1267 | stdin=subprocess.PIPE, |
| 1256 | stdout=subprocess.PIPE, |
1268 | stdout=subprocess.PIPE, |
| 1257 | stderr=subprocess.STDOUT, |
1269 | stderr=subprocess.STDOUT, |
| 1258 | shell=True, |
1270 | shell=True, |
| 1259 | universal_newlines=True) |
1271 | universal_newlines=True, |
| 1272 | env=env) |
||
| 1260 | else: |
1273 | else: |
| 1261 | p = subprocess.Popen(command, |
1274 | p = subprocess.Popen(command, |
| 1262 | stdin=subprocess.PIPE, |
1275 | stdin=subprocess.PIPE, |
| 1263 | stdout=subprocess.PIPE, |
1276 | stdout=subprocess.PIPE, |
| 1264 | stderr=subprocess.STDOUT, |
1277 | stderr=subprocess.STDOUT, |
| 1265 | shell=True, |
1278 | shell=True, |
| 1266 | close_fds=True, |
1279 | close_fds=True, |
| 1267 | universal_newlines=True) |
1280 | universal_newlines=True, |
| 1281 | env=env) |
||
| 1268 | if split_lines: |
1282 | if split_lines: |
| 1269 | data = p.stdout.readlines() |
1283 | data = p.stdout.readlines() |
| 1270 | else: |
1284 | else: |
| 1271 | data = p.stdout.read() |
1285 | data = p.stdout.read() |
| 1272 | rc = p.wait() |
1286 | rc = p.wait() |
| ... | 263 lines hidden [Expand] | ||
| 1536 | 1550 | ||
| 1537 | if options.revision_range: |
1551 | if options.revision_range: |
| 1538 | diff = tool.diff_between_revisions(options.revision_range) |
1552 | diff = tool.diff_between_revisions(options.revision_range) |
| 1539 | parent_diff = None |
1553 | parent_diff = None |
| 1540 | else: |
1554 | else: |
| 1541 | args = ["'%s'" % arg for arg in args] # quote filenames |
||
| 1542 | diff, parent_diff = tool.diff(args) |
1555 | diff, parent_diff = tool.diff(args) |
| 1543 | 1556 | ||
| 1544 | if options.output_diff_only: |
1557 | if options.output_diff_only: |
| 1545 | print diff |
1558 | print diff |
| 1546 | sys.exit(0) |
1559 | sys.exit(0) |
| ... | 25 lines hidden [Expand] | ||
Other reviews