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