-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgit-migration-tool.sh
More file actions
executable file
·972 lines (860 loc) · 33.9 KB
/
git-migration-tool.sh
File metadata and controls
executable file
·972 lines (860 loc) · 33.9 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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
#!/bin/bash
# ╔═══════════════════════════════════════════════════════════════════════════╗
# ║ Git Migration Tool v2.1.0 ║
# ║ Enhanced GitLab to GitHub migration with flexible ║
# ║ author handling options ║
# ║ ║
# ║ Author: Dev Kraken <soman@devkraken.com> ║
# ║ GitHub: https://github.com/dev-kraken ║
# ║ Website: https://devkraken.com ║
# ║ ║
# ║ A comprehensive solution for migrating repositories between ║
# ║ GitLab and GitHub with three flexible author handling modes ║
# ╚═══════════════════════════════════════════════════════════════════════════╝
set -euo pipefail
# Global installation detection
if [[ "${BASH_SOURCE[0]}" == "/usr/local/bin/git-migration-tool" || "${BASH_SOURCE[0]}" == "/usr/bin/git-migration-tool" ]]; then
# Global installation - use home directory for configs and logs
SCRIPT_DIR="$HOME/.git-migration-tool"
mkdir -p "$SCRIPT_DIR/logs" "$SCRIPT_DIR/config"
GLOBAL_INSTALL=true
else
# Local installation - use script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GLOBAL_INSTALL=false
fi
CORE_LIB="$SCRIPT_DIR/src/git-migration-core.sh"
BANNER_LIB="$SCRIPT_DIR/src/banner-utils.sh"
PLATFORM_LIB="$SCRIPT_DIR/src/platform-detection.sh"
# Import libraries if they exist
if [[ -f "$CORE_LIB" ]]; then
source "$CORE_LIB"
fi
if [[ -f "$BANNER_LIB" ]]; then
source "$BANNER_LIB"
fi
if [[ -f "$PLATFORM_LIB" ]]; then
source "$PLATFORM_LIB"
PLATFORM_DETECTION_ENABLED=true
else
PLATFORM_DETECTION_ENABLED=false
fi
# Fallback functions if libraries don't exist
if [[ ! -f "$CORE_LIB" ]]; then
# Use inline functions if core library doesn't exist
source_inline_functions() {
# Basic color constants
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
NC='\033[0m'
# Basic logging functions
log_with_level() {
local level="$1"
local color="$2"
local message="$3"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "${color}[${level}]${NC} ${timestamp} - $message"
}
log_info() { log_with_level "INFO" "$GREEN" "$1"; }
log_warn() { log_with_level "WARN" "$YELLOW" "$1"; }
log_error() { log_with_level "ERROR" "$RED" "$1"; }
log_step() { log_with_level "STEP" "$PURPLE" "$1"; }
log_success() { log_with_level "SUCCESS" "$CYAN" "$1"; }
}
source_inline_functions
fi
# Fallback banner function if banner library doesn't exist
if [[ ! -f "$BANNER_LIB" ]]; then
print_migration_tool_banner() {
echo -e "${WHITE}╔═══════════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${WHITE}║ Git Migration Tool v2.1.0 ║${NC}"
echo -e "${WHITE}║ Created by Dev Kraken ║${NC}"
echo -e "${WHITE}║ https://devkraken.com ║${NC}"
echo -e "${WHITE}╚═══════════════════════════════════════════════════════════════════════════╝${NC}"
}
print_help_banner() {
echo -e "${WHITE}╔═══════════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${WHITE}║ Git Migration Tool v2.1.0 ║${NC}"
echo -e "${WHITE}║ Created by Dev Kraken ║${NC}"
echo -e "${WHITE}║ https://devkraken.com | soman@devkraken.com ║${NC}"
echo -e "${WHITE}╚═══════════════════════════════════════════════════════════════════════════╝${NC}"
}
fi
# Configuration variables
GITLAB_URL=""
GITHUB_URL=""
SOURCE_URL="" # New: Universal source URL
TARGET_URL="" # New: Universal target URL
OPERATION_MODE=""
FORCE_MODE=false
INTERACTIVE_MODE=false
MAX_FILE_SIZE="10M"
DRY_RUN=false
VERBOSE=false
UNIVERSAL_MODE=false # New: Use universal platform detection
MAIN_BRANCH_ONLY=false # New: Migrate only main branch
# Author handling
AUTHOR_MODE="preserve" # preserve, selective, unified
TARGET_AUTHOR_NAME="Dev Kraken" # Default author name
TARGET_AUTHOR_EMAIL="soman@devkraken.com" # Default author email
SOURCE_AUTHOR_NAME=""
SOURCE_AUTHOR_EMAIL=""
# Directories
DEFAULT_LOG_DIR="$SCRIPT_DIR/logs"
DEFAULT_TEMP_DIR="/tmp/git-migration-$$"
TEMP_DIR=""
#==============================================================================
# CORE FUNCTIONS (fallback if library not available)
#==============================================================================
# Only define these fallback functions if the core library was not loaded
if [[ ! -f "$CORE_LIB" ]]; then
check_dependencies() {
log_step "Checking dependencies..."
if ! command -v git >/dev/null 2>&1; then
log_error "Git is not installed"
return 1
fi
if ! command -v python3 >/dev/null 2>&1; then
log_error "Python 3 is not installed"
return 1
fi
if ! command -v git-filter-repo >/dev/null 2>&1; then
log_info "Installing git-filter-repo..."
python3 -m pip install git-filter-repo || {
log_error "Failed to install git-filter-repo"
return 1
}
fi
log_success "All dependencies are available"
return 0
}
process_authors() {
local repo_dir="$1"
cd "$repo_dir" || return 1
case "$AUTHOR_MODE" in
"preserve")
log_step "Preserving original commit authors"
if [[ "$DRY_RUN" == true ]]; then
log_info "[DRY RUN] Would preserve all original authors"
else
log_success "Original authors preserved - no changes made"
fi
;;
"selective")
log_step "Changing commits from '$SOURCE_AUTHOR_NAME <$SOURCE_AUTHOR_EMAIL>' to '$TARGET_AUTHOR_NAME <$TARGET_AUTHOR_EMAIL>'"
if [[ "$DRY_RUN" == true ]]; then
local commit_count
commit_count=$(git log --author="$SOURCE_AUTHOR_EMAIL" --oneline | wc -l)
log_info "[DRY RUN] Found $commit_count commits from $SOURCE_AUTHOR_NAME"
else
git filter-repo \
--name-callback "return b'$TARGET_AUTHOR_NAME' if old_name.decode('utf-8') == '$SOURCE_AUTHOR_NAME' else old_name" \
--email-callback "return b'$TARGET_AUTHOR_EMAIL' if old_email.decode('utf-8') == '$SOURCE_AUTHOR_EMAIL' else old_email" \
--force 2>/dev/null
log_success "Selective author change completed"
fi
;;
"unified")
log_step "Changing ALL commit authors to: $TARGET_AUTHOR_NAME <$TARGET_AUTHOR_EMAIL>"
if [[ "$DRY_RUN" == true ]]; then
local total_commits
total_commits=$(git rev-list --all --count)
log_info "[DRY RUN] Would change $total_commits commits"
else
git filter-repo \
--name-callback "return b'$TARGET_AUTHOR_NAME'" \
--email-callback "return b'$TARGET_AUTHOR_EMAIL'" \
--force 2>/dev/null
log_success "All commits unified under single author"
fi
;;
*)
log_error "Invalid author mode: $AUTHOR_MODE"
return 1
;;
esac
}
clean_repository_history() {
local repo_dir="$1"
local max_size="${2:-10M}"
log_step "Cleaning repository history"
cd "$repo_dir" || return 1
if [[ "$DRY_RUN" == true ]]; then
log_info "[DRY RUN] Would clean repository history"
return 0
fi
# Remove large directories
local large_dirs=("node_modules" "build" "dist" "target" "bin" "obj" ".cache" "vendor")
for dir in "${large_dirs[@]}"; do
git filter-repo --path "$dir" --invert-paths --force 2>/dev/null || true
done
# Remove large file types
local large_extensions=("*.map" "*.log" "*.tmp" "*.pdf" "*.zip" "*.tar.gz" "*.rar" "*.7z")
for ext in "${large_extensions[@]}"; do
git filter-repo --path "$ext" --invert-paths --force 2>/dev/null || true
done
# Remove blobs bigger than specified size
git filter-repo --strip-blobs-bigger-than "$max_size" --force 2>/dev/null || true
log_success "Repository history cleaned"
}
fi # End of fallback functions guard
#==============================================================================
# INTERACTIVE CONFIGURATION
#==============================================================================
interactive_setup() {
print_migration_tool_banner
echo
if [[ "$PLATFORM_DETECTION_ENABLED" == true ]]; then
echo "Universal Git Migration Tool"
echo "Supports migration between any Git platforms (GitHub, GitLab, BitBucket, etc.)"
echo
# Use enhanced platform setup
interactive_platform_setup
else
echo "This tool helps you migrate repositories from GitLab to GitHub"
echo "with flexible author handling options."
echo
# Get repository URLs (legacy mode) with validation
while true; do
read -p "Source repository URL: " GITLAB_URL
if [[ -z "$GITLAB_URL" ]]; then
echo -e "${RED}✗ URL cannot be empty. Please enter a valid Git repository URL.${NC}"
continue
fi
# Basic URL validation
if [[ "$GITLAB_URL" =~ ^(https?|git|ssh)://.*\.git$ ]] || [[ "$GITLAB_URL" =~ ^git@.*:.*\.git$ ]]; then
echo -e "${GREEN}✓ Source URL accepted${NC}"
break
else
echo -e "${RED}✗ Invalid URL format${NC}"
echo -e "${YELLOW}Expected format: https://platform.com/user/repo.git or git@platform.com:user/repo.git${NC}"
fi
done
while true; do
read -p "Target repository URL: " GITHUB_URL
if [[ -z "$GITHUB_URL" ]]; then
echo -e "${RED}✗ URL cannot be empty. Please enter a valid Git repository URL.${NC}"
continue
fi
# Basic URL validation
if [[ "$GITHUB_URL" =~ ^(https?|git|ssh)://.*\.git$ ]] || [[ "$GITHUB_URL" =~ ^git@.*:.*\.git$ ]]; then
echo -e "${GREEN}✓ Target URL accepted${NC}"
break
else
echo -e "${RED}✗ Invalid URL format${NC}"
echo -e "${YELLOW}Expected format: https://platform.com/user/repo.git or git@platform.com:user/repo.git${NC}"
fi
done
fi
echo
echo "Author Handling Options:"
echo "1) Preserve original authors (default - no changes)"
echo "2) Change specific user's commits only"
echo "3) Change ALL commits to one user"
echo
read -p "Choose author mode (1-3): " -n 1 -r
echo
case $REPLY in
1)
AUTHOR_MODE="preserve"
log_info "Selected: Preserve original authors"
;;
2)
AUTHOR_MODE="selective"
echo
read -p "Source author name (to change FROM): " SOURCE_AUTHOR_NAME
read -p "Source author email (to change FROM): " SOURCE_AUTHOR_EMAIL
read -p "Target author name (to change TO): " TARGET_AUTHOR_NAME
read -p "Target author email (to change TO): " TARGET_AUTHOR_EMAIL
;;
3)
AUTHOR_MODE="unified"
echo
read -p "New author name (for ALL commits) [${TARGET_AUTHOR_NAME}]: " input_name
TARGET_AUTHOR_NAME="${input_name:-$TARGET_AUTHOR_NAME}"
read -p "New author email (for ALL commits) [${TARGET_AUTHOR_EMAIL}]: " input_email
TARGET_AUTHOR_EMAIL="${input_email:-$TARGET_AUTHOR_EMAIL}"
;;
*)
AUTHOR_MODE="preserve"
log_warn "Invalid selection, defaulting to preserve original authors"
;;
esac
echo
echo "Operation Mode:"
echo "1) Migration (first time GitLab → GitHub)"
echo "2) Sync (update existing GitHub repo)"
echo
read -p "Choose operation (1-2): " -n 1 -r
echo
case $REPLY in
1) OPERATION_MODE="migrate" ;;
2) OPERATION_MODE="sync" ;;
*) OPERATION_MODE="migrate"; log_warn "Invalid selection, defaulting to migration" ;;
esac
echo
echo "Branch Options:"
echo "1) Migrate all branches (default)"
echo "2) Migrate only main branch (clean migration)"
echo
read -p "Choose branch option (1-2): " -n 1 -r
echo
case $REPLY in
2)
MAIN_BRANCH_ONLY=true
log_info "Selected: Migrate only main branch"
;;
*)
MAIN_BRANCH_ONLY=false
log_info "Selected: Migrate all branches"
;;
esac
echo
read -p "Enable dry run mode? (Y/n): " -n 1 -r
echo
[[ ! $REPLY =~ ^[Nn]$ ]] && DRY_RUN=true
read -p "Enable verbose logging? (y/N): " -n 1 -r
echo
[[ $REPLY =~ ^[Yy]$ ]] && VERBOSE=true
echo
echo "Configuration Summary:"
echo " GitLab URL: $GITLAB_URL"
echo " GitHub URL: $GITHUB_URL"
echo " Author Mode: $AUTHOR_MODE"
if [[ "$AUTHOR_MODE" == "selective" ]]; then
echo " Change FROM: $SOURCE_AUTHOR_NAME <$SOURCE_AUTHOR_EMAIL>"
echo " Change TO: $TARGET_AUTHOR_NAME <$TARGET_AUTHOR_EMAIL>"
elif [[ "$AUTHOR_MODE" == "unified" ]]; then
echo " New Author: $TARGET_AUTHOR_NAME <$TARGET_AUTHOR_EMAIL>"
fi
echo " Branch Mode: $([ "$MAIN_BRANCH_ONLY" == true ] && echo "Main branch only" || echo "All branches")"
echo " Operation: $OPERATION_MODE"
echo " Dry Run: $DRY_RUN"
echo " Verbose: $VERBOSE"
echo
read -p "Proceed with these settings? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_info "Operation cancelled"
exit 0
fi
}
#==============================================================================
# OPERATION FUNCTIONS
#==============================================================================
migrate_repository() {
if [[ "$PLATFORM_DETECTION_ENABLED" == true ]]; then
# Use universal migration with platform detection
local source_platform=$(detect_platform "$SOURCE_URL")
local target_platform=$(detect_platform "$TARGET_URL")
local source_name=$(get_platform_name "$source_platform")
local target_name=$(get_platform_name "$target_platform")
log_step "=== Starting Universal Migration: $source_name → $target_name ==="
show_migration_info "$SOURCE_URL" "$TARGET_URL"
else
# Legacy mode
log_step "=== Starting GitLab to GitHub Migration ==="
fi
local temp_repo_dir="$TEMP_DIR/migration-repo"
# Clone from source repository
if [[ "$PLATFORM_DETECTION_ENABLED" == true ]]; then
local source_name=$(get_platform_name "$(detect_platform "$SOURCE_URL")")
log_step "Cloning from $source_name: $SOURCE_URL"
else
log_step "Cloning from GitLab: $GITLAB_URL"
fi
if [[ "$DRY_RUN" == true ]]; then
if [[ "$MAIN_BRANCH_ONLY" == true ]]; then
log_info "[DRY RUN] Would clone only main branch from $SOURCE_URL"
else
log_info "[DRY RUN] Would clone all branches from $SOURCE_URL"
fi
else
if [[ "$MAIN_BRANCH_ONLY" == true ]]; then
log_info "Cloning only main branch..."
git clone --single-branch "$SOURCE_URL" "$temp_repo_dir" || {
if [[ "$PLATFORM_DETECTION_ENABLED" == true ]]; then
local source_name=$(get_platform_name "$(detect_platform "$SOURCE_URL")")
log_error "Failed to clone main branch from $source_name"
else
log_error "Failed to clone main branch from source repository"
fi
return 1
}
else
log_info "Cloning all branches..."
git clone "$SOURCE_URL" "$temp_repo_dir" || {
if [[ "$PLATFORM_DETECTION_ENABLED" == true ]]; then
local source_name=$(get_platform_name "$(detect_platform "$SOURCE_URL")")
log_error "Failed to clone from $source_name"
else
log_error "Failed to clone from source repository"
fi
return 1
}
fi
fi
# Process authors based on selected mode
process_authors "$temp_repo_dir" || return 1
# Clean repository history
clean_repository_history "$temp_repo_dir" "$MAX_FILE_SIZE" || return 1
# Create enhanced .gitignore
log_step "Creating enhanced .gitignore"
if [[ "$DRY_RUN" == true ]]; then
log_info "[DRY RUN] Would create enhanced .gitignore"
else
cd "$temp_repo_dir"
cat > .gitignore << 'EOF'
# Dependencies
node_modules/
vendor/
.env
.env.local
.env.*.local
# Build outputs
build/
dist/
target/
bin/
obj/
out/
# Large files
*.map
*.log
*.tmp
*.pdf
*.zip
*.tar.gz
*.rar
*.7z
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
desktop.ini
# Cache
.cache/
.npm/
.yarn/
__pycache__/
EOF
git add .gitignore
git commit -m "Add enhanced .gitignore" 2>/dev/null || true
log_success "Enhanced .gitignore created"
fi
# Optimize repository
log_step "Optimizing repository"
if [[ "$DRY_RUN" == true ]]; then
log_info "[DRY RUN] Would optimize repository"
else
cd "$temp_repo_dir"
git gc --aggressive --prune=now
log_success "Repository optimized"
fi
# Push to target repository
if [[ "$PLATFORM_DETECTION_ENABLED" == true ]]; then
local target_name=$(get_platform_name "$(detect_platform "$TARGET_URL")")
log_step "Pushing to $target_name: $TARGET_URL"
else
log_step "Pushing to GitHub: $GITHUB_URL"
fi
if [[ "$DRY_RUN" == true ]]; then
if [[ "$PLATFORM_DETECTION_ENABLED" == true ]]; then
local target_name=$(get_platform_name "$(detect_platform "$TARGET_URL")")
log_info "[DRY RUN] Would push to $target_name"
else
log_info "[DRY RUN] Would push to GitHub"
fi
else
cd "$temp_repo_dir"
git remote remove origin 2>/dev/null || true
git remote add origin "$TARGET_URL"
if [[ "$FORCE_MODE" != true && "$AUTHOR_MODE" != "preserve" ]]; then
log_warn "About to push to GitHub with modified history!"
read -p "Continue? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_info "Push cancelled"
return 1
fi
fi
if [[ "$MAIN_BRANCH_ONLY" == true ]]; then
# Push only main branch
local main_branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main")
if ! git show-ref --verify --quiet refs/heads/$main_branch; then
main_branch="master" # Fallback to master if main doesn't exist
fi
local push_flags=""
[[ "$AUTHOR_MODE" != "preserve" ]] && push_flags="--force"
log_info "Pushing only '$main_branch' branch..."
git push origin "$main_branch:$main_branch" $push_flags
else
# Push all branches
local push_flags="--all"
[[ "$AUTHOR_MODE" != "preserve" ]] && push_flags="$push_flags --force"
git push origin $push_flags
fi
git push origin --tags --force 2>/dev/null || true
if [[ "$PLATFORM_DETECTION_ENABLED" == true ]]; then
local target_name=$(get_platform_name "$(detect_platform "$TARGET_URL")")
log_success "Successfully pushed to $target_name"
else
log_success "Successfully pushed to GitHub"
fi
fi
log_success "=== Migration completed successfully ==="
log_info "Repository migrated to: $TARGET_URL"
}
sync_repository() {
# Use universal platform detection for URLs
local source_repo_url="${SOURCE_URL:-$GITLAB_URL}"
local target_repo_url="${TARGET_URL:-$GITHUB_URL}"
# Auto-detect platform names for better UX
local source_platform="source repository"
local target_platform="target repository"
if [[ "$PLATFORM_DETECTION_ENABLED" == true ]]; then
source_platform=$(detect_platform_name "$source_repo_url")
target_platform=$(detect_platform_name "$target_repo_url")
fi
log_step "=== Starting $source_platform to $target_platform Sync ==="
local temp_repo_dir="$TEMP_DIR/sync-repo"
# Clone fresh from source
log_step "Cloning fresh copy from $source_platform: $source_repo_url"
if [[ "$DRY_RUN" == true ]]; then
if [[ "$MAIN_BRANCH_ONLY" == true ]]; then
log_info "[DRY RUN] Would clone only main branch"
else
log_info "[DRY RUN] Would clone all branches"
fi
else
if [[ "$MAIN_BRANCH_ONLY" == true ]]; then
log_info "Cloning only main branch..."
git clone --single-branch "$source_repo_url" "$temp_repo_dir" || {
log_error "Failed to clone main branch from $source_platform"
return 1
}
else
log_info "Cloning all branches..."
git clone "$source_repo_url" "$temp_repo_dir" || {
log_error "Failed to clone from $source_platform"
return 1
}
fi
fi
# Compare with target (informational)
if [[ "$DRY_RUN" != true ]]; then
cd "$temp_repo_dir"
local source_commit=$(git rev-parse HEAD)
log_info "$source_platform latest commit: ${source_commit:0:8}"
if target_commit=$(git ls-remote "$target_repo_url" HEAD 2>/dev/null | cut -f1); then
log_info "$target_platform latest commit: ${target_commit:0:8}"
else
log_info "$target_platform repository appears empty or inaccessible"
fi
fi
# Process authors, clean, and optimize (same as migration)
process_authors "$temp_repo_dir" || return 1
clean_repository_history "$temp_repo_dir" "$MAX_FILE_SIZE" || return 1
# Force push to target (sync operation)
log_step "Force pushing to $target_platform: $target_repo_url"
if [[ "$DRY_RUN" == true ]]; then
log_info "[DRY RUN] Would force push to $target_platform"
else
cd "$temp_repo_dir"
git remote remove origin 2>/dev/null || true
git remote add origin "$target_repo_url"
if [[ "$FORCE_MODE" != true ]]; then
log_warn "About to FORCE PUSH to $target_platform repository!"
read -p "Continue? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_info "Push cancelled"
return 1
fi
fi
if [[ "$MAIN_BRANCH_ONLY" == true ]]; then
# Push only main branch for sync
local main_branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main")
if ! git show-ref --verify --quiet refs/heads/$main_branch; then
main_branch="master" # Fallback to master if main doesn't exist
fi
log_info "Force pushing only '$main_branch' branch..."
git push origin "$main_branch:$main_branch" --force
else
git push origin --all --force
fi
git push origin --tags --force 2>/dev/null || true
log_success "Successfully synced to $target_platform"
fi
log_success "=== Sync completed successfully ==="
log_info "Repository synced to: $target_repo_url"
}
#==============================================================================
# HELP AND USAGE
#==============================================================================
show_help() {
print_help_banner
echo
cat << EOF
Enhanced universal Git migration with flexible author handling.
Supports migration between GitHub, GitLab, BitBucket, and other Git platforms.
USAGE:
$0 [OPTIONS] COMMAND
COMMANDS:
migrate Full migration between any Git platforms
sync Sync changes between repositories
help Show this help message
AUTHOR MODES:
--preserve Preserve original commit authors (default)
--selective Change commits from specific user only
--unified Change ALL commits to one user
OPTIONS:
-i, --interactive Run in interactive mode (recommended)
-d, --dry-run Perform dry run (no actual changes)
-f, --force Force operations without confirmations
-v, --verbose Enable verbose logging
--max-size SIZE Maximum file size to keep (default: 10M)
--main-branch-only Migrate only the main branch (not all branches)
REPOSITORY OPTIONS (Universal):
--source-url URL Source repository URL (any Git platform)
--target-url URL Target repository URL (any Git platform)
--from URL Source repository URL (short form)
--to URL Target repository URL (short form)
REPOSITORY OPTIONS (Legacy):
--gitlab-url URL GitLab repository URL
--github-url URL GitHub repository URL
EXAMPLES:
# Interactive mode (recommended) - auto-detects platforms
$0 --interactive
# Universal syntax - GitHub to GitLab
$0 --preserve \\
--from "https://github.com/user/repo.git" \\
--to "https://gitlab.com/user/repo.git" \\
migrate
# Universal syntax - GitLab to BitBucket
$0 --unified \\
--source-url "https://gitlab.com/company/project.git" \\
--target-url "https://bitbucket.org/company/project.git" \\
migrate
# Legacy syntax - GitLab to GitHub
$0 --preserve \\
--gitlab-url "git@gitlab.com:user/repo.git" \\
--github-url "git@github.com:user/repo.git" \\
migrate
# Selective author change (works with any platform)
$0 --selective \\
--source-name "Old Name" --source-email "old@email.com" \\
--target-name "New Name" --target-email "new@email.com" \\
--from "https://github.com/old/repo.git" \\
--to "https://gitlab.com/new/repo.git" \\
migrate
# Dry run to test any platform combination
$0 --dry-run --unified \\
--target-name "Dev Kraken" --target-email "soman@devkraken.com" \\
--from "https://bitbucket.org/team/project.git" \\
--to "https://github.com/team/project.git" \\
migrate
# Migrate only main branch (clean migration)
$0 --preserve --main-branch-only \\
--from "https://gitlab.com/company/repo.git" \\
--to "https://github.com/company/repo.git" \\
migrate
AUTHOR HANDLING:
1. PRESERVE (default): Keep original commit authors unchanged
2. SELECTIVE: Change commits from one specific user to another
3. UNIFIED: Change ALL commits to a single author
FEATURES:
✓ Three flexible author handling modes
✓ Large file cleanup and optimization
✓ Enhanced .gitignore generation
✓ Dry run mode for safe testing
✓ Comprehensive logging
EOF
}
#==============================================================================
# MAIN EXECUTION
#==============================================================================
main() {
# Setup directories
mkdir -p "$DEFAULT_LOG_DIR"
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-i|--interactive)
INTERACTIVE_MODE=true
shift
;;
-d|--dry-run)
DRY_RUN=true
shift
;;
-f|--force)
FORCE_MODE=true
shift
;;
-v|--verbose)
VERBOSE=true
shift
;;
--preserve)
AUTHOR_MODE="preserve"
shift
;;
--selective)
AUTHOR_MODE="selective"
shift
;;
--unified)
AUTHOR_MODE="unified"
shift
;;
--main-branch-only)
MAIN_BRANCH_ONLY=true
shift
;;
--source-name)
SOURCE_AUTHOR_NAME="$2"
shift 2
;;
--source-email)
SOURCE_AUTHOR_EMAIL="$2"
shift 2
;;
--target-name)
TARGET_AUTHOR_NAME="$2"
shift 2
;;
--target-email)
TARGET_AUTHOR_EMAIL="$2"
shift 2
;;
--max-size)
MAX_FILE_SIZE="$2"
shift 2
;;
--gitlab-url)
GITLAB_URL="$2"
shift 2
;;
--github-url)
GITHUB_URL="$2"
shift 2
;;
--source-url)
SOURCE_URL="$2"
UNIVERSAL_MODE=true
shift 2
;;
--target-url)
TARGET_URL="$2"
UNIVERSAL_MODE=true
shift 2
;;
--from)
SOURCE_URL="$2"
UNIVERSAL_MODE=true
shift 2
;;
--to)
TARGET_URL="$2"
UNIVERSAL_MODE=true
shift 2
;;
migrate|sync)
OPERATION_MODE="$1"
shift
;;
help|--help|-h)
show_help
exit 0
;;
*)
log_error "Unknown argument: $1"
echo "Use '$0 help' for usage information"
exit 1
;;
esac
done
# Run interactive mode if requested or no operation specified
if [[ "$INTERACTIVE_MODE" == true ]] || [[ -z "$OPERATION_MODE" ]]; then
interactive_setup
fi
# Set URLs based on mode
if [[ "$UNIVERSAL_MODE" == true ]]; then
# Use universal URLs
if [[ -z "$SOURCE_URL" || -z "$TARGET_URL" ]]; then
log_error "Missing repository URLs for universal mode"
log_info "Use --interactive mode or provide --source-url and --target-url (or --from and --to)"
exit 1
fi
# Map to legacy variables for compatibility
GITLAB_URL="$SOURCE_URL"
GITHUB_URL="$TARGET_URL"
else
# Legacy mode validation
if [[ -z "$GITLAB_URL" || -z "$GITHUB_URL" ]]; then
log_error "Missing repository URLs"
log_info "Use --interactive mode or provide --gitlab-url and --github-url"
exit 1
fi
# Map to universal variables
SOURCE_URL="$GITLAB_URL"
TARGET_URL="$GITHUB_URL"
fi
# Validate author mode configuration
case "$AUTHOR_MODE" in
"selective")
if [[ -z "$SOURCE_AUTHOR_NAME" || -z "$SOURCE_AUTHOR_EMAIL" || -z "$TARGET_AUTHOR_NAME" || -z "$TARGET_AUTHOR_EMAIL" ]]; then
log_error "Selective mode requires source and target author information"
exit 1
fi
;;
"unified")
if [[ -z "$TARGET_AUTHOR_NAME" || -z "$TARGET_AUTHOR_EMAIL" ]]; then
log_error "Unified mode requires target author information"
exit 1
fi
;;
esac
# Check dependencies
check_dependencies || exit 1
# Create temporary directory
TEMP_DIR="${DEFAULT_TEMP_DIR}-$(date +%s)"
mkdir -p "$TEMP_DIR"
# Set up cleanup - use unique name to avoid conflict with core library
_main_cleanup() {
if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then
log_info "Cleaning up temporary directory"
rm -rf "$TEMP_DIR" 2>/dev/null || true
fi
}
trap _main_cleanup EXIT
# Execute operation
case "$OPERATION_MODE" in
migrate)
migrate_repository
;;
sync)
sync_repository
;;
*)
log_error "No operation specified"
exit 1
;;
esac
log_success "Operation completed successfully!"
if [[ "$DRY_RUN" == true ]]; then
log_info "This was a dry run - no actual changes were made"
fi
}
# Execute main function with all arguments
main "$@"