blob: 9a4da192e09e6b2081bf527a7e5ece927dd82825 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import os
16import select
Renaud Paquaye8595e92016-11-01 15:51:59 -070017import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import sys
19
Renaud Paquaye8595e92016-11-01 15:51:59 -070020import platform_utils
21
Mike Frysinger64477332023-08-21 21:20:32 -040022
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070023active = False
Renaud Paquaye8595e92016-11-01 15:51:59 -070024pager_process = None
25old_stdout = None
26old_stderr = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070027
David Pursehouse819827a2020-02-12 15:20:19 +090028
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070029def RunPager(globalConfig):
Gavin Makea2e3302023-03-11 06:46:20 +000030 if not os.isatty(0) or not os.isatty(1):
31 return
32 pager = _SelectPager(globalConfig)
33 if pager == "" or pager == "cat":
34 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070035
Gavin Makea2e3302023-03-11 06:46:20 +000036 if platform_utils.isWindows():
37 _PipePager(pager)
38 else:
39 _ForkPager(pager)
Renaud Paquaye8595e92016-11-01 15:51:59 -070040
David Pursehouse819827a2020-02-12 15:20:19 +090041
Renaud Paquaye8595e92016-11-01 15:51:59 -070042def TerminatePager():
Gavin Makea2e3302023-03-11 06:46:20 +000043 global pager_process, old_stdout, old_stderr
44 if pager_process:
45 sys.stdout.flush()
46 sys.stderr.flush()
47 pager_process.stdin.close()
48 pager_process.wait()
49 pager_process = None
50 # Restore initial stdout/err in case there is more output in this
51 # process after shutting down the pager process.
52 sys.stdout = old_stdout
53 sys.stderr = old_stderr
Renaud Paquaye8595e92016-11-01 15:51:59 -070054
David Pursehouse819827a2020-02-12 15:20:19 +090055
Renaud Paquaye8595e92016-11-01 15:51:59 -070056def _PipePager(pager):
Gavin Makea2e3302023-03-11 06:46:20 +000057 global pager_process, old_stdout, old_stderr
58 assert pager_process is None, "Only one active pager process at a time"
59 # Create pager process, piping stdout/err into its stdin.
60 try:
61 pager_process = subprocess.Popen(
62 [pager], stdin=subprocess.PIPE, stdout=sys.stdout, stderr=sys.stderr
63 )
64 except FileNotFoundError:
65 sys.exit(f'fatal: cannot start pager "{pager}"')
66 old_stdout = sys.stdout
67 old_stderr = sys.stderr
68 sys.stdout = pager_process.stdin
69 sys.stderr = pager_process.stdin
Renaud Paquaye8595e92016-11-01 15:51:59 -070070
David Pursehouse819827a2020-02-12 15:20:19 +090071
Renaud Paquaye8595e92016-11-01 15:51:59 -070072def _ForkPager(pager):
Gavin Makea2e3302023-03-11 06:46:20 +000073 global active
74 # This process turns into the pager; a child it forks will
75 # do the real processing and output back to the pager. This
76 # is necessary to keep the pager in control of the tty.
77 try:
78 r, w = os.pipe()
79 pid = os.fork()
80 if not pid:
81 os.dup2(w, 1)
82 os.dup2(w, 2)
83 os.close(r)
84 os.close(w)
85 active = True
86 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070087
Gavin Makea2e3302023-03-11 06:46:20 +000088 os.dup2(r, 0)
89 os.close(r)
90 os.close(w)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070091
Gavin Makea2e3302023-03-11 06:46:20 +000092 _BecomePager(pager)
93 except Exception:
94 print("fatal: cannot start pager '%s'" % pager, file=sys.stderr)
95 sys.exit(255)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070096
David Pursehouse819827a2020-02-12 15:20:19 +090097
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070098def _SelectPager(globalConfig):
Gavin Makea2e3302023-03-11 06:46:20 +000099 try:
100 return os.environ["GIT_PAGER"]
101 except KeyError:
102 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700103
Gavin Makea2e3302023-03-11 06:46:20 +0000104 pager = globalConfig.GetString("core.pager")
105 if pager:
106 return pager
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700107
Gavin Makea2e3302023-03-11 06:46:20 +0000108 try:
109 return os.environ["PAGER"]
110 except KeyError:
111 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700112
Gavin Makea2e3302023-03-11 06:46:20 +0000113 return "less"
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700114
David Pursehouse819827a2020-02-12 15:20:19 +0900115
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700116def _BecomePager(pager):
Gavin Makea2e3302023-03-11 06:46:20 +0000117 # Delaying execution of the pager until we have output
118 # ready works around a long-standing bug in popularly
119 # available versions of 'less', a better 'more'.
120 _a, _b, _c = select.select([0], [], [0])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700121
Chih-Hsuan Yen07a45292023-05-24 22:32:23 +0800122 # This matches the behavior of git, which sets $LESS to `FRX` if it is not
123 # set. See:
124 # https://git-scm.com/docs/git-config#Documentation/git-config.txt-corepager
125 os.environ.setdefault("LESS", "FRX")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700126
Gavin Makea2e3302023-03-11 06:46:20 +0000127 try:
128 os.execvp(pager, [pager])
129 except OSError:
130 os.execv("/bin/sh", ["sh", "-c", pager])