@stream44.studio/dco 0.3.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dco-signatures +9 -0
- package/.github/workflows/dco.yml +12 -0
- package/.o/GordianOpenIntegrity-CurrentLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity-InceptionLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity.yaml +25 -0
- package/DCO.md +34 -0
- package/README.md +122 -0
- package/action.yml +32 -0
- package/caps/Dco.test.ts +288 -0
- package/caps/Dco.ts +269 -0
- package/commit.sh +468 -0
- package/dco.sh +49 -0
- package/examples/01-Lifecycle/main.test.ts +223 -0
- package/package.json +39 -0
- package/test.sh +422 -0
- package/tsconfig.json +28 -0
- package/validate.sh +353 -0
package/commit.sh
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# DCO-enabled Git Commit Wrapper
|
|
4
|
+
# ===============================
|
|
5
|
+
# This script wraps `git commit` to automatically add --signoff
|
|
6
|
+
# and shows the DCO terms on first use.
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
|
|
11
|
+
# Color codes for better UX
|
|
12
|
+
readonly RED='\033[0;31m'
|
|
13
|
+
readonly GREEN='\033[0;32m'
|
|
14
|
+
readonly YELLOW='\033[1;33m'
|
|
15
|
+
readonly BLUE='\033[0;34m'
|
|
16
|
+
readonly CYAN='\033[0;36m'
|
|
17
|
+
readonly MAGENTA='\033[0;35m'
|
|
18
|
+
readonly NC='\033[0m' # No Color
|
|
19
|
+
readonly BOLD='\033[1m'
|
|
20
|
+
|
|
21
|
+
# Verbose logging
|
|
22
|
+
VERBOSE=false
|
|
23
|
+
|
|
24
|
+
# SSH signing key path (optional, set via --signing-key)
|
|
25
|
+
SIGNING_KEY=""
|
|
26
|
+
|
|
27
|
+
# Build git commit args with optional SSH signing
|
|
28
|
+
git_commit_with_sign() {
|
|
29
|
+
local sign_args=()
|
|
30
|
+
if [[ -n "$SIGNING_KEY" ]]; then
|
|
31
|
+
sign_args+=(-c 'gpg.format=ssh' -c "user.signingkey=$SIGNING_KEY")
|
|
32
|
+
fi
|
|
33
|
+
git "${sign_args[@]}" commit "$@"
|
|
34
|
+
}
|
|
35
|
+
verbose_log() {
|
|
36
|
+
if [[ "$VERBOSE" == "true" ]]; then
|
|
37
|
+
echo -e "${BLUE}[verbose]${NC} $*" >&2
|
|
38
|
+
fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Resolve package version from package.json next to this script
|
|
42
|
+
get_package_version() {
|
|
43
|
+
local script_dir
|
|
44
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
45
|
+
if [[ -f "$script_dir/package.json" ]]; then
|
|
46
|
+
sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$script_dir/package.json" | head -1
|
|
47
|
+
else
|
|
48
|
+
echo "unknown"
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
readonly DCO_VERSION=$(get_package_version)
|
|
52
|
+
|
|
53
|
+
# Find the git repository root
|
|
54
|
+
find_git_root() {
|
|
55
|
+
local dir
|
|
56
|
+
dir=$(git rev-parse --show-toplevel 2>/dev/null) || {
|
|
57
|
+
echo -e "${RED}Error: Not in a git repository${NC}" >&2
|
|
58
|
+
exit 1
|
|
59
|
+
}
|
|
60
|
+
echo "$dir"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Record DCO signature in git tree for permanent record
|
|
64
|
+
record_dco_signature() {
|
|
65
|
+
local git_root="$1"
|
|
66
|
+
local name="$2"
|
|
67
|
+
local email="$3"
|
|
68
|
+
local date="$4"
|
|
69
|
+
local agreement_commit="$5"
|
|
70
|
+
local agreement_change_date="$6"
|
|
71
|
+
local sig_file="$git_root/.dco-signatures"
|
|
72
|
+
|
|
73
|
+
# Create or append to the signatures file
|
|
74
|
+
if [[ ! -f "$sig_file" ]]; then
|
|
75
|
+
cat > "$sig_file" <<EOF
|
|
76
|
+
# DCO Signatures
|
|
77
|
+
|
|
78
|
+
This file contains a permanent record of all Developer Certificate of Origin (DCO) agreements.
|
|
79
|
+
Each contributor who has agreed to the DCO.md is listed below with their signing date.
|
|
80
|
+
Format: name <email> | signed: <date> | agreement: <commit> (<agreement_change_date>) [| signature: <key_fingerprint>]
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
EOF
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Append the new signature (single line with all info)
|
|
88
|
+
local sig_line="$name <$email> | signed: $date | agreement: $agreement_commit ($agreement_change_date)"
|
|
89
|
+
if [[ -n "$SIGNING_KEY" ]]; then
|
|
90
|
+
local fingerprint
|
|
91
|
+
fingerprint=$(ssh-keygen -lf "$SIGNING_KEY" 2>/dev/null | awk '{print $2}')
|
|
92
|
+
if [[ -n "$fingerprint" ]]; then
|
|
93
|
+
sig_line="$sig_line | signature: $fingerprint"
|
|
94
|
+
fi
|
|
95
|
+
fi
|
|
96
|
+
echo "$sig_line" >> "$sig_file"
|
|
97
|
+
|
|
98
|
+
# Commit the signature to git
|
|
99
|
+
# Save list of currently staged files (excluding .dco-signatures)
|
|
100
|
+
local staged_files
|
|
101
|
+
staged_files=$(git diff --cached --name-only --diff-filter=ACMR 2>/dev/null | grep -v "^\.dco-signatures$" || true)
|
|
102
|
+
verbose_log "record_dco_signature: staged files to preserve: $(echo "$staged_files" | tr '\n' ' ')"
|
|
103
|
+
|
|
104
|
+
# Unstage everything
|
|
105
|
+
git reset >/dev/null 2>&1 || true
|
|
106
|
+
|
|
107
|
+
# Stage ONLY the signature file
|
|
108
|
+
git add "$sig_file" 2>/dev/null || true
|
|
109
|
+
|
|
110
|
+
# Create a commit for the DCO signature (should only contain .dco-signatures)
|
|
111
|
+
local sign_args=()
|
|
112
|
+
if [[ -n "$SIGNING_KEY" ]]; then
|
|
113
|
+
sign_args+=(--gpg-sign)
|
|
114
|
+
fi
|
|
115
|
+
if git_commit_with_sign --signoff --no-verify "${sign_args[@]}" -m "[DCO] DCO.md signed by $name" -m "Developer Certificates of Origin established using https://github.com/Stream44/dco" >/dev/null 2>&1; then
|
|
116
|
+
echo -e "${GREEN}✓ Signature recorded in repository history${NC}"
|
|
117
|
+
else
|
|
118
|
+
echo -e "${YELLOW}Note: Signature file already up to date${NC}"
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# Restore the originally staged files
|
|
122
|
+
if [[ -n "$staged_files" ]]; then
|
|
123
|
+
echo "$staged_files" | while IFS= read -r file; do
|
|
124
|
+
git add "$file" 2>/dev/null || true
|
|
125
|
+
done
|
|
126
|
+
fi
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Show the DCO and get user agreement
|
|
130
|
+
show_dco_first_time() {
|
|
131
|
+
local git_root="$1"
|
|
132
|
+
local auto_agree="$2"
|
|
133
|
+
local dco_file="$git_root/DCO.md"
|
|
134
|
+
local marker_file="$git_root/.git/.dco-agreed"
|
|
135
|
+
|
|
136
|
+
# Check if user has already agreed
|
|
137
|
+
if [[ -f "$marker_file" ]]; then
|
|
138
|
+
# Verify the current git user matches the one who signed
|
|
139
|
+
local current_name current_email
|
|
140
|
+
current_name=$(git config user.name 2>/dev/null || echo "")
|
|
141
|
+
current_email=$(git config user.email 2>/dev/null || echo "")
|
|
142
|
+
|
|
143
|
+
local stored_name stored_email stored_date stored_agreement_commit stored_agreement_change_date
|
|
144
|
+
stored_name=$(grep "^name=" "$marker_file" | cut -d'=' -f2-)
|
|
145
|
+
stored_email=$(grep "^email=" "$marker_file" | cut -d'=' -f2-)
|
|
146
|
+
stored_date=$(grep "^date=" "$marker_file" | cut -d'=' -f2-)
|
|
147
|
+
stored_agreement_commit=$(grep "^agreement_commit=" "$marker_file" | cut -d'=' -f2-)
|
|
148
|
+
stored_agreement_change_date=$(grep "^agreement_change_date=" "$marker_file" | cut -d'=' -f2-)
|
|
149
|
+
|
|
150
|
+
if [[ "$current_name" != "$stored_name" ]] || [[ "$current_email" != "$stored_email" ]]; then
|
|
151
|
+
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" >&2
|
|
152
|
+
echo -e "${RED}${BOLD} DCO Identity Mismatch${NC}" >&2
|
|
153
|
+
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" >&2
|
|
154
|
+
echo >&2
|
|
155
|
+
echo -e "${YELLOW}The DCO was originally signed by:${NC}" >&2
|
|
156
|
+
echo -e " Name: ${CYAN}$stored_name${NC}" >&2
|
|
157
|
+
echo -e " Email: ${CYAN}$stored_email${NC}" >&2
|
|
158
|
+
echo >&2
|
|
159
|
+
echo -e "${YELLOW}But you are currently configured as:${NC}" >&2
|
|
160
|
+
echo -e " Name: ${CYAN}$current_name${NC}" >&2
|
|
161
|
+
echo -e " Email: ${CYAN}$current_email${NC}" >&2
|
|
162
|
+
echo >&2
|
|
163
|
+
echo -e "${BLUE}To sign with a different identity, please remove the DCO agreement file:${NC}" >&2
|
|
164
|
+
echo -e " ${BOLD}rm $marker_file${NC}" >&2
|
|
165
|
+
echo >&2
|
|
166
|
+
echo -e "${BLUE}Then run this script again to sign the DCO with your current identity.${NC}" >&2
|
|
167
|
+
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" >&2
|
|
168
|
+
exit 1
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# Verify the signature exists in .dco-signatures file
|
|
172
|
+
local sig_file="$git_root/.dco-signatures"
|
|
173
|
+
local sig_valid="false"
|
|
174
|
+
if [[ -f "$sig_file" ]]; then
|
|
175
|
+
local sig_line
|
|
176
|
+
sig_line=$(grep "$stored_name.*<$stored_email>" "$sig_file" || true)
|
|
177
|
+
if [[ -n "$sig_line" ]]; then
|
|
178
|
+
sig_valid="true"
|
|
179
|
+
# Re-create marker from signature line if marker data is stale/empty
|
|
180
|
+
if [[ -z "$stored_agreement_commit" ]]; then
|
|
181
|
+
verbose_log "Marker missing agreement data, restoring from .dco-signatures"
|
|
182
|
+
local restored_agreement_commit restored_agreement_change_date restored_signed_date
|
|
183
|
+
restored_agreement_commit=$(echo "$sig_line" | sed -n 's/.*| agreement: \([a-f0-9]*\).*/\1/p')
|
|
184
|
+
restored_agreement_change_date=$(echo "$sig_line" | sed -n 's/.*| agreement: [a-f0-9]* (\(.*\))/\1/p')
|
|
185
|
+
restored_signed_date=$(echo "$sig_line" | sed -n 's/.*| signed: \([^|]*\) |.*/\1/p')
|
|
186
|
+
cat > "$marker_file" <<EOF
|
|
187
|
+
name=$stored_name
|
|
188
|
+
email=$stored_email
|
|
189
|
+
date=$restored_signed_date
|
|
190
|
+
agreement_commit=$restored_agreement_commit
|
|
191
|
+
agreement_change_date=$restored_agreement_change_date
|
|
192
|
+
EOF
|
|
193
|
+
stored_date="$restored_signed_date"
|
|
194
|
+
stored_agreement_commit="$restored_agreement_commit"
|
|
195
|
+
stored_agreement_change_date="$restored_agreement_change_date"
|
|
196
|
+
echo -e "${GREEN}✓ DCO marker restored from existing signature${NC}"
|
|
197
|
+
fi
|
|
198
|
+
else
|
|
199
|
+
verbose_log "Signature not found in .dco-signatures, will re-sign"
|
|
200
|
+
fi
|
|
201
|
+
else
|
|
202
|
+
verbose_log ".dco-signatures missing, will re-sign"
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
if [[ "$sig_valid" == "false" ]]; then
|
|
206
|
+
# Auto-recover: remove stale marker and fall through to re-sign
|
|
207
|
+
echo -e "${YELLOW}DCO signature record missing or out of sync. Re-signing...${NC}"
|
|
208
|
+
rm -f "$marker_file"
|
|
209
|
+
# Fall through to the signing flow below
|
|
210
|
+
else
|
|
211
|
+
|
|
212
|
+
# Already signed - display details
|
|
213
|
+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
214
|
+
echo -e "${GREEN}${BOLD} DCO Already Signed${NC}"
|
|
215
|
+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
216
|
+
echo
|
|
217
|
+
echo -e " ${BOLD}Signer:${NC} $stored_name <$stored_email>"
|
|
218
|
+
echo -e " ${BOLD}Signed:${NC} $stored_date"
|
|
219
|
+
if [[ -n "$stored_agreement_commit" ]]; then
|
|
220
|
+
echo -e " ${BOLD}Agreement commit:${NC} $(git rev-parse --short "$stored_agreement_commit" 2>/dev/null || echo "$stored_agreement_commit")"
|
|
221
|
+
fi
|
|
222
|
+
if [[ -n "$stored_agreement_change_date" ]]; then
|
|
223
|
+
echo -e " ${BOLD}Agreement date:${NC} $stored_agreement_change_date"
|
|
224
|
+
fi
|
|
225
|
+
echo
|
|
226
|
+
return 0
|
|
227
|
+
fi
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# No marker file — check if .dco-signatures already has this user's signature
|
|
231
|
+
local sig_file="$git_root/.dco-signatures"
|
|
232
|
+
if [[ -f "$sig_file" ]]; then
|
|
233
|
+
local current_name current_email
|
|
234
|
+
current_name=$(git config user.name 2>/dev/null || echo "")
|
|
235
|
+
current_email=$(git config user.email 2>/dev/null || echo "")
|
|
236
|
+
local existing_sig
|
|
237
|
+
existing_sig=$(grep "$current_name.*<$current_email>" "$sig_file" || true)
|
|
238
|
+
if [[ -n "$existing_sig" ]]; then
|
|
239
|
+
verbose_log "Found existing signature in .dco-signatures, restoring marker"
|
|
240
|
+
local restored_agreement_commit restored_agreement_change_date restored_signed_date
|
|
241
|
+
restored_agreement_commit=$(echo "$existing_sig" | sed -n 's/.*| agreement: \([a-f0-9]*\).*/\1/p')
|
|
242
|
+
restored_agreement_change_date=$(echo "$existing_sig" | sed -n 's/.*| agreement: [a-f0-9]* (\(.*\))/\1/p')
|
|
243
|
+
restored_signed_date=$(echo "$existing_sig" | sed -n 's/.*| signed: \([^|]*\) |.*/\1/p')
|
|
244
|
+
cat > "$marker_file" <<EOF
|
|
245
|
+
name=$current_name
|
|
246
|
+
email=$current_email
|
|
247
|
+
date=$restored_signed_date
|
|
248
|
+
agreement_commit=$restored_agreement_commit
|
|
249
|
+
agreement_change_date=$restored_agreement_change_date
|
|
250
|
+
EOF
|
|
251
|
+
echo -e "${GREEN}✓ DCO marker restored from existing signature${NC}"
|
|
252
|
+
return 0
|
|
253
|
+
fi
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
# Check if DCO.md exists on disk
|
|
257
|
+
if [[ ! -f "$dco_file" ]]; then
|
|
258
|
+
echo -e "${RED}Error: DCO.md not found in repository root${NC}" >&2
|
|
259
|
+
echo -e "${RED}Cannot proceed without DCO file${NC}" >&2
|
|
260
|
+
exit 1
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
# Check if DCO.md is committed to git; if not, commit it automatically
|
|
264
|
+
local has_dco_commits="false"
|
|
265
|
+
# git log fails on repos with no commits at all, so handle that
|
|
266
|
+
if git rev-parse HEAD >/dev/null 2>&1; then
|
|
267
|
+
# Repo has at least one commit - check if DCO.md is tracked
|
|
268
|
+
if [[ -n "$(git log --oneline -1 -- "$dco_file" 2>/dev/null)" ]]; then
|
|
269
|
+
has_dco_commits="true"
|
|
270
|
+
fi
|
|
271
|
+
fi
|
|
272
|
+
verbose_log "DCO.md has commits: $has_dco_commits"
|
|
273
|
+
|
|
274
|
+
if [[ "$has_dco_commits" == "false" ]]; then
|
|
275
|
+
echo -e "${YELLOW}DCO.md is not yet committed to git. Committing it now...${NC}"
|
|
276
|
+
|
|
277
|
+
# Save currently staged files (excluding DCO.md)
|
|
278
|
+
local staged_files
|
|
279
|
+
staged_files=$(git diff --cached --name-only --diff-filter=ACMR 2>/dev/null | grep -v "^DCO\.md$" || true)
|
|
280
|
+
verbose_log "Staged files to preserve: $(echo "$staged_files" | tr '\n' ' ')"
|
|
281
|
+
|
|
282
|
+
# Unstage everything (may fail on empty repos, that's ok)
|
|
283
|
+
if git rev-parse HEAD >/dev/null 2>&1; then
|
|
284
|
+
git reset >/dev/null 2>&1 || true
|
|
285
|
+
else
|
|
286
|
+
# Empty repo: unstage individually
|
|
287
|
+
verbose_log "Empty repo detected, unstaging files individually"
|
|
288
|
+
if [[ -n "$staged_files" ]]; then
|
|
289
|
+
echo "$staged_files" | while IFS= read -r file; do
|
|
290
|
+
git rm --cached "$file" >/dev/null 2>&1 || true
|
|
291
|
+
done
|
|
292
|
+
fi
|
|
293
|
+
fi
|
|
294
|
+
|
|
295
|
+
# Stage and commit only DCO.md
|
|
296
|
+
git add "$dco_file"
|
|
297
|
+
verbose_log "Staging DCO.md for commit"
|
|
298
|
+
local sign_args=()
|
|
299
|
+
if [[ -n "$SIGNING_KEY" ]]; then
|
|
300
|
+
sign_args+=(--gpg-sign)
|
|
301
|
+
fi
|
|
302
|
+
if git_commit_with_sign --signoff --no-verify "${sign_args[@]}" -m "[DCO] Set DCO.md Policy by $(git config user.name)" -m "Developer Certificates of Origin established using https://github.com/Stream44/dco" >/dev/null 2>&1; then
|
|
303
|
+
echo -e "${GREEN}✓ DCO.md committed to repository${NC}"
|
|
304
|
+
else
|
|
305
|
+
echo -e "${RED}Error: Failed to commit DCO.md${NC}" >&2
|
|
306
|
+
exit 1
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
# Restore originally staged files
|
|
310
|
+
if [[ -n "$staged_files" ]]; then
|
|
311
|
+
verbose_log "Restoring staged files"
|
|
312
|
+
echo "$staged_files" | while IFS= read -r file; do
|
|
313
|
+
git add "$file" 2>/dev/null || true
|
|
314
|
+
done
|
|
315
|
+
fi
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
# Display the DCO
|
|
319
|
+
echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
320
|
+
echo -e "${BOLD}${CYAN} DEVELOPER CERTIFICATE OF ORIGIN (DCO)${NC} - tools version ${CYAN}${DCO_VERSION}${NC}"
|
|
321
|
+
echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
322
|
+
echo
|
|
323
|
+
echo -e "${BLUE}This is your first commit to this repository.${NC}"
|
|
324
|
+
echo -e "${BLUE}Please read and agree to the Developer Certificate of Origin below.${NC}"
|
|
325
|
+
echo
|
|
326
|
+
|
|
327
|
+
# Ask if ready to review (unless auto-agreeing)
|
|
328
|
+
if [[ "$auto_agree" != "true" ]]; then
|
|
329
|
+
while true; do
|
|
330
|
+
echo -e -n "${YELLOW}Are you ready to review the DCO? (yes/no): ${NC}"
|
|
331
|
+
read -r ready_response
|
|
332
|
+
case "$ready_response" in
|
|
333
|
+
[Yy]es|[Yy])
|
|
334
|
+
echo
|
|
335
|
+
break
|
|
336
|
+
;;
|
|
337
|
+
[Nn]o|[Nn])
|
|
338
|
+
echo -e "${RED}✗ DCO review required to commit${NC}" >&2
|
|
339
|
+
exit 1
|
|
340
|
+
;;
|
|
341
|
+
*)
|
|
342
|
+
echo -e "${RED}Please answer 'yes' or 'no'${NC}"
|
|
343
|
+
;;
|
|
344
|
+
esac
|
|
345
|
+
done
|
|
346
|
+
else
|
|
347
|
+
echo -e "${GREEN}Auto-agreeing to DCO (--yes-signoff)${NC}"
|
|
348
|
+
echo
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
cat "$dco_file"
|
|
352
|
+
echo
|
|
353
|
+
echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
354
|
+
echo
|
|
355
|
+
echo -e "${MAGENTA}NOTE: You will only be asked to agree once and all future commits will be signed off automatically.${NC}"
|
|
356
|
+
echo
|
|
357
|
+
|
|
358
|
+
# Show who is signing
|
|
359
|
+
local git_name git_email
|
|
360
|
+
git_name=$(git config user.name 2>/dev/null || echo "")
|
|
361
|
+
git_email=$(git config user.email 2>/dev/null || echo "")
|
|
362
|
+
|
|
363
|
+
if [[ -n "$git_name" ]] && [[ -n "$git_email" ]]; then
|
|
364
|
+
echo -e "${BLUE}You are signing as:${NC}"
|
|
365
|
+
echo -e " ${BOLD}$git_name <$git_email>${NC}"
|
|
366
|
+
if [[ -n "$SIGNING_KEY" ]]; then
|
|
367
|
+
local key_fingerprint
|
|
368
|
+
key_fingerprint=$(ssh-keygen -lf "$SIGNING_KEY" 2>/dev/null | awk '{print $2}')
|
|
369
|
+
echo -e " ${BOLD}Signing key:${NC} ${CYAN}${key_fingerprint}${NC}"
|
|
370
|
+
echo -e " ${BOLD}Key path:${NC} ${CYAN}${SIGNING_KEY}${NC}"
|
|
371
|
+
fi
|
|
372
|
+
echo
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
# Ask for agreement (unless auto-agreeing)
|
|
376
|
+
if [[ "$auto_agree" != "true" ]]; then
|
|
377
|
+
while true; do
|
|
378
|
+
echo -e -n "${YELLOW}Do you agree to the DCO terms above? (yes/no): ${NC}"
|
|
379
|
+
read -r response
|
|
380
|
+
case "$response" in
|
|
381
|
+
[Yy]es|[Yy])
|
|
382
|
+
break
|
|
383
|
+
;;
|
|
384
|
+
[Nn]o|[Nn])
|
|
385
|
+
echo -e "${RED}✗ DCO agreement required to commit${NC}" >&2
|
|
386
|
+
exit 1
|
|
387
|
+
;;
|
|
388
|
+
*)
|
|
389
|
+
echo -e "${RED}Please answer 'yes' or 'no'${NC}"
|
|
390
|
+
;;
|
|
391
|
+
esac
|
|
392
|
+
done
|
|
393
|
+
fi
|
|
394
|
+
|
|
395
|
+
# Record the agreement
|
|
396
|
+
echo -e "${GREEN}✓ DCO agreement accepted${NC}"
|
|
397
|
+
local git_name git_email sign_date agreement_commit agreement_change_date
|
|
398
|
+
git_name=$(git config user.name 2>/dev/null || echo "")
|
|
399
|
+
git_email=$(git config user.email 2>/dev/null || echo "")
|
|
400
|
+
sign_date=$(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
|
401
|
+
|
|
402
|
+
# Get the last commit that changed DCO.md and its date
|
|
403
|
+
agreement_commit=$(git log -1 --format='%H' -- "$dco_file")
|
|
404
|
+
agreement_change_date=$(git log -1 --format='%ai' -- "$dco_file")
|
|
405
|
+
|
|
406
|
+
cat > "$marker_file" <<EOF
|
|
407
|
+
name=$git_name
|
|
408
|
+
email=$git_email
|
|
409
|
+
date=$sign_date
|
|
410
|
+
agreement_commit=$agreement_commit
|
|
411
|
+
agreement_change_date=$agreement_change_date
|
|
412
|
+
EOF
|
|
413
|
+
|
|
414
|
+
# Record the DCO signature in the git tree for permanent record
|
|
415
|
+
record_dco_signature "$git_root" "$git_name" "$git_email" "$sign_date" "$agreement_commit" "$agreement_change_date"
|
|
416
|
+
|
|
417
|
+
echo
|
|
418
|
+
return 0
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
# Main function
|
|
422
|
+
main() {
|
|
423
|
+
local git_root
|
|
424
|
+
git_root=$(find_git_root)
|
|
425
|
+
|
|
426
|
+
# Parse flags; collect remaining args for git commit
|
|
427
|
+
local auto_agree="false"
|
|
428
|
+
local git_args=()
|
|
429
|
+
|
|
430
|
+
while [[ $# -gt 0 ]]; do
|
|
431
|
+
case "$1" in
|
|
432
|
+
--yes-signoff)
|
|
433
|
+
auto_agree="true"
|
|
434
|
+
;;
|
|
435
|
+
--verbose)
|
|
436
|
+
VERBOSE=true
|
|
437
|
+
;;
|
|
438
|
+
--signing-key)
|
|
439
|
+
shift
|
|
440
|
+
SIGNING_KEY="$1"
|
|
441
|
+
;;
|
|
442
|
+
*)
|
|
443
|
+
git_args+=("$1")
|
|
444
|
+
;;
|
|
445
|
+
esac
|
|
446
|
+
shift
|
|
447
|
+
done
|
|
448
|
+
|
|
449
|
+
verbose_log "git_root: $git_root"
|
|
450
|
+
verbose_log "auto_agree: $auto_agree"
|
|
451
|
+
verbose_log "git_args: ${git_args[*]}"
|
|
452
|
+
|
|
453
|
+
# Sign the DCO (sign-only, does not commit user code)
|
|
454
|
+
show_dco_first_time "$git_root" "$auto_agree"
|
|
455
|
+
|
|
456
|
+
# If git arguments were provided, run git commit with --signoff
|
|
457
|
+
if [[ ${#git_args[@]} -gt 0 ]]; then
|
|
458
|
+
verbose_log "Running git commit with signoff and args: ${git_args[*]}"
|
|
459
|
+
local sign_args=()
|
|
460
|
+
if [[ -n "$SIGNING_KEY" ]]; then
|
|
461
|
+
sign_args+=(-c 'gpg.format=ssh' -c "user.signingkey=$SIGNING_KEY" --gpg-sign)
|
|
462
|
+
fi
|
|
463
|
+
git_commit_with_sign --signoff "${sign_args[@]}" "${git_args[@]}"
|
|
464
|
+
fi
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
# Run main function
|
|
468
|
+
main "$@"
|
package/dco.sh
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# DCO CLI Entry Point
|
|
4
|
+
# ====================
|
|
5
|
+
# Routes subcommands to the appropriate script.
|
|
6
|
+
#
|
|
7
|
+
# dco commit [--signing-key <path>] [--yes] <git commit arguments>
|
|
8
|
+
# dco validate [--verbose] [--enforce-signature-fingerprints]
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
set -e
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
|
|
15
|
+
# Default subcommand is 'commit'
|
|
16
|
+
SUBCOMMAND="${1:-commit}"
|
|
17
|
+
|
|
18
|
+
case "$SUBCOMMAND" in
|
|
19
|
+
commit)
|
|
20
|
+
shift 2>/dev/null || true
|
|
21
|
+
exec "$SCRIPT_DIR/commit.sh" "$@"
|
|
22
|
+
;;
|
|
23
|
+
validate)
|
|
24
|
+
shift
|
|
25
|
+
exec "$SCRIPT_DIR/validate.sh" "$@"
|
|
26
|
+
;;
|
|
27
|
+
--help|-h)
|
|
28
|
+
echo "Usage: dco <command> [options]"
|
|
29
|
+
echo ""
|
|
30
|
+
echo "Commands:"
|
|
31
|
+
echo " commit Sign the DCO and commit (default)"
|
|
32
|
+
echo " validate Validate DCO signatures on commits"
|
|
33
|
+
echo ""
|
|
34
|
+
echo "Commit options:"
|
|
35
|
+
echo " --signing-key <path> SSH key for cryptographic signing"
|
|
36
|
+
echo " --yes-signoff Auto-agree to DCO terms"
|
|
37
|
+
echo " <git arguments> Passed through to git commit"
|
|
38
|
+
echo ""
|
|
39
|
+
echo "Validate options:"
|
|
40
|
+
echo " --verbose Show detailed output"
|
|
41
|
+
echo " --enforce-signature-fingerprints Require SSH signature fingerprints"
|
|
42
|
+
exit 0
|
|
43
|
+
;;
|
|
44
|
+
*)
|
|
45
|
+
echo "Unknown command: $SUBCOMMAND" >&2
|
|
46
|
+
echo "Usage: dco <commit|validate> [options]" >&2
|
|
47
|
+
exit 1
|
|
48
|
+
;;
|
|
49
|
+
esac
|