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