Code

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