Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 80eaf40

Browse filesBrowse files
committed
fix(run) Fix live / streaming / flushing of output
Live updates were broken, e.g. flushing of output during `git clone`
1 parent fdf8992 commit 80eaf40
Copy full SHA for 80eaf40

File tree

Expand file treeCollapse file tree

1 file changed

+34
-15
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+34
-15
lines changed

‎src/libvcs/_internal/run.py

Copy file name to clipboardExpand all lines: src/libvcs/_internal/run.py
+34-15Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@
2222

2323
logger = logging.getLogger(__name__)
2424

25+
console_encoding = sys.stdout.encoding
26+
27+
28+
def console_to_str(s: bytes) -> str:
29+
"""From pypa/pip project, pip.backwardwardcompat. License MIT."""
30+
try:
31+
return s.decode(console_encoding)
32+
except UnicodeDecodeError:
33+
return s.decode("utf_8")
34+
except AttributeError: # for tests, #13
35+
return str(s)
36+
2537

2638
if t.TYPE_CHECKING:
2739
_LoggerAdapter = logging.LoggerAdapter[logging.Logger]
@@ -78,7 +90,7 @@ def process(
7890
class ProgressCallbackProtocol(t.Protocol):
7991
"""Callback to report subprocess communication."""
8092

81-
def __call__(self, output: t.AnyStr, timestamp: datetime.datetime) -> None:
93+
def __call__(self, output: str, timestamp: datetime.datetime) -> None:
8294
"""Process progress for subprocess communication."""
8395
...
8496

@@ -182,7 +194,7 @@ def progress_cb(output, timestamp):
182194
restore_signals=restore_signals,
183195
start_new_session=start_new_session,
184196
pass_fds=pass_fds,
185-
text=True,
197+
text=False, # Keep in bytes mode to preserve \r properly
186198
encoding=encoding,
187199
errors=errors,
188200
user=user,
@@ -201,29 +213,36 @@ def progress_cb(output: t.AnyStr, timestamp: datetime.datetime) -> None:
201213
sys.stdout.flush()
202214

203215
callback = progress_cb
216+
217+
# Note: When git detects that stderr is not a TTY (e.g., when piped),
218+
# it outputs progress with newlines instead of carriage returns.
219+
# This causes each progress update to appear on a new line.
220+
# To get proper single-line progress updates, git would need to be
221+
# connected to a pseudo-TTY, which would require significant changes
222+
# to how subprocess execution is handled.
223+
204224
while code is None:
205225
code = proc.poll()
206226

207227
if callback and callable(callback) and proc.stderr is not None:
208-
line = str(proc.stderr.read(128))
228+
line = console_to_str(proc.stderr.read(128))
209229
if line:
210230
callback(output=line, timestamp=datetime.datetime.now())
211231
if callback and callable(callback):
212232
callback(output="\r", timestamp=datetime.datetime.now())
213233

214-
lines = (
215-
filter(None, (line.strip() for line in proc.stdout.readlines()))
216-
if proc.stdout is not None
217-
else []
218-
)
219-
all_output = "\n".join(lines)
220-
if code:
221-
stderr_lines = (
222-
filter(None, (line.strip() for line in proc.stderr.readlines()))
223-
if proc.stderr is not None
224-
else []
234+
if proc.stdout is not None:
235+
lines: t.Iterable[bytes] = filter(
236+
None, (line.strip() for line in proc.stdout.readlines())
237+
)
238+
all_output = console_to_str(b"\n".join(lines))
239+
else:
240+
all_output = ""
241+
if code and proc.stderr is not None:
242+
stderr_lines: t.Iterable[bytes] = filter(
243+
None, (line.strip() for line in proc.stderr.readlines())
225244
)
226-
all_output = "".join(stderr_lines)
245+
all_output = console_to_str(b"".join(stderr_lines))
227246
output = "".join(all_output)
228247
if code != 0 and check_returncode:
229248
raise exc.CommandError(

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.