-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcmd_commit.py
More file actions
241 lines (210 loc) · 7.11 KB
/
Copy pathcmd_commit.py
File metadata and controls
241 lines (210 loc) · 7.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
"""Handler for 'git commit' command.
Supported flags:
- SAFE-01: -a/--all -> REMOVED (dangerous semantic difference)
- COMM-01: --amend -> sl amend command
- COMM-02: --no-edit -> omit -e with amend (default sl amend behavior)
- COMM-03: -F/--file -> -l (logfile)
- COMM-04: --author -> -u (user)
- COMM-05: --date -> -d (date)
- COMM-06: -v/--verbose -> warning (different semantics)
- COMM-07: -s/--signoff -> custom trailer implementation
- COMM-08: -n/--no-verify -> warning (not supported)
"""
import subprocess
import sys
import tempfile
import os
from common import ParsedCommand, run_sl
def get_user_identity() -> str:
"""Get user identity from sl config for signoff trailer."""
result = subprocess.run(
['sl', 'config', 'ui.username'],
capture_output=True, text=True
)
return result.stdout.strip() or "Unknown User <unknown@example.com>"
def add_signoff_trailer(message: str, identity: str) -> str:
"""Append Signed-off-by trailer to message if not already present."""
trailer = f"Signed-off-by: {identity}"
if trailer not in message:
return message.rstrip() + "\n\n" + trailer
return message
def handle(parsed: ParsedCommand) -> int:
"""
Handle 'git commit' command.
Translates to 'sl commit' or 'sl amend' and passes through arguments.
"""
args = list(parsed.args)
sl_args = []
remaining_args = []
# Extract flags
amend = False
no_edit = False
signoff = False
verbose = False
no_verify = False
message = None
message_file = None
author = None
date_value = None
i = 0
while i < len(args):
arg = args[i]
# SAFE-01: Skip -a/--all flags (dangerous semantic difference)
if arg in ('-a', '--all'):
i += 1
continue
# COMM-01: --amend
if arg == '--amend':
amend = True
i += 1
continue
# COMM-02: --no-edit
if arg == '--no-edit':
no_edit = True
i += 1
continue
# COMM-06: -v/--verbose
if arg in ('-v', '--verbose'):
verbose = True
i += 1
continue
# COMM-07: -s/--signoff
if arg in ('-s', '--signoff'):
signoff = True
i += 1
continue
# COMM-08: -n/--no-verify
if arg in ('-n', '--no-verify'):
no_verify = True
i += 1
continue
# -m/--message handling (need to capture for signoff)
if arg == '-m':
if i + 1 < len(args):
message = args[i + 1]
i += 2
continue
elif arg.startswith('-m'):
# -m"message" format (attached)
message = arg[2:]
i += 1
continue
elif arg == '--message':
if i + 1 < len(args):
message = args[i + 1]
i += 2
continue
elif arg.startswith('--message='):
message = arg.split('=', 1)[1]
i += 1
continue
# COMM-03: -F/--file
if arg in ('-F', '--file'):
if i + 1 < len(args):
message_file = args[i + 1]
i += 2
continue
elif arg.startswith('-F'):
# -F<file> format (attached)
message_file = arg[2:]
i += 1
continue
elif arg.startswith('--file='):
message_file = arg.split('=', 1)[1]
i += 1
continue
# COMM-04: --author
if arg == '--author':
if i + 1 < len(args):
author = args[i + 1]
i += 2
continue
elif arg.startswith('--author='):
author = arg.split('=', 1)[1]
i += 1
continue
# COMM-05: --date
if arg == '--date':
if i + 1 < len(args):
date_value = args[i + 1]
i += 2
continue
elif arg.startswith('--date='):
date_value = arg.split('=', 1)[1]
i += 1
continue
# Pass through other args
remaining_args.append(arg)
i += 1
# Print warnings for unsupported flags
if verbose:
print("Note: -v/--verbose in git shows diff in editor. "
"Sapling -v shows repository info. Proceeding without -v.",
file=sys.stderr)
if no_verify:
print("Warning: --no-verify not directly supported. "
"Sapling has no native hook bypass. "
"Pre-commit hooks will still run.",
file=sys.stderr)
# Build sl command
if amend:
sl_args.append('amend')
# sl amend reuses message by default (like git --no-edit)
# Add -e to match git's default (open editor) unless:
# - --no-edit is present, OR
# - a message is provided via -m or -F (no editor needed)
if not no_edit and not message and not message_file:
sl_args.append('-e')
else:
sl_args.append('commit')
# Handle signoff
temp_file = None
if signoff:
identity = get_user_identity()
if message:
# Add signoff to message
message = add_signoff_trailer(message, identity)
elif message_file:
# Read file, add signoff, write to temp file
try:
with open(message_file, 'r') as f:
file_content = f.read()
new_content = add_signoff_trailer(file_content, identity)
temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False)
temp_file.write(new_content)
temp_file.close()
message_file = temp_file.name
except Exception as e:
print(f"Error reading message file: {e}", file=sys.stderr)
return 1
else:
# No -m or -F, add signoff trailer via temp file for interactive editor
identity = get_user_identity()
trailer = f"\n\nSigned-off-by: {identity}"
temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False)
temp_file.write(trailer)
temp_file.close()
# For interactive mode, we append the trailer via a template approach
# Use -l with a temp file that just has the trailer, then -e to edit
message_file = temp_file.name
# Force editor mode so user can add their message above the trailer
if amend and '-e' not in sl_args:
sl_args.append('-e')
# Add message or file
if message:
sl_args.extend(['-m', message])
elif message_file:
sl_args.extend(['-l', message_file])
# COMM-04: --author -> -u
if author:
sl_args.extend(['-u', author])
# COMM-05: --date -> -d
if date_value:
sl_args.extend(['-d', date_value])
sl_args.extend(remaining_args)
try:
return run_sl(sl_args)
finally:
# Cleanup temp file if created
if temp_file and os.path.exists(temp_file.name):
os.unlink(temp_file.name)