Merge branch 'jc/spht'
[git.git] / git-gui / lib / choose_repository.tcl
1 # git-gui Git repository chooser
2 # Copyright (C) 2007 Shawn Pearce
3
4 class choose_repository {
5
6 field top
7 field w
8 field w_body      ; # Widget holding the center content
9 field w_next      ; # Next button
10 field w_quit      ; # Quit button
11 field o_cons      ; # Console object (if active)
12 field w_types     ; # List of type buttons in clone
13 field w_recentlist ; # Listbox containing recent repositories
14
15 field done              0 ; # Finished picking the repository?
16 field local_path       {} ; # Where this repository is locally
17 field origin_url       {} ; # Where we are cloning from
18 field origin_name  origin ; # What we shall call 'origin'
19 field clone_type hardlink ; # Type of clone to construct
20 field readtree_err        ; # Error output from read-tree (if any)
21 field sorted_recent       ; # recent repositories (sorted)
22
23 constructor pick {} {
24         global M1T M1B
25
26         make_toplevel top w
27         wm title $top [mc "Git Gui"]
28
29         if {$top eq {.}} {
30                 menu $w.mbar -tearoff 0
31                 $top configure -menu $w.mbar
32
33                 set m_repo $w.mbar.repository
34                 $w.mbar add cascade \
35                         -label [mc Repository] \
36                         -menu $m_repo
37                 menu $m_repo
38
39                 if {[is_MacOSX]} {
40                         $w.mbar add cascade -label [mc Apple] -menu .mbar.apple
41                         menu $w.mbar.apple
42                         $w.mbar.apple add command \
43                                 -label [mc "About %s" [appname]] \
44                                 -command do_about
45                 } else {
46                         $w.mbar add cascade -label [mc Help] -menu $w.mbar.help
47                         menu $w.mbar.help
48                         $w.mbar.help add command \
49                                 -label [mc "About %s" [appname]] \
50                                 -command do_about
51                 }
52
53                 wm protocol $top WM_DELETE_WINDOW exit
54                 bind $top <$M1B-q> exit
55                 bind $top <$M1B-Q> exit
56                 bind $top <Key-Escape> exit
57         } else {
58                 wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
59                 bind $top <Key-Escape> [list destroy $top]
60                 set m_repo {}
61         }
62
63         pack [git_logo $w.git_logo] -side left -fill y -padx 10 -pady 10
64
65         set w_body $w.body
66         set opts $w_body.options
67         frame $w_body
68         text $opts \
69                 -cursor $::cursor_ptr \
70                 -relief flat \
71                 -background [$w_body cget -background] \
72                 -wrap none \
73                 -spacing1 5 \
74                 -width 50 \
75                 -height 3
76         pack $opts -anchor w -fill x
77
78         $opts tag conf link_new -foreground blue -underline 1
79         $opts tag bind link_new <1> [cb _next new]
80         $opts insert end [mc "Create New Repository"] link_new
81         $opts insert end "\n"
82         if {$m_repo ne {}} {
83                 $m_repo add command \
84                         -command [cb _next new] \
85                         -accelerator $M1T-N \
86                         -label [mc "New..."]
87                 bind $top <$M1B-n> [cb _next new]
88                 bind $top <$M1B-N> [cb _next new]
89         }
90
91         $opts tag conf link_clone -foreground blue -underline 1
92         $opts tag bind link_clone <1> [cb _next clone]
93         $opts insert end [mc "Clone Existing Repository"] link_clone
94         $opts insert end "\n"
95         if {$m_repo ne {}} {
96                 $m_repo add command \
97                         -command [cb _next clone] \
98                         -accelerator $M1T-C \
99                         -label [mc "Clone..."]
100                 bind $top <$M1B-c> [cb _next clone]
101                 bind $top <$M1B-C> [cb _next clone]
102         }
103
104         $opts tag conf link_open -foreground blue -underline 1
105         $opts tag bind link_open <1> [cb _next open]
106         $opts insert end [mc "Open Existing Repository"] link_open
107         $opts insert end "\n"
108         if {$m_repo ne {}} {
109                 $m_repo add command \
110                         -command [cb _next open] \
111                         -accelerator $M1T-O \
112                         -label [mc "Open..."]
113                 bind $top <$M1B-o> [cb _next open]
114                 bind $top <$M1B-O> [cb _next open]
115         }
116
117         $opts conf -state disabled
118
119         set sorted_recent [_get_recentrepos]
120         if {[llength $sorted_recent] > 0} {
121                 if {$m_repo ne {}} {
122                         $m_repo add separator
123                         $m_repo add command \
124                                 -state disabled \
125                                 -label [mc "Recent Repositories"]
126                 }
127
128                 label $w_body.space
129                 label $w_body.recentlabel \
130                         -anchor w \
131                         -text [mc "Open Recent Repository:"]
132                 set w_recentlist $w_body.recentlist
133                 text $w_recentlist \
134                         -cursor $::cursor_ptr \
135                         -relief flat \
136                         -background [$w_body.recentlabel cget -background] \
137                         -wrap none \
138                         -width 50 \
139                         -height 10
140                 $w_recentlist tag conf link \
141                         -foreground blue \
142                         -underline 1
143                 set home $::env(HOME)
144                 if {[is_Cygwin]} {
145                         set home [exec cygpath --windows --absolute $home]
146                 }
147                 set home "[file normalize $home]/"
148                 set hlen [string length $home]
149                 foreach p $sorted_recent {
150                         set path $p
151                         if {[string equal -length $hlen $home $p]} {
152                                 set p "~/[string range $p $hlen end]"
153                         }
154                         regsub -all "\n" $p "\\n" p
155                         $w_recentlist insert end $p link
156                         $w_recentlist insert end "\n"
157
158                         if {$m_repo ne {}} {
159                                 $m_repo add command \
160                                         -command [cb _open_recent_path $path] \
161                                         -label "    $p"
162                         }
163                 }
164                 $w_recentlist conf -state disabled
165                 $w_recentlist tag bind link <1> [cb _open_recent %x,%y]
166                 pack $w_body.space -anchor w -fill x
167                 pack $w_body.recentlabel -anchor w -fill x
168                 pack $w_recentlist -anchor w -fill x
169         }
170         pack $w_body -fill x -padx 10 -pady 10
171
172         frame $w.buttons
173         set w_next $w.buttons.next
174         set w_quit $w.buttons.quit
175         button $w_quit \
176                 -text [mc "Quit"] \
177                 -command exit
178         pack $w_quit -side right -padx 5
179         pack $w.buttons -side bottom -fill x -padx 10 -pady 10
180
181         if {$m_repo ne {}} {
182                 $m_repo add separator
183                 $m_repo add command \
184                         -label [mc Quit] \
185                         -command exit \
186                         -accelerator $M1T-Q
187         }
188
189         bind $top <Return> [cb _invoke_next]
190         bind $top <Visibility> "
191                 [cb _center]
192                 grab $top
193                 focus $top
194                 bind $top <Visibility> {}
195         "
196         wm deiconify $top
197         tkwait variable @done
198
199         if {$top eq {.}} {
200                 eval destroy [winfo children $top]
201         }
202 }
203
204 proc _home {} {
205         if {[catch {set h $::env(HOME)}]
206                 || ![file isdirectory $h]} {
207                 set h .
208         }
209         return $h
210 }
211
212 method _center {} {
213         set nx [winfo reqwidth $top]
214         set ny [winfo reqheight $top]
215         set rx [expr {([winfo screenwidth  $top] - $nx) / 3}]
216         set ry [expr {([winfo screenheight $top] - $ny) / 3}]
217         wm geometry $top [format {+%d+%d} $rx $ry]
218 }
219
220 method _invoke_next {} {
221         if {[winfo exists $w_next]} {
222                 uplevel #0 [$w_next cget -command]
223         }
224 }
225
226 proc _get_recentrepos {} {
227         set recent [list]
228         foreach p [get_config gui.recentrepo] {
229                 if {[_is_git [file join $p .git]]} {
230                         lappend recent $p
231                 }
232         }
233         return [lsort $recent]
234 }
235
236 proc _unset_recentrepo {p} {
237         regsub -all -- {([()\[\]{}\.^$+*?\\])} $p {\\\1} p
238         git config --global --unset gui.recentrepo "^$p\$"
239 }
240
241 proc _append_recentrepos {path} {
242         set path [file normalize $path]
243         set recent [get_config gui.recentrepo]
244
245         if {[lindex $recent end] eq $path} {
246                 return
247         }
248
249         set i [lsearch $recent $path]
250         if {$i >= 0} {
251                 _unset_recentrepo $path
252                 set recent [lreplace $recent $i $i]
253         }
254
255         lappend recent $path
256         git config --global --add gui.recentrepo $path
257
258         while {[llength $recent] > 10} {
259                 _unset_recentrepo [lindex $recent 0]
260                 set recent [lrange $recent 1 end]
261         }
262 }
263
264 method _open_recent {xy} {
265         set id [lindex [split [$w_recentlist index @$xy] .] 0]
266         set local_path [lindex $sorted_recent [expr {$id - 1}]]
267         _do_open2 $this
268 }
269
270 method _open_recent_path {p} {
271         set local_path $p
272         _do_open2 $this
273 }
274
275 method _next {action} {
276         destroy $w_body
277         if {![winfo exists $w_next]} {
278                 button $w_next -default active
279                 pack $w_next -side right -padx 5 -before $w_quit
280         }
281         _do_$action $this
282 }
283
284 method _write_local_path {args} {
285         if {$local_path eq {}} {
286                 $w_next conf -state disabled
287         } else {
288                 $w_next conf -state normal
289         }
290 }
291
292 method _git_init {} {
293         if {[file exists $local_path]} {
294                 error_popup [mc "Location %s already exists." $local_path]
295                 return 0
296         }
297
298         if {[catch {file mkdir $local_path} err]} {
299                 error_popup [strcat \
300                         [mc "Failed to create repository %s:" $local_path] \
301                         "\n\n$err"]
302                 return 0
303         }
304
305         if {[catch {cd $local_path} err]} {
306                 error_popup [strcat \
307                         [mc "Failed to create repository %s:" $local_path] \
308                         "\n\n$err"]
309                 return 0
310         }
311
312         if {[catch {git init} err]} {
313                 error_popup [strcat \
314                         [mc "Failed to create repository %s:" $local_path] \
315                         "\n\n$err"]
316                 return 0
317         }
318
319         _append_recentrepos [pwd]
320         set ::_gitdir .git
321         set ::_prefix {}
322         return 1
323 }
324
325 proc _is_git {path} {
326         if {[file exists [file join $path HEAD]]
327          && [file exists [file join $path objects]]
328          && [file exists [file join $path config]]} {
329                 return 1
330         }
331         if {[is_Cygwin]} {
332                 if {[file exists [file join $path HEAD]]
333                  && [file exists [file join $path objects.lnk]]
334                  && [file exists [file join $path config.lnk]]} {
335                         return 1
336                 }
337         }
338         return 0
339 }
340
341 proc _objdir {path} {
342         set objdir [file join $path .git objects]
343         if {[file isdirectory $objdir]} {
344                 return $objdir
345         }
346
347         set objdir [file join $path objects]
348         if {[file isdirectory $objdir]} {
349                 return $objdir
350         }
351
352         if {[is_Cygwin]} {
353                 set objdir [file join $path .git objects.lnk]
354                 if {[file isfile $objdir]} {
355                         return [win32_read_lnk $objdir]
356                 }
357
358                 set objdir [file join $path objects.lnk]
359                 if {[file isfile $objdir]} {
360                         return [win32_read_lnk $objdir]
361                 }
362         }
363
364         return {}
365 }
366
367 ######################################################################
368 ##
369 ## Create New Repository
370
371 method _do_new {} {
372         $w_next conf \
373                 -state disabled \
374                 -command [cb _do_new2] \
375                 -text [mc "Create"]
376
377         frame $w_body
378         label $w_body.h \
379                 -font font_uibold \
380                 -text [mc "Create New Repository"]
381         pack $w_body.h -side top -fill x -pady 10
382         pack $w_body -fill x -padx 10
383
384         frame $w_body.where
385         label $w_body.where.l -text [mc "Directory:"]
386         entry $w_body.where.t \
387                 -textvariable @local_path \
388                 -font font_diff \
389                 -width 50
390         button $w_body.where.b \
391                 -text [mc "Browse"] \
392                 -command [cb _new_local_path]
393
394         pack $w_body.where.b -side right
395         pack $w_body.where.l -side left
396         pack $w_body.where.t -fill x
397         pack $w_body.where -fill x
398
399         trace add variable @local_path write [cb _write_local_path]
400         bind $w_body.h <Destroy> [list trace remove variable @local_path write [cb _write_local_path]]
401         update
402         focus $w_body.where.t
403 }
404
405 method _new_local_path {} {
406         if {$local_path ne {}} {
407                 set p [file dirname $local_path]
408         } else {
409                 set p [_home]
410         }
411
412         set p [tk_chooseDirectory \
413                 -initialdir $p \
414                 -parent $top \
415                 -title [mc "Git Repository"] \
416                 -mustexist false]
417         if {$p eq {}} return
418
419         set p [file normalize $p]
420         if {[file isdirectory $p]} {
421                 foreach i [glob \
422                         -directory $p \
423                         -tails \
424                         -nocomplain \
425                         * .*] {
426                         switch -- $i {
427                          . continue
428                         .. continue
429                         default {
430                                 error_popup [mc "Directory %s already exists." $p]
431                                 return
432                         }
433                         }
434                 }
435                 if {[catch {file delete $p} err]} {
436                         error_popup [strcat \
437                                 [mc "Directory %s already exists." $p] \
438                                 "\n\n$err"]
439                         return
440                 }
441         } elseif {[file exists $p]} {
442                 error_popup [mc "File %s already exists." $p]
443                 return
444         }
445         set local_path $p
446 }
447
448 method _do_new2 {} {
449         if {![_git_init $this]} {
450                 return
451         }
452         set done 1
453 }
454
455 ######################################################################
456 ##
457 ## Clone Existing Repository
458
459 method _do_clone {} {
460         $w_next conf \
461                 -state disabled \
462                 -command [cb _do_clone2] \
463                 -text [mc "Clone"]
464
465         frame $w_body
466         label $w_body.h \
467                 -font font_uibold \
468                 -text [mc "Clone Existing Repository"]
469         pack $w_body.h -side top -fill x -pady 10
470         pack $w_body -fill x -padx 10
471
472         set args $w_body.args
473         frame $w_body.args
474         pack $args -fill both
475
476         label $args.origin_l -text [mc "URL:"]
477         entry $args.origin_t \
478                 -textvariable @origin_url \
479                 -font font_diff \
480                 -width 50
481         button $args.origin_b \
482                 -text [mc "Browse"] \
483                 -command [cb _open_origin]
484         grid $args.origin_l $args.origin_t $args.origin_b -sticky ew
485
486         label $args.where_l -text [mc "Directory:"]
487         entry $args.where_t \
488                 -textvariable @local_path \
489                 -font font_diff \
490                 -width 50
491         button $args.where_b \
492                 -text [mc "Browse"] \
493                 -command [cb _new_local_path]
494         grid $args.where_l $args.where_t $args.where_b -sticky ew
495
496         label $args.type_l -text [mc "Clone Type:"]
497         frame $args.type_f
498         set w_types [list]
499         lappend w_types [radiobutton $args.type_f.hardlink \
500                 -state disabled \
501                 -anchor w \
502                 -text [mc "Standard (Fast, Semi-Redundant, Hardlinks)"] \
503                 -variable @clone_type \
504                 -value hardlink]
505         lappend w_types [radiobutton $args.type_f.full \
506                 -state disabled \
507                 -anchor w \
508                 -text [mc "Full Copy (Slower, Redundant Backup)"] \
509                 -variable @clone_type \
510                 -value full]
511         lappend w_types [radiobutton $args.type_f.shared \
512                 -state disabled \
513                 -anchor w \
514                 -text [mc "Shared (Fastest, Not Recommended, No Backup)"] \
515                 -variable @clone_type \
516                 -value shared]
517         foreach r $w_types {
518                 pack $r -anchor w
519         }
520         grid $args.type_l $args.type_f -sticky new
521
522         grid columnconfigure $args 1 -weight 1
523
524         trace add variable @local_path write [cb _update_clone]
525         trace add variable @origin_url write [cb _update_clone]
526         bind $w_body.h <Destroy> "
527                 [list trace remove variable @local_path write [cb _update_clone]]
528                 [list trace remove variable @origin_url write [cb _update_clone]]
529         "
530         update
531         focus $args.origin_t
532 }
533
534 method _open_origin {} {
535         if {$origin_url ne {} && [file isdirectory $origin_url]} {
536                 set p $origin_url
537         } else {
538                 set p [_home]
539         }
540
541         set p [tk_chooseDirectory \
542                 -initialdir $p \
543                 -parent $top \
544                 -title [mc "Git Repository"] \
545                 -mustexist true]
546         if {$p eq {}} return
547
548         set p [file normalize $p]
549         if {![_is_git [file join $p .git]] && ![_is_git $p]} {
550                 error_popup [mc "Not a Git repository: %s" [file tail $p]]
551                 return
552         }
553         set origin_url $p
554 }
555
556 method _update_clone {args} {
557         if {$local_path ne {} && $origin_url ne {}} {
558                 $w_next conf -state normal
559         } else {
560                 $w_next conf -state disabled
561         }
562
563         if {$origin_url ne {} &&
564                 (  [_is_git [file join $origin_url .git]]
565                 || [_is_git $origin_url])} {
566                 set e normal
567                 if {[[lindex $w_types 0] cget -state] eq {disabled}} {
568                         set clone_type hardlink
569                 }
570         } else {
571                 set e disabled
572                 set clone_type full
573         }
574
575         foreach r $w_types {
576                 $r conf -state $e
577         }
578 }
579
580 method _do_clone2 {} {
581         if {[file isdirectory $origin_url]} {
582                 set origin_url [file normalize $origin_url]
583         }
584
585         if {$clone_type eq {hardlink} && ![file isdirectory $origin_url]} {
586                 error_popup [mc "Standard only available for local repository."]
587                 return
588         }
589         if {$clone_type eq {shared} && ![file isdirectory $origin_url]} {
590                 error_popup [mc "Shared only available for local repository."]
591                 return
592         }
593
594         if {$clone_type eq {hardlink} || $clone_type eq {shared}} {
595                 set objdir [_objdir $origin_url]
596                 if {$objdir eq {}} {
597                         error_popup [mc "Not a Git repository: %s" [file tail $origin_url]]
598                         return
599                 }
600         }
601
602         set giturl $origin_url
603         if {[is_Cygwin] && [file isdirectory $giturl]} {
604                 set giturl [exec cygpath --unix --absolute $giturl]
605                 if {$clone_type eq {shared}} {
606                         set objdir [exec cygpath --unix --absolute $objdir]
607                 }
608         }
609
610         if {![_git_init $this]} return
611         set local_path [pwd]
612
613         if {[catch {
614                         git config remote.$origin_name.url $giturl
615                         git config remote.$origin_name.fetch +refs/heads/*:refs/remotes/$origin_name/*
616                 } err]} {
617                 error_popup [strcat [mc "Failed to configure origin"] "\n\n$err"]
618                 return
619         }
620
621         destroy $w_body $w_next
622
623         switch -exact -- $clone_type {
624         hardlink {
625                 set o_cons [status_bar::two_line $w_body]
626                 pack $w_body -fill x -padx 10 -pady 10
627
628                 $o_cons start \
629                         [mc "Counting objects"] \
630                         [mc "buckets"]
631                 update
632
633                 if {[file exists [file join $objdir info alternates]]} {
634                         set pwd [pwd]
635                         if {[catch {
636                                 file mkdir [gitdir objects info]
637                                 set f_in [open [file join $objdir info alternates] r]
638                                 set f_cp [open [gitdir objects info alternates] w]
639                                 fconfigure $f_in -translation binary -encoding binary
640                                 fconfigure $f_cp -translation binary -encoding binary
641                                 cd $objdir
642                                 while {[gets $f_in line] >= 0} {
643                                         if {[is_Cygwin]} {
644                                                 puts $f_cp [exec cygpath --unix --absolute $line]
645                                         } else {
646                                                 puts $f_cp [file normalize $line]
647                                         }
648                                 }
649                                 close $f_in
650                                 close $f_cp
651                                 cd $pwd
652                         } err]} {
653                                 catch {cd $pwd}
654                                 _clone_failed $this [mc "Unable to copy objects/info/alternates: %s" $err]
655                                 return
656                         }
657                 }
658
659                 set tolink  [list]
660                 set buckets [glob \
661                         -tails \
662                         -nocomplain \
663                         -directory [file join $objdir] ??]
664                 set bcnt [expr {[llength $buckets] + 2}]
665                 set bcur 1
666                 $o_cons update $bcur $bcnt
667                 update
668
669                 file mkdir [file join .git objects pack]
670                 foreach i [glob -tails -nocomplain \
671                         -directory [file join $objdir pack] *] {
672                         lappend tolink [file join pack $i]
673                 }
674                 $o_cons update [incr bcur] $bcnt
675                 update
676
677                 foreach i $buckets {
678                         file mkdir [file join .git objects $i]
679                         foreach j [glob -tails -nocomplain \
680                                 -directory [file join $objdir $i] *] {
681                                 lappend tolink [file join $i $j]
682                         }
683                         $o_cons update [incr bcur] $bcnt
684                         update
685                 }
686                 $o_cons stop
687
688                 if {$tolink eq {}} {
689                         info_popup [strcat \
690                                 [mc "Nothing to clone from %s." $origin_url] \
691                                 "\n" \
692                                 [mc "The 'master' branch has not been initialized."] \
693                                 ]
694                         destroy $w_body
695                         set done 1
696                         return
697                 }
698
699                 set i [lindex $tolink 0]
700                 if {[catch {
701                                 file link -hard \
702                                         [file join .git objects $i] \
703                                         [file join $objdir $i]
704                         } err]} {
705                         info_popup [mc "Hardlinks are unavailable.  Falling back to copying."]
706                         set i [_copy_files $this $objdir $tolink]
707                 } else {
708                         set i [_link_files $this $objdir [lrange $tolink 1 end]]
709                 }
710                 if {!$i} return
711
712                 destroy $w_body
713         }
714         full {
715                 set o_cons [console::embed \
716                         $w_body \
717                         [mc "Cloning from %s" $origin_url]]
718                 pack $w_body -fill both -expand 1 -padx 10
719                 $o_cons exec \
720                         [list git fetch --no-tags -k $origin_name] \
721                         [cb _do_clone_tags]
722         }
723         shared {
724                 set fd [open [gitdir objects info alternates] w]
725                 fconfigure $fd -translation binary
726                 puts $fd $objdir
727                 close $fd
728         }
729         }
730
731         if {$clone_type eq {hardlink} || $clone_type eq {shared}} {
732                 if {![_clone_refs $this]} return
733                 set pwd [pwd]
734                 if {[catch {
735                                 cd $origin_url
736                                 set HEAD [git rev-parse --verify HEAD^0]
737                         } err]} {
738                         _clone_failed $this [mc "Not a Git repository: %s" [file tail $origin_url]]
739                         return 0
740                 }
741                 cd $pwd
742                 _do_clone_checkout $this $HEAD
743         }
744 }
745
746 method _copy_files {objdir tocopy} {
747         $o_cons start \
748                 [mc "Copying objects"] \
749                 [mc "KiB"]
750         set tot 0
751         set cmp 0
752         foreach p $tocopy {
753                 incr tot [file size [file join $objdir $p]]
754         }
755         foreach p $tocopy {
756                 if {[catch {
757                                 set f_in [open [file join $objdir $p] r]
758                                 set f_cp [open [file join .git objects $p] w]
759                                 fconfigure $f_in -translation binary -encoding binary
760                                 fconfigure $f_cp -translation binary -encoding binary
761
762                                 while {![eof $f_in]} {
763                                         incr cmp [fcopy $f_in $f_cp -size 16384]
764                                         $o_cons update \
765                                                 [expr {$cmp / 1024}] \
766                                                 [expr {$tot / 1024}]
767                                         update
768                                 }
769
770                                 close $f_in
771                                 close $f_cp
772                         } err]} {
773                         _clone_failed $this [mc "Unable to copy object: %s" $err]
774                         return 0
775                 }
776         }
777         return 1
778 }
779
780 method _link_files {objdir tolink} {
781         set total [llength $tolink]
782         $o_cons start \
783                 [mc "Linking objects"] \
784                 [mc "objects"]
785         for {set i 0} {$i < $total} {} {
786                 set p [lindex $tolink $i]
787                 if {[catch {
788                                 file link -hard \
789                                         [file join .git objects $p] \
790                                         [file join $objdir $p]
791                         } err]} {
792                         _clone_failed $this [mc "Unable to hardlink object: %s" $err]
793                         return 0
794                 }
795
796                 incr i
797                 if {$i % 5 == 0} {
798                         $o_cons update $i $total
799                         update
800                 }
801         }
802         return 1
803 }
804
805 method _clone_refs {} {
806         set pwd [pwd]
807         if {[catch {cd $origin_url} err]} {
808                 error_popup [mc "Not a Git repository: %s" [file tail $origin_url]]
809                 return 0
810         }
811         set fd_in [git_read for-each-ref \
812                 --tcl \
813                 {--format=list %(refname) %(objectname) %(*objectname)}]
814         cd $pwd
815
816         set fd [open [gitdir packed-refs] w]
817         fconfigure $fd -translation binary
818         puts $fd "# pack-refs with: peeled"
819         while {[gets $fd_in line] >= 0} {
820                 set line [eval $line]
821                 set refn [lindex $line 0]
822                 set robj [lindex $line 1]
823                 set tobj [lindex $line 2]
824
825                 if {[regsub ^refs/heads/ $refn \
826                         "refs/remotes/$origin_name/" refn]} {
827                         puts $fd "$robj $refn"
828                 } elseif {[string match refs/tags/* $refn]} {
829                         puts $fd "$robj $refn"
830                         if {$tobj ne {}} {
831                                 puts $fd "^$tobj"
832                         }
833                 }
834         }
835         close $fd_in
836         close $fd
837         return 1
838 }
839
840 method _do_clone_tags {ok} {
841         if {$ok} {
842                 $o_cons exec \
843                         [list git fetch --tags -k $origin_name] \
844                         [cb _do_clone_HEAD]
845         } else {
846                 $o_cons done $ok
847                 _clone_failed $this [mc "Cannot fetch branches and objects.  See console output for details."]
848         }
849 }
850
851 method _do_clone_HEAD {ok} {
852         if {$ok} {
853                 $o_cons exec \
854                         [list git fetch $origin_name HEAD] \
855                         [cb _do_clone_full_end]
856         } else {
857                 $o_cons done $ok
858                 _clone_failed $this [mc "Cannot fetch tags.  See console output for details."]
859         }
860 }
861
862 method _do_clone_full_end {ok} {
863         $o_cons done $ok
864
865         if {$ok} {
866                 destroy $w_body
867
868                 set HEAD {}
869                 if {[file exists [gitdir FETCH_HEAD]]} {
870                         set fd [open [gitdir FETCH_HEAD] r]
871                         while {[gets $fd line] >= 0} {
872                                 if {[regexp "^(.{40})\t\t" $line line HEAD]} {
873                                         break
874                                 }
875                         }
876                         close $fd
877                 }
878
879                 catch {git pack-refs}
880                 _do_clone_checkout $this $HEAD
881         } else {
882                 _clone_failed $this [mc "Cannot determine HEAD.  See console output for details."]
883         }
884 }
885
886 method _clone_failed {{why {}}} {
887         if {[catch {file delete -force $local_path} err]} {
888                 set why [strcat \
889                         $why \
890                         "\n\n" \
891                         [mc "Unable to cleanup %s" $local_path] \
892                         "\n\n" \
893                         $err]
894         }
895         if {$why ne {}} {
896                 update
897                 error_popup [strcat [mc "Clone failed."] "\n" $why]
898         }
899 }
900
901 method _do_clone_checkout {HEAD} {
902         if {$HEAD eq {}} {
903                 info_popup [strcat \
904                         [mc "No default branch obtained."] \
905                         "\n" \
906                         [mc "The 'master' branch has not been initialized."] \
907                         ]
908                 set done 1
909                 return
910         }
911         if {[catch {
912                         git update-ref HEAD $HEAD^0
913                 } err]} {
914                 info_popup [strcat \
915                         [mc "Cannot resolve %s as a commit." $HEAD^0] \
916                         "\n  $err" \
917                         "\n" \
918                         [mc "The 'master' branch has not been initialized."] \
919                         ]
920                 set done 1
921                 return
922         }
923
924         set o_cons [status_bar::two_line $w_body]
925         pack $w_body -fill x -padx 10 -pady 10
926         $o_cons start \
927                 [mc "Creating working directory"] \
928                 [mc "files"]
929
930         set readtree_err {}
931         set fd [git_read --stderr read-tree \
932                 -m \
933                 -u \
934                 -v \
935                 HEAD \
936                 HEAD \
937                 ]
938         fconfigure $fd -blocking 0 -translation binary
939         fileevent $fd readable [cb _readtree_wait $fd]
940 }
941
942 method _readtree_wait {fd} {
943         set buf [read $fd]
944         $o_cons update_meter $buf
945         append readtree_err $buf
946
947         fconfigure $fd -blocking 1
948         if {![eof $fd]} {
949                 fconfigure $fd -blocking 0
950                 return
951         }
952
953         if {[catch {close $fd}]} {
954                 set err $readtree_err
955                 regsub {^fatal: } $err {} err
956                 error_popup [strcat \
957                         [mc "Initial file checkout failed."] \
958                         "\n\n$err"]
959                 return
960         }
961
962         set done 1
963 }
964
965 ######################################################################
966 ##
967 ## Open Existing Repository
968
969 method _do_open {} {
970         $w_next conf \
971                 -state disabled \
972                 -command [cb _do_open2] \
973                 -text [mc "Open"]
974
975         frame $w_body
976         label $w_body.h \
977                 -font font_uibold \
978                 -text [mc "Open Existing Repository"]
979         pack $w_body.h -side top -fill x -pady 10
980         pack $w_body -fill x -padx 10
981
982         frame $w_body.where
983         label $w_body.where.l -text [mc "Repository:"]
984         entry $w_body.where.t \
985                 -textvariable @local_path \
986                 -font font_diff \
987                 -width 50
988         button $w_body.where.b \
989                 -text [mc "Browse"] \
990                 -command [cb _open_local_path]
991
992         pack $w_body.where.b -side right
993         pack $w_body.where.l -side left
994         pack $w_body.where.t -fill x
995         pack $w_body.where -fill x
996
997         trace add variable @local_path write [cb _write_local_path]
998         bind $w_body.h <Destroy> [list trace remove variable @local_path write [cb _write_local_path]]
999         update
1000         focus $w_body.where.t
1001 }
1002
1003 method _open_local_path {} {
1004         if {$local_path ne {}} {
1005                 set p $local_path
1006         } else {
1007                 set p [_home]
1008         }
1009
1010         set p [tk_chooseDirectory \
1011                 -initialdir $p \
1012                 -parent $top \
1013                 -title [mc "Git Repository"] \
1014                 -mustexist true]
1015         if {$p eq {}} return
1016
1017         set p [file normalize $p]
1018         if {![_is_git [file join $p .git]]} {
1019                 error_popup [mc "Not a Git repository: %s" [file tail $p]]
1020                 return
1021         }
1022         set local_path $p
1023 }
1024
1025 method _do_open2 {} {
1026         if {![_is_git [file join $local_path .git]]} {
1027                 error_popup [mc "Not a Git repository: %s" [file tail $local_path]]
1028                 return
1029         }
1030
1031         if {[catch {cd $local_path} err]} {
1032                 error_popup [strcat \
1033                         [mc "Failed to open repository %s:" $local_path] \
1034                         "\n\n$err"]
1035                 return
1036         }
1037
1038         _append_recentrepos [pwd]
1039         set ::_gitdir .git
1040         set ::_prefix {}
1041         set done 1
1042 }
1043
1044 }