git-gui: Allow 'Create New Repository' on existing directories
[git] / 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 {[catch {file mkdir $local_path} err]} {
294                 error_popup [strcat \
295                         [mc "Failed to create repository %s:" $local_path] \
296                         "\n\n$err"]
297                 return 0
298         }
299
300         if {[catch {cd $local_path} err]} {
301                 error_popup [strcat \
302                         [mc "Failed to create repository %s:" $local_path] \
303                         "\n\n$err"]
304                 return 0
305         }
306
307         if {[catch {git init} err]} {
308                 error_popup [strcat \
309                         [mc "Failed to create repository %s:" $local_path] \
310                         "\n\n$err"]
311                 return 0
312         }
313
314         _append_recentrepos [pwd]
315         set ::_gitdir .git
316         set ::_prefix {}
317         return 1
318 }
319
320 proc _is_git {path} {
321         if {[file exists [file join $path HEAD]]
322          && [file exists [file join $path objects]]
323          && [file exists [file join $path config]]} {
324                 return 1
325         }
326         if {[is_Cygwin]} {
327                 if {[file exists [file join $path HEAD]]
328                  && [file exists [file join $path objects.lnk]]
329                  && [file exists [file join $path config.lnk]]} {
330                         return 1
331                 }
332         }
333         return 0
334 }
335
336 proc _objdir {path} {
337         set objdir [file join $path .git objects]
338         if {[file isdirectory $objdir]} {
339                 return $objdir
340         }
341
342         set objdir [file join $path objects]
343         if {[file isdirectory $objdir]} {
344                 return $objdir
345         }
346
347         if {[is_Cygwin]} {
348                 set objdir [file join $path .git objects.lnk]
349                 if {[file isfile $objdir]} {
350                         return [win32_read_lnk $objdir]
351                 }
352
353                 set objdir [file join $path objects.lnk]
354                 if {[file isfile $objdir]} {
355                         return [win32_read_lnk $objdir]
356                 }
357         }
358
359         return {}
360 }
361
362 ######################################################################
363 ##
364 ## Create New Repository
365
366 method _do_new {} {
367         $w_next conf \
368                 -state disabled \
369                 -command [cb _do_new2] \
370                 -text [mc "Create"]
371
372         frame $w_body
373         label $w_body.h \
374                 -font font_uibold \
375                 -text [mc "Create New Repository"]
376         pack $w_body.h -side top -fill x -pady 10
377         pack $w_body -fill x -padx 10
378
379         frame $w_body.where
380         label $w_body.where.l -text [mc "Directory:"]
381         entry $w_body.where.t \
382                 -textvariable @local_path \
383                 -font font_diff \
384                 -width 50
385         button $w_body.where.b \
386                 -text [mc "Browse"] \
387                 -command [cb _new_local_path]
388
389         pack $w_body.where.b -side right
390         pack $w_body.where.l -side left
391         pack $w_body.where.t -fill x
392         pack $w_body.where -fill x
393
394         trace add variable @local_path write [cb _write_local_path]
395         bind $w_body.h <Destroy> [list trace remove variable @local_path write [cb _write_local_path]]
396         update
397         focus $w_body.where.t
398 }
399
400 method _new_local_path {} {
401         if {$local_path ne {}} {
402                 set p [file dirname $local_path]
403         } else {
404                 set p [_home]
405         }
406
407         set p [tk_chooseDirectory \
408                 -initialdir $p \
409                 -parent $top \
410                 -title [mc "Git Repository"] \
411                 -mustexist false]
412         if {$p eq {}} return
413
414         set p [file normalize $p]
415         if {![_new_ok $p]} {
416                 return
417         }
418         set local_path $p
419 }
420
421 method _do_new2 {} {
422         if {![_new_ok $local_path]} {
423                 return
424         }
425         if {![_git_init $this]} {
426                 return
427         }
428         set done 1
429 }
430
431 proc _new_ok {p} {
432         if {[file isdirectory $p]} {
433                 if {[_is_git [file join $p .git]]} {
434                         error_popup [mc "Directory %s already exists." $p]
435                         return 0
436                 }
437         } elseif {[file exists $p]} {
438                 error_popup [mc "File %s already exists." $p]
439                 return 0
440         }
441         return 1
442 }
443
444 ######################################################################
445 ##
446 ## Clone Existing Repository
447
448 method _do_clone {} {
449         $w_next conf \
450                 -state disabled \
451                 -command [cb _do_clone2] \
452                 -text [mc "Clone"]
453
454         frame $w_body
455         label $w_body.h \
456                 -font font_uibold \
457                 -text [mc "Clone Existing Repository"]
458         pack $w_body.h -side top -fill x -pady 10
459         pack $w_body -fill x -padx 10
460
461         set args $w_body.args
462         frame $w_body.args
463         pack $args -fill both
464
465         label $args.origin_l -text [mc "URL:"]
466         entry $args.origin_t \
467                 -textvariable @origin_url \
468                 -font font_diff \
469                 -width 50
470         button $args.origin_b \
471                 -text [mc "Browse"] \
472                 -command [cb _open_origin]
473         grid $args.origin_l $args.origin_t $args.origin_b -sticky ew
474
475         label $args.where_l -text [mc "Directory:"]
476         entry $args.where_t \
477                 -textvariable @local_path \
478                 -font font_diff \
479                 -width 50
480         button $args.where_b \
481                 -text [mc "Browse"] \
482                 -command [cb _new_local_path]
483         grid $args.where_l $args.where_t $args.where_b -sticky ew
484
485         label $args.type_l -text [mc "Clone Type:"]
486         frame $args.type_f
487         set w_types [list]
488         lappend w_types [radiobutton $args.type_f.hardlink \
489                 -state disabled \
490                 -anchor w \
491                 -text [mc "Standard (Fast, Semi-Redundant, Hardlinks)"] \
492                 -variable @clone_type \
493                 -value hardlink]
494         lappend w_types [radiobutton $args.type_f.full \
495                 -state disabled \
496                 -anchor w \
497                 -text [mc "Full Copy (Slower, Redundant Backup)"] \
498                 -variable @clone_type \
499                 -value full]
500         lappend w_types [radiobutton $args.type_f.shared \
501                 -state disabled \
502                 -anchor w \
503                 -text [mc "Shared (Fastest, Not Recommended, No Backup)"] \
504                 -variable @clone_type \
505                 -value shared]
506         foreach r $w_types {
507                 pack $r -anchor w
508         }
509         grid $args.type_l $args.type_f -sticky new
510
511         grid columnconfigure $args 1 -weight 1
512
513         trace add variable @local_path write [cb _update_clone]
514         trace add variable @origin_url write [cb _update_clone]
515         bind $w_body.h <Destroy> "
516                 [list trace remove variable @local_path write [cb _update_clone]]
517                 [list trace remove variable @origin_url write [cb _update_clone]]
518         "
519         update
520         focus $args.origin_t
521 }
522
523 method _open_origin {} {
524         if {$origin_url ne {} && [file isdirectory $origin_url]} {
525                 set p $origin_url
526         } else {
527                 set p [_home]
528         }
529
530         set p [tk_chooseDirectory \
531                 -initialdir $p \
532                 -parent $top \
533                 -title [mc "Git Repository"] \
534                 -mustexist true]
535         if {$p eq {}} return
536
537         set p [file normalize $p]
538         if {![_is_git [file join $p .git]] && ![_is_git $p]} {
539                 error_popup [mc "Not a Git repository: %s" [file tail $p]]
540                 return
541         }
542         set origin_url $p
543 }
544
545 method _update_clone {args} {
546         if {$local_path ne {} && $origin_url ne {}} {
547                 $w_next conf -state normal
548         } else {
549                 $w_next conf -state disabled
550         }
551
552         if {$origin_url ne {} &&
553                 (  [_is_git [file join $origin_url .git]]
554                 || [_is_git $origin_url])} {
555                 set e normal
556                 if {[[lindex $w_types 0] cget -state] eq {disabled}} {
557                         set clone_type hardlink
558                 }
559         } else {
560                 set e disabled
561                 set clone_type full
562         }
563
564         foreach r $w_types {
565                 $r conf -state $e
566         }
567 }
568
569 method _do_clone2 {} {
570         if {[file isdirectory $origin_url]} {
571                 set origin_url [file normalize $origin_url]
572         }
573
574         if {$clone_type eq {hardlink} && ![file isdirectory $origin_url]} {
575                 error_popup [mc "Standard only available for local repository."]
576                 return
577         }
578         if {$clone_type eq {shared} && ![file isdirectory $origin_url]} {
579                 error_popup [mc "Shared only available for local repository."]
580                 return
581         }
582
583         if {$clone_type eq {hardlink} || $clone_type eq {shared}} {
584                 set objdir [_objdir $origin_url]
585                 if {$objdir eq {}} {
586                         error_popup [mc "Not a Git repository: %s" [file tail $origin_url]]
587                         return
588                 }
589         }
590
591         set giturl $origin_url
592         if {[is_Cygwin] && [file isdirectory $giturl]} {
593                 set giturl [exec cygpath --unix --absolute $giturl]
594                 if {$clone_type eq {shared}} {
595                         set objdir [exec cygpath --unix --absolute $objdir]
596                 }
597         }
598
599         if {[file exists $local_path]} {
600                 error_popup [mc "Location %s already exists." $local_path]
601                 return
602         }
603
604         if {![_git_init $this]} return
605         set local_path [pwd]
606
607         if {[catch {
608                         git config remote.$origin_name.url $giturl
609                         git config remote.$origin_name.fetch +refs/heads/*:refs/remotes/$origin_name/*
610                 } err]} {
611                 error_popup [strcat [mc "Failed to configure origin"] "\n\n$err"]
612                 return
613         }
614
615         destroy $w_body $w_next
616
617         switch -exact -- $clone_type {
618         hardlink {
619                 set o_cons [status_bar::two_line $w_body]
620                 pack $w_body -fill x -padx 10 -pady 10
621
622                 $o_cons start \
623                         [mc "Counting objects"] \
624                         [mc "buckets"]
625                 update
626
627                 if {[file exists [file join $objdir info alternates]]} {
628                         set pwd [pwd]
629                         if {[catch {
630                                 file mkdir [gitdir objects info]
631                                 set f_in [open [file join $objdir info alternates] r]
632                                 set f_cp [open [gitdir objects info alternates] w]
633                                 fconfigure $f_in -translation binary -encoding binary
634                                 fconfigure $f_cp -translation binary -encoding binary
635                                 cd $objdir
636                                 while {[gets $f_in line] >= 0} {
637                                         if {[is_Cygwin]} {
638                                                 puts $f_cp [exec cygpath --unix --absolute $line]
639                                         } else {
640                                                 puts $f_cp [file normalize $line]
641                                         }
642                                 }
643                                 close $f_in
644                                 close $f_cp
645                                 cd $pwd
646                         } err]} {
647                                 catch {cd $pwd}
648                                 _clone_failed $this [mc "Unable to copy objects/info/alternates: %s" $err]
649                                 return
650                         }
651                 }
652
653                 set tolink  [list]
654                 set buckets [glob \
655                         -tails \
656                         -nocomplain \
657                         -directory [file join $objdir] ??]
658                 set bcnt [expr {[llength $buckets] + 2}]
659                 set bcur 1
660                 $o_cons update $bcur $bcnt
661                 update
662
663                 file mkdir [file join .git objects pack]
664                 foreach i [glob -tails -nocomplain \
665                         -directory [file join $objdir pack] *] {
666                         lappend tolink [file join pack $i]
667                 }
668                 $o_cons update [incr bcur] $bcnt
669                 update
670
671                 foreach i $buckets {
672                         file mkdir [file join .git objects $i]
673                         foreach j [glob -tails -nocomplain \
674                                 -directory [file join $objdir $i] *] {
675                                 lappend tolink [file join $i $j]
676                         }
677                         $o_cons update [incr bcur] $bcnt
678                         update
679                 }
680                 $o_cons stop
681
682                 if {$tolink eq {}} {
683                         info_popup [strcat \
684                                 [mc "Nothing to clone from %s." $origin_url] \
685                                 "\n" \
686                                 [mc "The 'master' branch has not been initialized."] \
687                                 ]
688                         destroy $w_body
689                         set done 1
690                         return
691                 }
692
693                 set i [lindex $tolink 0]
694                 if {[catch {
695                                 file link -hard \
696                                         [file join .git objects $i] \
697                                         [file join $objdir $i]
698                         } err]} {
699                         info_popup [mc "Hardlinks are unavailable.  Falling back to copying."]
700                         set i [_copy_files $this $objdir $tolink]
701                 } else {
702                         set i [_link_files $this $objdir [lrange $tolink 1 end]]
703                 }
704                 if {!$i} return
705
706                 destroy $w_body
707         }
708         full {
709                 set o_cons [console::embed \
710                         $w_body \
711                         [mc "Cloning from %s" $origin_url]]
712                 pack $w_body -fill both -expand 1 -padx 10
713                 $o_cons exec \
714                         [list git fetch --no-tags -k $origin_name] \
715                         [cb _do_clone_tags]
716         }
717         shared {
718                 set fd [open [gitdir objects info alternates] w]
719                 fconfigure $fd -translation binary
720                 puts $fd $objdir
721                 close $fd
722         }
723         }
724
725         if {$clone_type eq {hardlink} || $clone_type eq {shared}} {
726                 if {![_clone_refs $this]} return
727                 set pwd [pwd]
728                 if {[catch {
729                                 cd $origin_url
730                                 set HEAD [git rev-parse --verify HEAD^0]
731                         } err]} {
732                         _clone_failed $this [mc "Not a Git repository: %s" [file tail $origin_url]]
733                         return 0
734                 }
735                 cd $pwd
736                 _do_clone_checkout $this $HEAD
737         }
738 }
739
740 method _copy_files {objdir tocopy} {
741         $o_cons start \
742                 [mc "Copying objects"] \
743                 [mc "KiB"]
744         set tot 0
745         set cmp 0
746         foreach p $tocopy {
747                 incr tot [file size [file join $objdir $p]]
748         }
749         foreach p $tocopy {
750                 if {[catch {
751                                 set f_in [open [file join $objdir $p] r]
752                                 set f_cp [open [file join .git objects $p] w]
753                                 fconfigure $f_in -translation binary -encoding binary
754                                 fconfigure $f_cp -translation binary -encoding binary
755
756                                 while {![eof $f_in]} {
757                                         incr cmp [fcopy $f_in $f_cp -size 16384]
758                                         $o_cons update \
759                                                 [expr {$cmp / 1024}] \
760                                                 [expr {$tot / 1024}]
761                                         update
762                                 }
763
764                                 close $f_in
765                                 close $f_cp
766                         } err]} {
767                         _clone_failed $this [mc "Unable to copy object: %s" $err]
768                         return 0
769                 }
770         }
771         return 1
772 }
773
774 method _link_files {objdir tolink} {
775         set total [llength $tolink]
776         $o_cons start \
777                 [mc "Linking objects"] \
778                 [mc "objects"]
779         for {set i 0} {$i < $total} {} {
780                 set p [lindex $tolink $i]
781                 if {[catch {
782                                 file link -hard \
783                                         [file join .git objects $p] \
784                                         [file join $objdir $p]
785                         } err]} {
786                         _clone_failed $this [mc "Unable to hardlink object: %s" $err]
787                         return 0
788                 }
789
790                 incr i
791                 if {$i % 5 == 0} {
792                         $o_cons update $i $total
793                         update
794                 }
795         }
796         return 1
797 }
798
799 method _clone_refs {} {
800         set pwd [pwd]
801         if {[catch {cd $origin_url} err]} {
802                 error_popup [mc "Not a Git repository: %s" [file tail $origin_url]]
803                 return 0
804         }
805         set fd_in [git_read for-each-ref \
806                 --tcl \
807                 {--format=list %(refname) %(objectname) %(*objectname)}]
808         cd $pwd
809
810         set fd [open [gitdir packed-refs] w]
811         fconfigure $fd -translation binary
812         puts $fd "# pack-refs with: peeled"
813         while {[gets $fd_in line] >= 0} {
814                 set line [eval $line]
815                 set refn [lindex $line 0]
816                 set robj [lindex $line 1]
817                 set tobj [lindex $line 2]
818
819                 if {[regsub ^refs/heads/ $refn \
820                         "refs/remotes/$origin_name/" refn]} {
821                         puts $fd "$robj $refn"
822                 } elseif {[string match refs/tags/* $refn]} {
823                         puts $fd "$robj $refn"
824                         if {$tobj ne {}} {
825                                 puts $fd "^$tobj"
826                         }
827                 }
828         }
829         close $fd_in
830         close $fd
831         return 1
832 }
833
834 method _do_clone_tags {ok} {
835         if {$ok} {
836                 $o_cons exec \
837                         [list git fetch --tags -k $origin_name] \
838                         [cb _do_clone_HEAD]
839         } else {
840                 $o_cons done $ok
841                 _clone_failed $this [mc "Cannot fetch branches and objects.  See console output for details."]
842         }
843 }
844
845 method _do_clone_HEAD {ok} {
846         if {$ok} {
847                 $o_cons exec \
848                         [list git fetch $origin_name HEAD] \
849                         [cb _do_clone_full_end]
850         } else {
851                 $o_cons done $ok
852                 _clone_failed $this [mc "Cannot fetch tags.  See console output for details."]
853         }
854 }
855
856 method _do_clone_full_end {ok} {
857         $o_cons done $ok
858
859         if {$ok} {
860                 destroy $w_body
861
862                 set HEAD {}
863                 if {[file exists [gitdir FETCH_HEAD]]} {
864                         set fd [open [gitdir FETCH_HEAD] r]
865                         while {[gets $fd line] >= 0} {
866                                 if {[regexp "^(.{40})\t\t" $line line HEAD]} {
867                                         break
868                                 }
869                         }
870                         close $fd
871                 }
872
873                 catch {git pack-refs}
874                 _do_clone_checkout $this $HEAD
875         } else {
876                 _clone_failed $this [mc "Cannot determine HEAD.  See console output for details."]
877         }
878 }
879
880 method _clone_failed {{why {}}} {
881         if {[catch {file delete -force $local_path} err]} {
882                 set why [strcat \
883                         $why \
884                         "\n\n" \
885                         [mc "Unable to cleanup %s" $local_path] \
886                         "\n\n" \
887                         $err]
888         }
889         if {$why ne {}} {
890                 update
891                 error_popup [strcat [mc "Clone failed."] "\n" $why]
892         }
893 }
894
895 method _do_clone_checkout {HEAD} {
896         if {$HEAD eq {}} {
897                 info_popup [strcat \
898                         [mc "No default branch obtained."] \
899                         "\n" \
900                         [mc "The 'master' branch has not been initialized."] \
901                         ]
902                 set done 1
903                 return
904         }
905         if {[catch {
906                         git update-ref HEAD $HEAD^0
907                 } err]} {
908                 info_popup [strcat \
909                         [mc "Cannot resolve %s as a commit." $HEAD^0] \
910                         "\n  $err" \
911                         "\n" \
912                         [mc "The 'master' branch has not been initialized."] \
913                         ]
914                 set done 1
915                 return
916         }
917
918         set o_cons [status_bar::two_line $w_body]
919         pack $w_body -fill x -padx 10 -pady 10
920         $o_cons start \
921                 [mc "Creating working directory"] \
922                 [mc "files"]
923
924         set readtree_err {}
925         set fd [git_read --stderr read-tree \
926                 -m \
927                 -u \
928                 -v \
929                 HEAD \
930                 HEAD \
931                 ]
932         fconfigure $fd -blocking 0 -translation binary
933         fileevent $fd readable [cb _readtree_wait $fd]
934 }
935
936 method _readtree_wait {fd} {
937         set buf [read $fd]
938         $o_cons update_meter $buf
939         append readtree_err $buf
940
941         fconfigure $fd -blocking 1
942         if {![eof $fd]} {
943                 fconfigure $fd -blocking 0
944                 return
945         }
946
947         if {[catch {close $fd}]} {
948                 set err $readtree_err
949                 regsub {^fatal: } $err {} err
950                 error_popup [strcat \
951                         [mc "Initial file checkout failed."] \
952                         "\n\n$err"]
953                 return
954         }
955
956         set done 1
957 }
958
959 ######################################################################
960 ##
961 ## Open Existing Repository
962
963 method _do_open {} {
964         $w_next conf \
965                 -state disabled \
966                 -command [cb _do_open2] \
967                 -text [mc "Open"]
968
969         frame $w_body
970         label $w_body.h \
971                 -font font_uibold \
972                 -text [mc "Open Existing Repository"]
973         pack $w_body.h -side top -fill x -pady 10
974         pack $w_body -fill x -padx 10
975
976         frame $w_body.where
977         label $w_body.where.l -text [mc "Repository:"]
978         entry $w_body.where.t \
979                 -textvariable @local_path \
980                 -font font_diff \
981                 -width 50
982         button $w_body.where.b \
983                 -text [mc "Browse"] \
984                 -command [cb _open_local_path]
985
986         pack $w_body.where.b -side right
987         pack $w_body.where.l -side left
988         pack $w_body.where.t -fill x
989         pack $w_body.where -fill x
990
991         trace add variable @local_path write [cb _write_local_path]
992         bind $w_body.h <Destroy> [list trace remove variable @local_path write [cb _write_local_path]]
993         update
994         focus $w_body.where.t
995 }
996
997 method _open_local_path {} {
998         if {$local_path ne {}} {
999                 set p $local_path
1000         } else {
1001                 set p [_home]
1002         }
1003
1004         set p [tk_chooseDirectory \
1005                 -initialdir $p \
1006                 -parent $top \
1007                 -title [mc "Git Repository"] \
1008                 -mustexist true]
1009         if {$p eq {}} return
1010
1011         set p [file normalize $p]
1012         if {![_is_git [file join $p .git]]} {
1013                 error_popup [mc "Not a Git repository: %s" [file tail $p]]
1014                 return
1015         }
1016         set local_path $p
1017 }
1018
1019 method _do_open2 {} {
1020         if {![_is_git [file join $local_path .git]]} {
1021                 error_popup [mc "Not a Git repository: %s" [file tail $local_path]]
1022                 return
1023         }
1024
1025         if {[catch {cd $local_path} err]} {
1026                 error_popup [strcat \
1027                         [mc "Failed to open repository %s:" $local_path] \
1028                         "\n\n$err"]
1029                 return
1030         }
1031
1032         _append_recentrepos [pwd]
1033         set ::_gitdir .git
1034         set ::_prefix {}
1035         set done 1
1036 }
1037
1038 }