如何让Git HTTP推送时post-receive钩子输出实时显示?
Great question! The core issue here is that HTTP's request-response model is inherently different from SSH's persistent bidirectional connection—by default, the Git HTTP server won't send any output back to the client until the entire request (including the post-receive hook execution) is fully completed. But don't worry, there are a few workarounds to get close to that real-time feedback you're used to with SSH.
Option 1: Redirect Hook Output to a Watchable Log File
This is the simplest approach if you don't want to modify your custom Git server code. Here's how to set it up:
Modify your post-receive hook to redirect all output to a dedicated log file. Add this line at the top of your hook script (adjust paths to match your server setup):
#!/bin/bash # Define a unique log file per repository LOG_FILE="/var/git/hook-logs/$(basename "$PWD").log" # Redirect stdout and stderr to the log file (append mode) exec >> "$LOG_FILE" 2>&1 # Your existing hook logic below... echo "Starting deployment at $(date)" # Run your build/pre-release commands /path/to/your/deploy-script.shExpose the log directory via HTTP on your Git server. If you're using a web server like Nginx or Apache, add a location block to serve the log files as static content. For example, with Nginx:
location /hook-logs/ { root /var/git/; autoindex on; # Optional: let users browse logs add_header Content-Type text/plain; }Watch the log in real-time from your client using
curlandwatch(or a similar tool):watch -n 1 curl http://your-git-server/hook-logs/your-repo.logRun this command in a separate terminal before executing
git push, and you'll see the hook's output update as it runs.
Option 2: Run the Hook Asynchronously + Poll for Status
If you want the git push command to return immediately (instead of waiting for the hook to finish), you can offload the hook work to a background process, then let the client check progress separately:
Update your post-receive hook to run the deployment logic in the background:
#!/bin/bash LOG_FILE="/var/git/hook-logs/$(basename "$PWD").log" # Start the deployment script in the background, redirect output to log nohup /path/to/your/deploy-script.sh >> "$LOG_FILE" 2>&1 & # Send an immediate response back to the Git client echo "Pre-release deployment started! Track progress at: http://your-git-server/hook-logs/your-repo.log"Now when you run
git push, you'll get the confirmation message right away, and can monitor the log file as before.Optional: Add a status endpoint (if you're willing to extend your custom Git server) that checks if the background process is still running, or returns the final exit code. This lets you build a simple script to poll for completion instead of watching the log continuously.
Option 3: Use HTTP Chunked Transfer Encoding (Real-Time Output Directly in Git Push)
This is the closest you'll get to SSH's real-time behavior, but it requires modifying your custom Git server code to support chunked HTTP responses. Here's the idea:
HTTP/1.1 supports chunked transfer encoding, which allows the server to send response data in small "chunks" as it becomes available, instead of waiting for the entire request to finish. You can leverage this to stream the post-receive hook's output directly to the Git client during the push.
Example Implementation (Python/Flask)
If your custom Git server is built with Flask, here's how you might modify the git-receive-pack handler to stream hook output:
from flask import Flask, Response import subprocess import os from werkzeug.exceptions import NotFound app = Flask(__name__) GIT_REPO_BASE = "/var/git/repos" @app.route('/<repo_name>/git-receive-pack', methods=['POST']) def handle_receive_pack(repo_name): repo_path = os.path.join(GIT_REPO_BASE, repo_name) if not os.path.exists(repo_path): raise NotFound("Repository not found") # First, handle the core Git receive-pack operation (your existing logic here) # ... # Now execute the post-receive hook and stream its output hook_path = os.path.join(repo_path, "hooks", "post-receive") if os.path.isfile(hook_path) and os.access(hook_path, os.X_OK): # Start the hook process with unbuffered output proc = subprocess.Popen( [hook_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True ) # Generator function to stream chunks def stream_hook_output(): for line in iter(proc.stdout.readline, ''): # Encode line to bytes, get length in hex, format as chunk chunk_bytes = line.encode('utf-8') yield f"{len(chunk_bytes):x}\r\n{line}\r\n" # Send final empty chunk to end the stream yield "0\r\n\r\n" # Wait for the process to finish and capture exit code proc.wait() # Return chunked response return Response( stream_hook_output(), mimetype='text/plain', headers={'Transfer-Encoding': 'chunked'} ) # If no hook exists, return standard response # ... if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)
With this setup, when you run git push, you'll see the post-receive hook's output print to your terminal in real-time, just like when using SSH.
Key Notes for This Approach:
- Ensure your Git client supports chunked transfer encoding (all modern Git versions do).
- You'll need to integrate this streaming logic with your existing Git HTTP handler (the part that processes
git-receive-packrequests). - Handle errors gracefully—if the hook crashes mid-execution, make sure the stream is properly closed.
内容的提问来源于stack exchange,提问作者Kevin Chung

