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