|
| 1 | +#!/bin/bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +STATE_FILE=".merge_chain_state" |
| 5 | + |
| 6 | +usage() { |
| 7 | + echo "Usage:" |
| 8 | + echo " $0 start <start_branch_id> <end_branch_id> # Start merging" |
| 9 | + echo " $0 continue # Continue interrupted merge" |
| 10 | + echo " $0 abort # Abort interrupted merge" |
| 11 | + echo " $0 status # Check current merge status" |
| 12 | + echo " $0 sync <start_branch_id> <end_branch_id> # Sync branches from remote" |
| 13 | + echo " $0 push <remote> <start_branch_id> <end_branch_id> # Push local branches to remote" |
| 14 | + echo " $0 help # Show detailed help information" |
| 15 | + echo "" |
| 16 | + echo "Examples:" |
| 17 | + echo " $0 sync 1 8 # Sync ch1 to ch8 from remote (for new clones)" |
| 18 | + echo " $0 start 3 8 # Start merging from ch3 to ch8" |
| 19 | + echo " $0 push origin 3 8 # Push ch3 to ch8 to origin remote" |
| 20 | + echo " $0 push upstream 3 8 # Push ch3 to ch8 to upstream remote" |
| 21 | + echo " $0 status # Check current merge progress" |
| 22 | + echo " $0 continue # Continue merge after resolving conflicts" |
| 23 | + echo "" |
| 24 | + echo "Merge message format: 'Merge ch{source} to ch{target}: {original message}'" |
| 25 | + exit 1 |
| 26 | +} |
| 27 | + |
| 28 | +show_help() { |
| 29 | + echo "Git Branch Chain Merge Tool" |
| 30 | + echo "===========================" |
| 31 | + echo "" |
| 32 | + echo "This tool maintains linear commit relationships across multiple branches." |
| 33 | + echo "When you commit to branch k, it automatically merges that commit to all" |
| 34 | + echo "branches with numbers greater than k." |
| 35 | + echo "" |
| 36 | + echo "Command descriptions:" |
| 37 | + echo " start <start_id> <end_id> Start chain merge from ch<start_id> to ch<end_id>" |
| 38 | + echo " continue Continue interrupted merge process" |
| 39 | + echo " abort Abort current merge process" |
| 40 | + echo " status Show current merge status and progress" |
| 41 | + echo " sync <start_id> <end_id> Sync local branches from remote (for new clones)" |
| 42 | + echo " push <remote> <start_id> <end_id> Push local branches to specified remote" |
| 43 | + echo " help Show this help information" |
| 44 | + echo "" |
| 45 | + echo "Git Operations done by the tool:" |
| 46 | + echo " For each target branch (start+1, start+2, ..., end):" |
| 47 | + echo " 1. git checkout ch{target} # Switch to target branch" |
| 48 | + echo " 2. git merge ch{source} -m \"Merge...\" # Merge with custom message" |
| 49 | + echo " 3. If conflict: save state and exit for manual resolution" |
| 50 | + echo " 4. If success: continue to next branch" |
| 51 | + echo "" |
| 52 | + echo "Workflow:" |
| 53 | + echo " For newly cloned repositories:" |
| 54 | + echo " 0. Run '$0 sync 1 8' to create local branches from remote" |
| 55 | + echo "" |
| 56 | + echo " Normal workflow:" |
| 57 | + echo " 1. Commit code to a branch (e.g., ch3)" |
| 58 | + echo " 2. Run '$0 start 3 8' to start merging" |
| 59 | + echo " 3. If conflicts occur, resolve manually and run '$0 continue'" |
| 60 | + echo " 4. Repeat step 3 until all branches are merged" |
| 61 | + echo " 5. Run '$0 push origin 3 8' to push all merged branches to remote" |
| 62 | + echo "" |
| 63 | + echo "Conflict Resolution (when merge fails):" |
| 64 | + echo " 1. Edit conflicted files manually" |
| 65 | + echo " 2. Stage resolved files with 'git add'" |
| 66 | + echo " 3. Complete the merge commit with 'git merge --continue'" |
| 67 | + echo " 4. Execute '$0 continue' to resume chain merge process" |
| 68 | + echo "" |
| 69 | + echo "Merge message format:" |
| 70 | + echo " 'Merge ch{source} to ch{target}: {original commit message}'" |
| 71 | + echo "" |
| 72 | + echo " Where:" |
| 73 | + echo " - source: Previous branch number (e.g., ch3)" |
| 74 | + echo " - target: Current branch number (e.g., ch4)" |
| 75 | + echo " - original commit message: The commit message from the starting branch" |
| 76 | + echo "" |
| 77 | + echo " Example: 'Merge ch3 to ch4: Add user authentication feature'" |
| 78 | + echo "" |
| 79 | + echo "State file:" |
| 80 | + echo " Creates '.merge_chain_state' file to save progress during merge" |
| 81 | + echo " Automatically deleted when merge completes or is aborted" |
| 82 | + echo "" |
| 83 | + exit 0 |
| 84 | +} |
| 85 | + |
| 86 | +if [[ $# -lt 1 ]]; then |
| 87 | + usage |
| 88 | +fi |
| 89 | + |
| 90 | +COMMAND=$1 |
| 91 | + |
| 92 | +case "$COMMAND" in |
| 93 | + start) |
| 94 | + if [[ $# -ne 3 ]]; then usage; fi |
| 95 | + START_BRANCH=$2 |
| 96 | + END_BRANCH=$3 |
| 97 | + |
| 98 | + # Validate branch numbers |
| 99 | + if [[ ! $START_BRANCH =~ ^[0-9]+$ ]] || [[ ! $END_BRANCH =~ ^[0-9]+$ ]]; then |
| 100 | + echo "Error: Branch numbers must be numeric" |
| 101 | + exit 1 |
| 102 | + fi |
| 103 | + |
| 104 | + if [[ $START_BRANCH -ge $END_BRANCH ]]; then |
| 105 | + echo "Error: Start branch number must be less than end branch number" |
| 106 | + exit 1 |
| 107 | + fi |
| 108 | + |
| 109 | + |
| 110 | + # Check if all required branches exist locally |
| 111 | + echo "Checking required branches..." |
| 112 | + for ((branch_num=START_BRANCH; branch_num<=END_BRANCH; branch_num++)); do |
| 113 | + branch_name="ch${branch_num}" |
| 114 | + |
| 115 | + # Check if local branch exists |
| 116 | + if ! git show-ref --verify --quiet refs/heads/${branch_name}; then |
| 117 | + echo "" |
| 118 | + echo "❌ Error: Local branch ${branch_name} does not exist" |
| 119 | + echo "💡 Solution: Run the following command to sync branches from remote:" |
| 120 | + echo " $0 sync ${START_BRANCH} ${END_BRANCH}" |
| 121 | + echo "" |
| 122 | + echo "This will create local branches from the remote repository." |
| 123 | + exit 1 |
| 124 | + else |
| 125 | + echo "Local branch ${branch_name} exists" |
| 126 | + fi |
| 127 | + done |
| 128 | + |
| 129 | + # Check for incomplete merge |
| 130 | + if [[ -f "$STATE_FILE" ]]; then |
| 131 | + echo "Error: Incomplete merge process exists, please run '$0 continue' or '$0 abort' first" |
| 132 | + exit 1 |
| 133 | + fi |
| 134 | + |
| 135 | + NEXT_BRANCH=$((START_BRANCH + 1)) |
| 136 | + # Get the latest commit message from the first branch |
| 137 | + ORIGIN_MSG=$(git log -1 --pretty=%B ch${START_BRANCH}) |
| 138 | + echo "Starting chain merge: ch${START_BRANCH} -> ch${END_BRANCH}" |
| 139 | + echo "Original commit message: $ORIGIN_MSG" |
| 140 | + ;; |
| 141 | + |
| 142 | + continue) |
| 143 | + if [[ ! -f "$STATE_FILE" ]]; then |
| 144 | + echo "No interrupted state found, cannot continue." |
| 145 | + exit 1 |
| 146 | + fi |
| 147 | + read START_BRANCH END_BRANCH ORIGIN_MSG NEXT_BRANCH < "$STATE_FILE" |
| 148 | + echo "Continuing interrupted merge from ch$NEXT_BRANCH..." |
| 149 | + ;; |
| 150 | + |
| 151 | + abort) |
| 152 | + if [[ -f "$STATE_FILE" ]]; then |
| 153 | + rm "$STATE_FILE" |
| 154 | + echo "Interrupted merge has been aborted." |
| 155 | + else |
| 156 | + echo "No interrupted state found, nothing to abort." |
| 157 | + fi |
| 158 | + exit 0 |
| 159 | + ;; |
| 160 | + |
| 161 | + status) |
| 162 | + if [[ -f "$STATE_FILE" ]]; then |
| 163 | + read START_BRANCH END_BRANCH ORIGIN_MSG NEXT_BRANCH < "$STATE_FILE" |
| 164 | + echo "Merge status: In progress" |
| 165 | + echo "Start branch: ch${START_BRANCH}" |
| 166 | + echo "End branch: ch${END_BRANCH}" |
| 167 | + echo "Original message: $ORIGIN_MSG" |
| 168 | + echo "Current progress: Preparing to merge to ch${NEXT_BRANCH}" |
| 169 | + echo "Remaining branches: $((END_BRANCH - NEXT_BRANCH + 1))" |
| 170 | + |
| 171 | + # Show completed branches |
| 172 | + if [[ $NEXT_BRANCH -gt $((START_BRANCH + 1)) ]]; then |
| 173 | + echo "Completed: ch${START_BRANCH} -> ch$((NEXT_BRANCH - 1))" |
| 174 | + fi |
| 175 | + |
| 176 | + # Show pending branches |
| 177 | + if [[ $NEXT_BRANCH -le $END_BRANCH ]]; then |
| 178 | + echo "Pending: ch${NEXT_BRANCH} -> ch${END_BRANCH}" |
| 179 | + fi |
| 180 | + else |
| 181 | + echo "Merge status: No merge in progress" |
| 182 | + echo "Current branch: $(git branch --show-current)" |
| 183 | + fi |
| 184 | + exit 0 |
| 185 | + ;; |
| 186 | + |
| 187 | + sync) |
| 188 | + if [[ $# -ne 3 ]]; then usage; fi |
| 189 | + SYNC_START=$2 |
| 190 | + SYNC_END=$3 |
| 191 | + |
| 192 | + # Validate branch numbers |
| 193 | + if [[ ! $SYNC_START =~ ^[0-9]+$ ]] || [[ ! $SYNC_END =~ ^[0-9]+$ ]]; then |
| 194 | + echo "Error: Branch numbers must be numeric" |
| 195 | + exit 1 |
| 196 | + fi |
| 197 | + |
| 198 | + if [[ $SYNC_START -gt $SYNC_END ]]; then |
| 199 | + echo "Error: Start branch number must be less than or equal to end branch number" |
| 200 | + exit 1 |
| 201 | + fi |
| 202 | + |
| 203 | + # Fetch remote branches |
| 204 | + echo "Fetching remote branch information..." |
| 205 | + if ! git fetch origin --quiet; then |
| 206 | + echo "Error: Failed to fetch from remote repository" |
| 207 | + exit 1 |
| 208 | + fi |
| 209 | + |
| 210 | + # Sync branches |
| 211 | + echo "Syncing branches ch${SYNC_START} to ch${SYNC_END}..." |
| 212 | + for ((branch_num=SYNC_START; branch_num<=SYNC_END; branch_num++)); do |
| 213 | + branch_name="ch${branch_num}" |
| 214 | + |
| 215 | + if ! git show-ref --verify --quiet refs/heads/${branch_name}; then |
| 216 | + if git show-ref --verify --quiet refs/remotes/origin/${branch_name}; then |
| 217 | + echo "Creating local branch ${branch_name} from origin/${branch_name}..." |
| 218 | + if ! git checkout -b ${branch_name} origin/${branch_name}; then |
| 219 | + echo "Error: Failed to create local branch ${branch_name}" |
| 220 | + exit 1 |
| 221 | + fi |
| 222 | + else |
| 223 | + echo "Warning: Remote branch origin/${branch_name} does not exist, skipping" |
| 224 | + fi |
| 225 | + else |
| 226 | + echo "Local branch ${branch_name} already exists" |
| 227 | + fi |
| 228 | + done |
| 229 | + |
| 230 | + echo "Branch synchronization completed!" |
| 231 | + exit 0 |
| 232 | + ;; |
| 233 | + |
| 234 | + push) |
| 235 | + if [[ $# -ne 4 ]]; then usage; fi |
| 236 | + REMOTE=$2 |
| 237 | + PUSH_START=$3 |
| 238 | + PUSH_END=$4 |
| 239 | + |
| 240 | + # Validate branch numbers |
| 241 | + if [[ ! $PUSH_START =~ ^[0-9]+$ ]] || [[ ! $PUSH_END =~ ^[0-9]+$ ]]; then |
| 242 | + echo "Error: Branch numbers must be numeric" |
| 243 | + exit 1 |
| 244 | + fi |
| 245 | + |
| 246 | + if [[ $PUSH_START -gt $PUSH_END ]]; then |
| 247 | + echo "Error: Start branch number must be less than or equal to end branch number" |
| 248 | + exit 1 |
| 249 | + fi |
| 250 | + |
| 251 | + # Check for incomplete merge |
| 252 | + if [[ -f "$STATE_FILE" ]]; then |
| 253 | + echo "Warning: There is an incomplete merge process." |
| 254 | + echo "Please run '$0 continue' or '$0 abort' before pushing." |
| 255 | + exit 1 |
| 256 | + fi |
| 257 | + |
| 258 | + # Push branches |
| 259 | + echo "Pushing branches ch${PUSH_START} to ch${PUSH_END} to remote '${REMOTE}'..." |
| 260 | + FAILED_PUSHES=() |
| 261 | + |
| 262 | + for ((branch_num=PUSH_START; branch_num<=PUSH_END; branch_num++)); do |
| 263 | + branch_name="ch${branch_num}" |
| 264 | + |
| 265 | + # Check if local branch exists |
| 266 | + if ! git show-ref --verify --quiet refs/heads/${branch_name}; then |
| 267 | + echo "❌ Warning: Local branch ${branch_name} does not exist, skipping" |
| 268 | + FAILED_PUSHES+=("${branch_name} (not found)") |
| 269 | + continue |
| 270 | + fi |
| 271 | + |
| 272 | + echo "Pushing ${branch_name}..." |
| 273 | + if git push ${REMOTE} ${branch_name}; then |
| 274 | + echo "✅ Successfully pushed ${branch_name}" |
| 275 | + else |
| 276 | + echo "❌ Failed to push ${branch_name}" |
| 277 | + FAILED_PUSHES+=("${branch_name} (push failed)") |
| 278 | + fi |
| 279 | + done |
| 280 | + |
| 281 | + echo "" |
| 282 | + if [[ ${#FAILED_PUSHES[@]} -eq 0 ]]; then |
| 283 | + echo "🎉 All branches pushed successfully!" |
| 284 | + else |
| 285 | + echo "⚠️ Push completed with some failures:" |
| 286 | + for failure in "${FAILED_PUSHES[@]}"; do |
| 287 | + echo " - ${failure}" |
| 288 | + done |
| 289 | + echo "" |
| 290 | + echo "Please check the failed branches and try again if needed." |
| 291 | + fi |
| 292 | + |
| 293 | + exit 0 |
| 294 | + ;; |
| 295 | + |
| 296 | + help) |
| 297 | + show_help |
| 298 | + ;; |
| 299 | + |
| 300 | + *) |
| 301 | + usage |
| 302 | + ;; |
| 303 | +esac |
| 304 | + |
| 305 | +for ((i=NEXT_BRANCH; i<=END_BRANCH; i++)); do |
| 306 | + CUR_BRANCH="ch${i}" |
| 307 | + PREV_BRANCH="ch$((i-1))" |
| 308 | + PROGRESS="[$((i-START_BRANCH))/$((END_BRANCH-START_BRANCH))]" |
| 309 | + |
| 310 | + echo "" |
| 311 | + echo "=== ${PROGRESS} Merging ${PREV_BRANCH} -> ${CUR_BRANCH} ===" |
| 312 | + |
| 313 | + # Check if target branch exists |
| 314 | + if ! git show-ref --verify --quiet refs/heads/${CUR_BRANCH}; then |
| 315 | + echo "Error: Branch ${CUR_BRANCH} does not exist" |
| 316 | + echo "$START_BRANCH $END_BRANCH \"$ORIGIN_MSG\" $i" > "$STATE_FILE" |
| 317 | + exit 1 |
| 318 | + fi |
| 319 | + |
| 320 | + echo "Switching to branch ${CUR_BRANCH}..." |
| 321 | + if ! git checkout "${CUR_BRANCH}"; then |
| 322 | + echo "Error: Cannot switch to branch ${CUR_BRANCH}" |
| 323 | + echo "$START_BRANCH $END_BRANCH \"$ORIGIN_MSG\" $i" > "$STATE_FILE" |
| 324 | + exit 1 |
| 325 | + fi |
| 326 | + |
| 327 | + echo "Merging ${PREV_BRANCH}..." |
| 328 | + if ! git merge "${PREV_BRANCH}" -m "Merge ${PREV_BRANCH} to ${CUR_BRANCH}: ${ORIGIN_MSG}"; then |
| 329 | + echo "" |
| 330 | + echo "❌ Conflict occurred in ${CUR_BRANCH}" |
| 331 | + echo "Please follow these steps to resolve:" |
| 332 | + echo "1. Manually resolve conflict files" |
| 333 | + echo "2. Run 'git add .' to add resolved files" |
| 334 | + echo "3. Run 'git commit' to commit the merge" |
| 335 | + echo "4. Run '$0 continue' to continue the merge process" |
| 336 | + echo "" |
| 337 | + echo "Or run '$0 abort' to abort the merge" |
| 338 | + echo "Or run '$0 status' to check current status" |
| 339 | + echo "$START_BRANCH $END_BRANCH \"$ORIGIN_MSG\" $i" > "$STATE_FILE" |
| 340 | + exit 1 |
| 341 | + fi |
| 342 | + |
| 343 | + echo "✅ Successfully merged to ${CUR_BRANCH}" |
| 344 | +done |
| 345 | + |
| 346 | +# Merge completed, delete state file |
| 347 | +[[ -f "$STATE_FILE" ]] && rm "$STATE_FILE" |
| 348 | +echo "" |
| 349 | +echo "🎉 All branches merged successfully!" |
| 350 | +echo "Merge range: ch${START_BRANCH} -> ch${END_BRANCH}" |
| 351 | +echo "Original commit: $ORIGIN_MSG" |
| 352 | +echo "" |
| 353 | +echo "💡 To push all merged branches to remote, run:" |
| 354 | +echo " $0 push origin ${START_BRANCH} ${END_BRANCH}" |
0 commit comments