From a8d610a2a39496a83108d95e7899e6b373e80940 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Thu, 19 Apr 2007 11:39:12 +1000 Subject: [PATCH 01/77] gitk: Allow user to choose whether to see the diff, old file, or new file This adds a set of radiobuttons that select between displaying the full diff (both - and + lines), the old file (suppressing the + lines) and the new file (suppressing the - lines) in the diff display window. Signed-off-by: Paul Mackerras --- gitk | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/gitk b/gitk index b1c65d7680..a57e84cef7 100755 --- a/gitk +++ b/gitk @@ -593,6 +593,7 @@ proc makewindow {} { frame .bleft -width $geometry(botwidth) -height $geometry(botheight) } frame .bleft.top + frame .bleft.mid button .bleft.top.search -text "Search" -command dosearch \ -font $uifont @@ -602,12 +603,20 @@ proc makewindow {} { lappend entries $sstring trace add variable searchstring write incrsearch pack $sstring -side left -expand 1 -fill x + radiobutton .bleft.mid.diff -text "Diff" \ + -command changediffdisp -variable diffelide -value {0 0} + radiobutton .bleft.mid.old -text "Old version" \ + -command changediffdisp -variable diffelide -value {0 1} + radiobutton .bleft.mid.new -text "New version" \ + -command changediffdisp -variable diffelide -value {1 0} + pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left set ctext .bleft.ctext text $ctext -background $bgcolor -foreground $fgcolor \ -state disabled -font $textfont \ -yscrollcommand scrolltext -wrap none scrollbar .bleft.sb -command "$ctext yview" pack .bleft.top -side top -fill x + pack .bleft.mid -side top -fill x pack .bleft.sb -side right -fill y pack $ctext -side left -fill both -expand 1 lappend bglist $ctext @@ -4486,6 +4495,13 @@ proc getblobdiffline {bdf ids} { } } +proc changediffdisp {} { + global ctext diffelide + + $ctext tag conf d0 -elide [lindex $diffelide 0] + $ctext tag conf d1 -elide [lindex $diffelide 1] +} + proc prevfile {} { global difffilestart ctext set prev [lindex $difffilestart 0] @@ -6330,6 +6346,7 @@ set highlight_paths {} set searchdirn -forwards set boldrows {} set boldnamerows {} +set diffelide {0 0} set optim_delay 16 From f20db5ff30af2169a0fe8a0afe015c30cdbe0256 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 24 Apr 2007 02:11:40 -0400 Subject: [PATCH 02/77] git-gui: Correctly handle UTF-8 encoded commit messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uwe Kleine-König discovered git-gui mangled his surname and did not send the proper UTF-8 byte sequence to git-commit-tree when his name appeared in the commit message (e.g. Signed-Off-By line). Turns out this was related to other trouble that I had in the past with trying to use "fconfigure $fd -encoding $enc" to select the stream encoding and let Tcl's IO engine do all of the encoding work for us. Other parts of git-gui were just always setting the file channels to "-encoding binary" and then performing the encoding work themselves using "encoding convertfrom" and "convertto", as that was the only way I could make UTF-8 filenames work properly. I found this same bug in the amend code path, and in the blame display. So its fixed in all three locations (commit creation, reloading message for amend, viewing message in blame). Signed-off-by: Shawn O. Pearce --- git-gui.sh | 54 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 94067cc5f7..3f41bd7243 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1067,8 +1067,8 @@ You are currently in the middle of a merge that has not been fully completed. Y set enc [string tolower [string range $line 9 end]] } } - fconfigure $fd -encoding $enc - set msg [string trim [read $fd]] + set msg [encoding convertfrom $enc [read $fd]] + set msg [string trim $msg] close $fd } err]} { error_popup "Error loading commit data for amend:\n\n$err" @@ -1291,8 +1291,8 @@ A rescan will be automatically started now. if {[catch {set enc $repo_config(i18n.commitencoding)}]} { set enc utf-8 } - fconfigure $msg_wt -encoding $enc -translation binary - puts -nonewline $msg_wt $msg + fconfigure $msg_wt -encoding binary -translation binary + puts -nonewline $msg_wt [encoding convertto $enc $msg] close $msg_wt # -- Create the commit. @@ -3663,26 +3663,6 @@ proc blame_showcommit {w w_cmit w_line w_file lno} { incr i } - if {[catch {set msg $blame_data($w,$cmit,message)}]} { - set msg {} - catch { - set fd [open "| git cat-file commit $cmit" r] - fconfigure $fd -encoding binary -translation lf - if {[catch {set enc $repo_config(i18n.commitencoding)}]} { - set enc utf-8 - } - while {[gets $fd line] > 0} { - if {[string match {encoding *} $line]} { - set enc [string tolower [string range $line 9 end]] - } - } - fconfigure $fd -encoding $enc - set msg [string trim [read $fd]] - close $fd - } - set blame_data($w,$cmit,message) $msg - } - set author_name {} set author_email {} set author_time {} @@ -3697,6 +3677,32 @@ proc blame_showcommit {w w_cmit w_line w_file lno} { catch {set committer_email $blame_data($w,$cmit,committer-mail)} catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]} + if {[catch {set msg $blame_data($w,$cmit,message)}]} { + set msg {} + catch { + set fd [open "| git cat-file commit $cmit" r] + fconfigure $fd -encoding binary -translation lf + if {[catch {set enc $repo_config(i18n.commitencoding)}]} { + set enc utf-8 + } + while {[gets $fd line] > 0} { + if {[string match {encoding *} $line]} { + set enc [string tolower [string range $line 9 end]] + } + } + set msg [encoding convertfrom $enc [read $fd]] + set msg [string trim $msg] + close $fd + + set author_name [encoding convertfrom $enc $author_name] + set committer_name [encoding convertfrom $enc $committer_name] + + set blame_data($w,$cmit,author) $author_name + set blame_data($w,$cmit,committer) $committer_name + } + set blame_data($w,$cmit,message) $msg + } + $w_cmit insert end "commit $cmit\n" $w_cmit insert end "Author: $author_name $author_email $author_time\n" $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n" From 681bfd59ce1a93a4492e11a480e438099f721294 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 2 May 2007 12:44:44 -0400 Subject: [PATCH 03/77] git-gui: Allow spaces in path to 'wish' If the path of our wish executable that are running under contains spaces we need to make sure they are escaped in a proper Tcl list, otherwise we are unable to start gitk. Reported by Randal L. Schwartz on #git. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index 7cbc977ea2..ae881336da 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -4134,7 +4134,7 @@ proc do_gitk {revs} { # -- Always start gitk through whatever we were loaded with. This # lets us bypass using shell process on Windows systems. # - set cmd [info nameofexecutable] + set cmd [list [info nameofexecutable]] lappend cmd [gitexec gitk] if {$revs ne {}} { append cmd { } From 2f1a955b99954fd18c512244e1321dc4cff856b4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 28 Apr 2007 20:49:22 -0400 Subject: [PATCH 04/77] git-gui: Include the subject in the status bar after commit Now that the command line git-commit has made displaying the subject (first line) of the newly created commit popular we can easily do the same thing here in git-gui, without the ugly part of forking off a child process to obtain that first line. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 0ad2815d10..f6c6d44357 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1317,10 +1317,11 @@ A rescan will be automatically started now. } set i [string first "\n" $msg] if {$i >= 0} { - append reflogm {: } [string range $msg 0 [expr {$i - 1}]] + set subject [string range $msg 0 [expr {$i - 1}]] } else { - append reflogm {: } $msg + set subject $msg } + append reflogm {: } $subject set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD] if {[catch {eval exec $cmd} err]} { error_popup "update-ref failed:\n\n$err" @@ -1414,7 +1415,7 @@ A rescan will be automatically started now. unlock_index reshow_diff set ui_status_value \ - "Changes committed as [string range $cmt_id 0 7]." + "Created commit [string range $cmt_id 0 7]: $subject" } ###################################################################### From 1afd1ec1077a6d43b9492e19ea88f5928204d4cc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 28 Apr 2007 21:26:27 -0400 Subject: [PATCH 05/77] git-gui: Warn users before making an octopus merge A coworker who was new to git-gui recently tried to make an octopus merge when he did not quite mean to. Unfortunately in his case the branches had file level conflicts and failed to merge with the octopus strategy, and he didn't quite know why this happened. Since most users really don't want to perform an octopus merge this additional safety valve in front of the merge process is a good thing. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index f6c6d44357..dd8a408c64 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2886,6 +2886,19 @@ proc start_local_merge_action {w} { set unit branch } elseif {$revcnt <= 15} { set unit branches + + if {[tk_dialog \ + $w.confirm_octopus \ + [wm title $w] \ + "Use octopus merge strategy? + +You are merging $revcnt branches at once. This requires using the octopus merge driver, which may not succeed if there are file-level conflicts. +" \ + question \ + 0 \ + {Cancel} \ + {Use octopus} \ + ] != 1} return } else { tk_messageBox \ -icon error \ From d45b52b540b13a6dae816b65f0f5431b56583570 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 28 Apr 2007 21:32:27 -0400 Subject: [PATCH 06/77] git-gui: Correct line wrapping for too many branch message Since Tk automatically wraps lines for us in tk_messageBox widgets we don't need to try to wrap them ourselves. Its actually worse that we linewrapped this here in the script, as not all fonts will render this dialog nicely. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index dd8a408c64..ba2ee985f7 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2907,12 +2907,9 @@ You are merging $revcnt branches at once. This requires using the octopus merge -parent $w \ -message "Too many branches selected. -You have requested to merge $revcnt branches -in an octopus merge. This exceeds Git's -internal limit of 15 branches per merge. +You have requested to merge $revcnt branches in an octopus merge. This exceeds Git's internal limit of 15 branches per merge. -Please select fewer branches. To merge more -than 15 branches, merge the branches in batches. +Please select fewer branches. To merge more than 15 branches, merge the branches in batches. " return } From 2739291b77fefe7cfae16481756716f11a01e654 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 28 Apr 2007 22:00:02 -0400 Subject: [PATCH 07/77] git-gui: Cleanup common font handling for font_ui An earlier change tossed these optionMenu font configurations all over the code, when really we can just rename the proc to a hidden internal name and provide our own wrapper to install the font configuration we really want. We also don't need to set these option database entries in all of the procedures that open dialogs; instead we should just set one time, them after we have the font configuration ready for use. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 59 +++++++++++++++++++----------------------------------- 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index ba2ee985f7..aa232f0e7e 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -242,8 +242,6 @@ proc error_popup {msg} { if {[reponame] ne {}} { append title " ([reponame])" } - option add *Dialog.msg.font font_ui - option add *Button.font font_ui set cmd [list tk_messageBox \ -icon error \ -type ok \ @@ -260,8 +258,6 @@ proc warn_popup {msg} { if {[reponame] ne {}} { append title " ([reponame])" } - option add *Dialog.msg.font font_ui - option add *Button.font font_ui set cmd [list tk_messageBox \ -icon warning \ -type ok \ @@ -278,8 +274,6 @@ proc info_popup {msg {parent .}} { if {[reponame] ne {}} { append title " ([reponame])" } - option add *Dialog.msg.font font_ui - option add *Button.font font_ui tk_messageBox \ -parent $parent \ -icon info \ @@ -293,8 +287,6 @@ proc ask_popup {msg} { if {[reponame] ne {}} { append title " ([reponame])" } - option add *Dialog.msg.font font_ui - option add *Button.font font_ui return [tk_messageBox \ -parent . \ -icon question \ @@ -303,6 +295,15 @@ proc ask_popup {msg} { -message $msg] } +auto_load tk_optionMenu +rename tk_optionMenu real__tkOptionMenu +proc tk_optionMenu {w varName args} { + set m [eval real__tkOptionMenu $w $varName $args] + $m configure -font font_ui + $w configure -font font_ui + return $m +} + ###################################################################### ## ## version check @@ -2114,10 +2115,7 @@ proc do_create_branch {} { -value head \ -variable create_branch_revtype \ -font font_ui - set lbranchm [eval tk_optionMenu $w.from.head_m create_branch_head \ - $all_heads] - $lbranchm configure -font font_ui - $w.from.head_m configure -font font_ui + eval tk_optionMenu $w.from.head_m create_branch_head $all_heads grid $w.from.head_r $w.from.head_m -sticky w set all_trackings [all_tracking_branches] if {$all_trackings ne {}} { @@ -2127,11 +2125,9 @@ proc do_create_branch {} { -value tracking \ -variable create_branch_revtype \ -font font_ui - set tbranchm [eval tk_optionMenu $w.from.tracking_m \ + eval tk_optionMenu $w.from.tracking_m \ create_branch_trackinghead \ - $all_trackings] - $tbranchm configure -font font_ui - $w.from.tracking_m configure -font font_ui + $all_trackings grid $w.from.tracking_r $w.from.tracking_m -sticky w } set all_tags [load_all_tags] @@ -2142,11 +2138,7 @@ proc do_create_branch {} { -value tag \ -variable create_branch_revtype \ -font font_ui - set tagsm [eval tk_optionMenu $w.from.tag_m \ - create_branch_tag \ - $all_tags] - $tagsm configure -font font_ui - $w.from.tag_m configure -font font_ui + eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags grid $w.from.tag_r $w.from.tag_m -sticky w } radiobutton $w.from.exp_r \ @@ -2340,11 +2332,7 @@ proc do_delete_branch {} { -value head \ -variable delete_branch_checktype \ -font font_ui - set mergedlocalm [eval tk_optionMenu $w.validate.head_m \ - delete_branch_head \ - $all_heads] - $mergedlocalm configure -font font_ui - $w.validate.head_m configure -font font_ui + eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads grid $w.validate.head_r $w.validate.head_m -sticky w set all_trackings [all_tracking_branches] if {$all_trackings ne {}} { @@ -2354,11 +2342,9 @@ proc do_delete_branch {} { -value tracking \ -variable delete_branch_checktype \ -font font_ui - set mergedtrackm [eval tk_optionMenu $w.validate.tracking_m \ + eval tk_optionMenu $w.validate.tracking_m \ delete_branch_trackinghead \ - $all_trackings] - $mergedtrackm configure -font font_ui - $w.validate.tracking_m configure -font font_ui + $all_trackings grid $w.validate.tracking_r $w.validate.tracking_m -sticky w } radiobutton $w.validate.always_r \ @@ -2729,10 +2715,7 @@ proc do_push_anywhere {} { -value remote \ -variable push_urltype \ -font font_ui - set remmenu [eval tk_optionMenu $w.dest.remote_m push_remote \ - $all_remotes] - $remmenu configure -font font_ui - $w.dest.remote_m configure -font font_ui + eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes grid $w.dest.remote_r $w.dest.remote_m -sticky w if {[lsearch -sorted -exact $all_remotes origin] != -1} { set push_remote origin @@ -4715,11 +4698,9 @@ proc do_options {} { frame $w.global.$name label $w.global.$name.l -text "$text:" -font font_ui pack $w.global.$name.l -side left -anchor w -fill x - set fontmenu [eval tk_optionMenu $w.global.$name.family \ + eval tk_optionMenu $w.global.$name.family \ global_config_new(gui.$font^^family) \ - $all_fonts] - $w.global.$name.family configure -font font_ui - $fontmenu configure -font font_ui + $all_fonts spinbox $w.global.$name.size \ -textvariable global_config_new(gui.$font^^size) \ -from 2 -to 80 -increment 1 \ @@ -5068,6 +5049,8 @@ set font_descs { } load_config 0 apply_config +option add *Dialog.msg.font font_ui +option add *Button.font font_ui ###################################################################### ## From 7416bbc65cbcc3db9cafe50b92c14193ac295dbe Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 28 Apr 2007 23:14:08 -0400 Subject: [PATCH 08/77] git-gui: Use option database defaults to set the font Rather than passing "-font font_ui" to every widget that we create we can instead reconfigure the option database for all widget classes to use our font_ui as the default widget font. This way Tk will automatically setup their defaults for us, and we can reduce the size of the application. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 370 +++++++++++++++++------------------------------------ 1 file changed, 114 insertions(+), 256 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index aa232f0e7e..94f80bb2b1 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1918,8 +1918,7 @@ proc populate_branch_menu {} { -label $b \ -command [list switch_branch $b] \ -variable current_branch \ - -value $b \ - -font font_ui + -value $b lappend disable_on_lock \ [list $m entryconf [$m index last] -state] } @@ -2078,26 +2077,21 @@ proc do_create_branch {} { frame $w.buttons button $w.buttons.create -text Create \ - -font font_ui \ -default active \ -command [list do_create_branch_action $w] pack $w.buttons.create -side right button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.desc \ - -text {Branch Description} \ - -font font_ui - label $w.desc.name_l -text {Name:} -font font_ui + labelframe $w.desc -text {Branch Description} + label $w.desc.name_l -text {Name:} entry $w.desc.name_t \ -borderwidth 1 \ -relief sunken \ -width 40 \ -textvariable create_branch_name \ - -font font_ui \ -validate key \ -validatecommand { if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0} @@ -2107,14 +2101,11 @@ proc do_create_branch {} { grid columnconfigure $w.desc 1 -weight 1 pack $w.desc -anchor nw -fill x -pady 5 -padx 5 - labelframe $w.from \ - -text {Starting Revision} \ - -font font_ui + labelframe $w.from -text {Starting Revision} radiobutton $w.from.head_r \ -text {Local Branch:} \ -value head \ - -variable create_branch_revtype \ - -font font_ui + -variable create_branch_revtype eval tk_optionMenu $w.from.head_m create_branch_head $all_heads grid $w.from.head_r $w.from.head_m -sticky w set all_trackings [all_tracking_branches] @@ -2123,8 +2114,7 @@ proc do_create_branch {} { radiobutton $w.from.tracking_r \ -text {Tracking Branch:} \ -value tracking \ - -variable create_branch_revtype \ - -font font_ui + -variable create_branch_revtype eval tk_optionMenu $w.from.tracking_m \ create_branch_trackinghead \ $all_trackings @@ -2136,22 +2126,19 @@ proc do_create_branch {} { radiobutton $w.from.tag_r \ -text {Tag:} \ -value tag \ - -variable create_branch_revtype \ - -font font_ui + -variable create_branch_revtype eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags grid $w.from.tag_r $w.from.tag_m -sticky w } radiobutton $w.from.exp_r \ -text {Revision Expression:} \ -value expression \ - -variable create_branch_revtype \ - -font font_ui + -variable create_branch_revtype entry $w.from.exp_t \ -borderwidth 1 \ -relief sunken \ -width 50 \ -textvariable create_branch_revexp \ - -font font_ui \ -validate key \ -validatecommand { if {%d == 1 && [regexp {\s} %S]} {return 0} @@ -2164,13 +2151,10 @@ proc do_create_branch {} { grid columnconfigure $w.from 1 -weight 1 pack $w.from -anchor nw -fill x -pady 5 -padx 5 - labelframe $w.postActions \ - -text {Post Creation Actions} \ - -font font_ui + labelframe $w.postActions -text {Post Creation Actions} checkbutton $w.postActions.checkout \ -text {Checkout after creation} \ - -variable create_branch_checkout \ - -font font_ui + -variable create_branch_checkout pack $w.postActions.checkout -anchor nw pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 @@ -2296,24 +2280,19 @@ proc do_delete_branch {} { frame $w.buttons button $w.buttons.create -text Delete \ - -font font_ui \ -command [list do_delete_branch_action $w] pack $w.buttons.create -side right button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.list \ - -text {Local Branches} \ - -font font_ui + labelframe $w.list -text {Local Branches} listbox $w.list.l \ -height 10 \ -width 70 \ -selectmode extended \ - -yscrollcommand [list $w.list.sby set] \ - -font font_ui + -yscrollcommand [list $w.list.sby set] foreach h $all_heads { if {$h ne $current_branch} { $w.list.l insert end $h @@ -2324,14 +2303,11 @@ proc do_delete_branch {} { pack $w.list.l -side left -fill both -expand 1 pack $w.list -fill both -expand 1 -pady 5 -padx 5 - labelframe $w.validate \ - -text {Delete Only If} \ - -font font_ui + labelframe $w.validate -text {Delete Only If} radiobutton $w.validate.head_r \ -text {Merged Into Local Branch:} \ -value head \ - -variable delete_branch_checktype \ - -font font_ui + -variable delete_branch_checktype eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads grid $w.validate.head_r $w.validate.head_m -sticky w set all_trackings [all_tracking_branches] @@ -2340,8 +2316,7 @@ proc do_delete_branch {} { radiobutton $w.validate.tracking_r \ -text {Merged Into Tracking Branch:} \ -value tracking \ - -variable delete_branch_checktype \ - -font font_ui + -variable delete_branch_checktype eval tk_optionMenu $w.validate.tracking_m \ delete_branch_trackinghead \ $all_trackings @@ -2350,8 +2325,7 @@ proc do_delete_branch {} { radiobutton $w.validate.always_r \ -text {Always (Do not perform merge checks)} \ -value always \ - -variable delete_branch_checktype \ - -font font_ui + -variable delete_branch_checktype grid $w.validate.always_r -columnspan 2 -sticky w grid columnconfigure $w.validate 1 -weight 1 pack $w.validate -anchor nw -fill x -pady 5 -padx 5 @@ -2578,8 +2552,7 @@ proc populate_fetch_menu {} { if {$enable} { $m add command \ -label "Fetch from $r..." \ - -command [list fetch_from $r] \ - -font font_ui + -command [list fetch_from $r] } } } @@ -2614,8 +2587,7 @@ proc populate_push_menu {} { } $m add command \ -label "Push to $r..." \ - -command [list push_to $r] \ - -font font_ui + -command [list push_to $r] incr fast_count } } @@ -2675,26 +2647,21 @@ proc do_push_anywhere {} { frame $w.buttons button $w.buttons.create -text Push \ - -font font_ui \ -default active \ -command [list start_push_anywhere_action $w] pack $w.buttons.create -side right button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ -default normal \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.source \ - -text {Source Branches} \ - -font font_ui + labelframe $w.source -text {Source Branches} listbox $w.source.l \ -height 10 \ -width 70 \ -selectmode extended \ - -yscrollcommand [list $w.source.sby set] \ - -font font_ui + -yscrollcommand [list $w.source.sby set] foreach h $all_heads { $w.source.l insert end $h if {$h eq $current_branch} { @@ -2706,15 +2673,12 @@ proc do_push_anywhere {} { pack $w.source.l -side left -fill both -expand 1 pack $w.source -fill both -expand 1 -pady 5 -padx 5 - labelframe $w.dest \ - -text {Destination Repository} \ - -font font_ui + labelframe $w.dest -text {Destination Repository} if {$all_remotes ne {}} { radiobutton $w.dest.remote_r \ -text {Remote:} \ -value remote \ - -variable push_urltype \ - -font font_ui + -variable push_urltype eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes grid $w.dest.remote_r $w.dest.remote_m -sticky w if {[lsearch -sorted -exact $all_remotes origin] != -1} { @@ -2729,14 +2693,12 @@ proc do_push_anywhere {} { radiobutton $w.dest.url_r \ -text {Arbitrary URL:} \ -value url \ - -variable push_urltype \ - -font font_ui + -variable push_urltype entry $w.dest.url_t \ -borderwidth 1 \ -relief sunken \ -width 50 \ -textvariable push_url \ - -font font_ui \ -validate key \ -validatecommand { if {%d == 1 && [regexp {\s} %S]} {return 0} @@ -2749,18 +2711,14 @@ proc do_push_anywhere {} { grid columnconfigure $w.dest 1 -weight 1 pack $w.dest -anchor nw -fill x -pady 5 -padx 5 - labelframe $w.options \ - -text {Transfer Options} \ - -font font_ui + labelframe $w.options -text {Transfer Options} checkbutton $w.options.thin \ -text {Use thin pack (for slow network connections)} \ - -variable push_thin \ - -font font_ui + -variable push_thin grid $w.options.thin -columnspan 2 -sticky w checkbutton $w.options.tags \ -text {Include tags} \ - -variable push_tags \ - -font font_ui + -variable push_tags grid $w.options.tags -columnspan 2 -sticky w grid columnconfigure $w.options 1 -weight 1 pack $w.options -anchor nw -fill x -pady 5 -padx 5 @@ -2950,28 +2908,22 @@ proc do_local_merge {} { frame $w.buttons button $w.buttons.visualize -text Visualize \ - -font font_ui \ -command [list visualize_local_merge $w] pack $w.buttons.visualize -side left button $w.buttons.create -text Merge \ - -font font_ui \ -command [list start_local_merge_action $w] pack $w.buttons.create -side right button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.source \ - -text {Source Branches} \ - -font font_ui + labelframe $w.source -text {Source Branches} listbox $w.source.l \ -height 10 \ -width 70 \ -selectmode extended \ - -yscrollcommand [list $w.source.sby set] \ - -font font_ui + -yscrollcommand [list $w.source.sby set] scrollbar $w.source.sby -command [list $w.source.l yview] pack $w.source.sby -side right -fill y pack $w.source.l -side left -fill both -expand 1 @@ -3106,8 +3058,7 @@ proc new_browser {commit} { -height 20 \ -width 70 \ -xscrollcommand [list $w.list.sbx set] \ - -yscrollcommand [list $w.list.sby set] \ - -font font_ui + -yscrollcommand [list $w.list.sby set] $w_list tag conf in_sel \ -background [$w_list cget -foreground] \ -foreground [$w_list cget -background] @@ -3122,8 +3073,7 @@ proc new_browser {commit} { -anchor w \ -justify left \ -borderwidth 1 \ - -relief sunken \ - -font font_ui + -relief sunken pack $w.status -anchor w -side bottom -fill x bind $w_list "browser_click 0 $w_list @%x,%y;break" @@ -3403,8 +3353,7 @@ proc show_blame {commit path} { -anchor w \ -justify left \ -borderwidth 1 \ - -relief sunken \ - -font font_ui + -relief sunken pack $w.status -side bottom -fill x frame $w.cm @@ -3426,7 +3375,6 @@ proc show_blame {commit path} { menu $w.ctxm -tearoff 0 $w.ctxm add command -label "Copy Commit" \ - -font font_ui \ -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY" foreach i [list \ @@ -3928,7 +3876,6 @@ proc hook_failed_popup {hook msg} { button $w.ok -text OK \ -width 15 \ - -font font_ui \ -command "destroy $w" pack $w.ok -side bottom -anchor e -pady 10 -padx 10 @@ -3977,13 +3924,10 @@ proc console_init {w} { menu $w.ctxm -tearoff 0 $w.ctxm add command -label "Copy" \ - -font font_ui \ -command "tk_textCopy $w.m.t" $w.ctxm add command -label "Select All" \ - -font font_ui \ -command "focus $w.m.t;$w.m.t tag add sel 0.0 end" $w.ctxm add command -label "Copy All" \ - -font font_ui \ -command " $w.m.t tag add sel 0.0 end tk_textCopy $w.m.t @@ -3991,7 +3935,6 @@ proc console_init {w} { " button $w.ok -text {Close} \ - -font font_ui \ -state disabled \ -command "destroy $w" pack $w.ok -side bottom -anchor e -pady 10 -padx 10 @@ -4179,17 +4122,14 @@ proc do_stats {} { toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - label $w.header -text {Database Statistics} \ - -font font_uibold + label $w.header -text {Database Statistics} pack $w.header -side top -fill x frame $w.buttons -border 1 button $w.buttons.close -text Close \ - -font font_ui \ -default active \ -command [list destroy $w] button $w.buttons.gc -text {Compress Database} \ - -font font_ui \ -default normal \ -command "destroy $w;do_gc" pack $w.buttons.close -side right @@ -4213,8 +4153,8 @@ proc do_stats {} { set value "$value[lindex $s 2]" } - label $w.stat.l_$name -text "$label:" -anchor w -font font_ui - label $w.stat.v_$name -text $value -anchor w -font font_ui + label $w.stat.l_$name -text "$label:" -anchor w + label $w.stat.v_$name -text $value -anchor w grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5} } pack $w.stat -pady 10 -padx 10 @@ -4515,7 +4455,6 @@ proc do_about {} { frame $w.buttons button $w.buttons.close -text {Close} \ - -font font_ui \ -default active \ -command [list destroy $w] pack $w.buttons.close -side right @@ -4528,8 +4467,7 @@ $copyright" \ -justify left \ -anchor w \ -borderwidth 1 \ - -relief solid \ - -font font_ui + -relief solid pack $w.desc -side top -fill x -padx 5 -pady 5 set v {} @@ -4549,14 +4487,12 @@ $copyright" \ -justify left \ -anchor w \ -borderwidth 1 \ - -relief solid \ - -font font_ui + -relief solid pack $w.vers -side top -fill x -padx 5 -pady 5 menu $w.ctxm -tearoff 0 $w.ctxm add command \ -label {Copy} \ - -font font_ui \ -command " clipboard clear clipboard append -format STRING -type STRING -- \[$w.vers cget -text\] @@ -4600,26 +4536,21 @@ proc do_options {} { frame $w.buttons button $w.buttons.restore -text {Restore Defaults} \ - -font font_ui \ -default normal \ -command do_restore_defaults pack $w.buttons.restore -side left button $w.buttons.save -text Save \ - -font font_ui \ -default active \ -command [list do_save_config $w] pack $w.buttons.save -side right button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ -default normal \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.repo -text "[reponame] Repository" \ - -font font_ui - labelframe $w.global -text {Global (All Repositories)} \ - -font font_ui + labelframe $w.repo -text "[reponame] Repository" + labelframe $w.global -text {Global (All Repositories)} pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5 pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 @@ -4645,35 +4576,32 @@ proc do_options {} { checkbutton $w.$f.$optid -text $text \ -variable ${f}_config_new($name) \ -onvalue true \ - -offvalue false \ - -font font_ui + -offvalue false pack $w.$f.$optid -side top -anchor w } i-* { regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max frame $w.$f.$optid - label $w.$f.$optid.l -text "$text:" -font font_ui + label $w.$f.$optid.l -text "$text:" pack $w.$f.$optid.l -side left -anchor w -fill x spinbox $w.$f.$optid.v \ -textvariable ${f}_config_new($name) \ -from $min \ -to $max \ -increment 1 \ - -width [expr {1 + [string length $max]}] \ - -font font_ui + -width [expr {1 + [string length $max]}] bind $w.$f.$optid.v {%W selection range 0 end} pack $w.$f.$optid.v -side right -anchor e -padx 5 pack $w.$f.$optid -side top -anchor w -fill x } t { frame $w.$f.$optid - label $w.$f.$optid.l -text "$text:" -font font_ui + label $w.$f.$optid.l -text "$text:" entry $w.$f.$optid.v \ -borderwidth 1 \ -relief sunken \ -width 20 \ - -textvariable ${f}_config_new($name) \ - -font font_ui + -textvariable ${f}_config_new($name) pack $w.$f.$optid.l -side left -anchor w pack $w.$f.$optid.v -side left -anchor w \ -fill x -expand 1 \ @@ -4696,7 +4624,7 @@ proc do_options {} { [font configure $font -size] frame $w.global.$name - label $w.global.$name.l -text "$text:" -font font_ui + label $w.global.$name.l -text "$text:" pack $w.global.$name.l -side left -anchor w -fill x eval tk_optionMenu $w.global.$name.family \ global_config_new(gui.$font^^family) \ @@ -4704,8 +4632,7 @@ proc do_options {} { spinbox $w.global.$name.size \ -textvariable global_config_new(gui.$font^^size) \ -from 2 -to 80 -increment 1 \ - -width 3 \ - -font font_ui + -width 3 bind $w.global.$name.size {%W selection range 0 end} pack $w.global.$name.size -side right -anchor e pack $w.global.$name.family -side right -anchor e @@ -5002,6 +4929,13 @@ catch { font create font_uibold font create font_diffbold +foreach class {Button Checkbutton Entry Label + Labelframe Listbox Menu Message + Radiobutton Text} { + option add *$class.font font_ui +} +unset class + if {[is_Windows]} { set M1B Control set M1T Ctrl @@ -5049,8 +4983,6 @@ set font_descs { } load_config 0 apply_config -option add *Dialog.msg.font font_ui -option add *Button.font font_ui ###################################################################### ## @@ -5098,18 +5030,18 @@ set ui_comm {} # -- Menu Bar # menu .mbar -tearoff 0 -.mbar add cascade -label Repository -menu .mbar.repository -font font_ui -.mbar add cascade -label Edit -menu .mbar.edit -font font_ui +.mbar add cascade -label Repository -menu .mbar.repository +.mbar add cascade -label Edit -menu .mbar.edit if {[is_enabled branch]} { - .mbar add cascade -label Branch -menu .mbar.branch -font font_ui + .mbar add cascade -label Branch -menu .mbar.branch } if {[is_enabled multicommit] || [is_enabled singlecommit]} { - .mbar add cascade -label Commit -menu .mbar.commit -font font_ui + .mbar add cascade -label Commit -menu .mbar.commit } if {[is_enabled transport]} { - .mbar add cascade -label Merge -menu .mbar.merge -font font_ui - .mbar add cascade -label Fetch -menu .mbar.fetch -font font_ui - .mbar add cascade -label Push -menu .mbar.push -font font_ui + .mbar add cascade -label Merge -menu .mbar.merge + .mbar add cascade -label Fetch -menu .mbar.fetch + .mbar add cascade -label Push -menu .mbar.push } . configure -menu .mbar @@ -5119,93 +5051,76 @@ menu .mbar.repository .mbar.repository add command \ -label {Browse Current Branch} \ - -command {new_browser $current_branch} \ - -font font_ui + -command {new_browser $current_branch} trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#" .mbar.repository add separator .mbar.repository add command \ -label {Visualize Current Branch} \ - -command {do_gitk $current_branch} \ - -font font_ui + -command {do_gitk $current_branch} trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#" .mbar.repository add command \ -label {Visualize All Branches} \ - -command {do_gitk --all} \ - -font font_ui + -command {do_gitk --all} .mbar.repository add separator if {[is_enabled multicommit]} { .mbar.repository add command -label {Database Statistics} \ - -command do_stats \ - -font font_ui + -command do_stats .mbar.repository add command -label {Compress Database} \ - -command do_gc \ - -font font_ui + -command do_gc .mbar.repository add command -label {Verify Database} \ - -command do_fsck_objects \ - -font font_ui + -command do_fsck_objects .mbar.repository add separator if {[is_Cygwin]} { .mbar.repository add command \ -label {Create Desktop Icon} \ - -command do_cygwin_shortcut \ - -font font_ui + -command do_cygwin_shortcut } elseif {[is_Windows]} { .mbar.repository add command \ -label {Create Desktop Icon} \ - -command do_windows_shortcut \ - -font font_ui + -command do_windows_shortcut } elseif {[is_MacOSX]} { .mbar.repository add command \ -label {Create Desktop Icon} \ - -command do_macosx_app \ - -font font_ui + -command do_macosx_app } } .mbar.repository add command -label Quit \ -command do_quit \ - -accelerator $M1T-Q \ - -font font_ui + -accelerator $M1T-Q # -- Edit Menu # menu .mbar.edit .mbar.edit add command -label Undo \ -command {catch {[focus] edit undo}} \ - -accelerator $M1T-Z \ - -font font_ui + -accelerator $M1T-Z .mbar.edit add command -label Redo \ -command {catch {[focus] edit redo}} \ - -accelerator $M1T-Y \ - -font font_ui + -accelerator $M1T-Y .mbar.edit add separator .mbar.edit add command -label Cut \ -command {catch {tk_textCut [focus]}} \ - -accelerator $M1T-X \ - -font font_ui + -accelerator $M1T-X .mbar.edit add command -label Copy \ -command {catch {tk_textCopy [focus]}} \ - -accelerator $M1T-C \ - -font font_ui + -accelerator $M1T-C .mbar.edit add command -label Paste \ -command {catch {tk_textPaste [focus]; [focus] see insert}} \ - -accelerator $M1T-V \ - -font font_ui + -accelerator $M1T-V .mbar.edit add command -label Delete \ -command {catch {[focus] delete sel.first sel.last}} \ - -accelerator Del \ - -font font_ui + -accelerator Del .mbar.edit add separator .mbar.edit add command -label {Select All} \ -command {catch {[focus] tag add sel 0.0 end}} \ - -accelerator $M1T-A \ - -font font_ui + -accelerator $M1T-A # -- Branch Menu # @@ -5214,20 +5129,17 @@ if {[is_enabled branch]} { .mbar.branch add command -label {Create...} \ -command do_create_branch \ - -accelerator $M1T-N \ - -font font_ui + -accelerator $M1T-N lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] .mbar.branch add command -label {Delete...} \ - -command do_delete_branch \ - -font font_ui + -command do_delete_branch lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] .mbar.branch add command -label {Reset...} \ - -command do_reset_hard \ - -font font_ui + -command do_reset_hard lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] } @@ -5241,8 +5153,7 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { -label {New Commit} \ -command do_select_commit_type \ -variable selected_commit_type \ - -value new \ - -font font_ui + -value new lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] @@ -5250,8 +5161,7 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { -label {Amend Last Commit} \ -command do_select_commit_type \ -variable selected_commit_type \ - -value amend \ - -font font_ui + -value amend lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] @@ -5259,33 +5169,28 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { .mbar.commit add command -label Rescan \ -command do_rescan \ - -accelerator F5 \ - -font font_ui + -accelerator F5 lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Add To Commit} \ - -command do_add_selection \ - -font font_ui + -command do_add_selection lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Add Existing To Commit} \ -command do_add_all \ - -accelerator $M1T-I \ - -font font_ui + -accelerator $M1T-I lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Unstage From Commit} \ - -command do_unstage_selection \ - -font font_ui + -command do_unstage_selection lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Revert Changes} \ - -command do_revert_selection \ - -font font_ui + -command do_revert_selection lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] @@ -5293,13 +5198,11 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { .mbar.commit add command -label {Sign Off} \ -command do_signoff \ - -accelerator $M1T-S \ - -font font_ui + -accelerator $M1T-S .mbar.commit add command -label Commit \ -command do_commit \ - -accelerator $M1T-Return \ - -font font_ui + -accelerator $M1T-Return lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] } @@ -5309,13 +5212,11 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { if {[is_enabled branch]} { menu .mbar.merge .mbar.merge add command -label {Local Merge...} \ - -command do_local_merge \ - -font font_ui + -command do_local_merge lappend disable_on_lock \ [list .mbar.merge entryconf [.mbar.merge index last] -state] .mbar.merge add command -label {Abort Merge...} \ - -command do_reset_hard \ - -font font_ui + -command do_reset_hard lappend disable_on_lock \ [list .mbar.merge entryconf [.mbar.merge index last] -state] @@ -5328,8 +5229,7 @@ if {[is_enabled transport]} { menu .mbar.push .mbar.push add command -label {Push...} \ - -command do_push_anywhere \ - -font font_ui + -command do_push_anywhere } if {[is_MacOSX]} { @@ -5339,18 +5239,15 @@ if {[is_MacOSX]} { menu .mbar.apple .mbar.apple add command -label "About [appname]" \ - -command do_about \ - -font font_ui + -command do_about .mbar.apple add command -label "Options..." \ - -command do_options \ - -font font_ui + -command do_options } else { # -- Edit Menu # .mbar.edit add separator .mbar.edit add command -label {Options...} \ - -command do_options \ - -font font_ui + -command do_options # -- Tools Menu # @@ -5376,8 +5273,7 @@ if {[is_MacOSX]} { .mbar add cascade -label Tools -menu .mbar.tools menu .mbar.tools .mbar.tools add command -label "Migrate" \ - -command do_miga \ - -font font_ui + -command do_miga lappend disable_on_lock \ [list .mbar.tools entryconf [.mbar.tools index last] -state] } @@ -5385,13 +5281,12 @@ if {[is_MacOSX]} { # -- Help Menu # -.mbar add cascade -label Help -menu .mbar.help -font font_ui +.mbar add cascade -label Help -menu .mbar.help menu .mbar.help if {![is_MacOSX]} { .mbar.help add command -label "About [appname]" \ - -command do_about \ - -font font_ui + -command do_about } set browser {} @@ -5428,8 +5323,7 @@ if {[file isfile $doc_path]} { if {$browser ne {}} { .mbar.help add command -label {Online Documentation} \ - -command [list exec $browser $doc_url &] \ - -font font_ui + -command [list exec $browser $doc_url &] } unset browser doc_path doc_url @@ -5488,13 +5382,11 @@ frame .branch \ label .branch.l1 \ -text {Current Branch:} \ -anchor w \ - -justify left \ - -font font_ui + -justify left label .branch.cb \ -textvariable current_branch \ -anchor w \ - -justify left \ - -font font_ui + -justify left pack .branch.l1 -side left pack .branch.cb -side left -fill x pack .branch -side top -fill x @@ -5510,12 +5402,10 @@ pack .vpane -anchor n -side top -fill both -expand 1 # frame .vpane.files.index -height 100 -width 200 label .vpane.files.index.title -text {Changes To Be Committed} \ - -background green \ - -font font_ui + -background green text $ui_index -background white -borderwidth 0 \ -width 20 -height 10 \ -wrap none \ - -font font_ui \ -cursor $cursor_ptr \ -xscrollcommand {.vpane.files.index.sx set} \ -yscrollcommand {.vpane.files.index.sy set} \ @@ -5532,12 +5422,10 @@ pack $ui_index -side left -fill both -expand 1 # frame .vpane.files.workdir -height 100 -width 200 label .vpane.files.workdir.title -text {Changed But Not Updated} \ - -background red \ - -font font_ui + -background red text $ui_workdir -background white -borderwidth 0 \ -width 20 -height 10 \ -wrap none \ - -font font_ui \ -cursor $cursor_ptr \ -xscrollcommand {.vpane.files.workdir.sx set} \ -yscrollcommand {.vpane.files.workdir.sy set} \ @@ -5572,33 +5460,28 @@ pack .vpane.lower.diff -side bottom -fill both -expand 1 frame .vpane.lower.commarea.buttons label .vpane.lower.commarea.buttons.l -text {} \ -anchor w \ - -justify left \ - -font font_ui + -justify left pack .vpane.lower.commarea.buttons.l -side top -fill x pack .vpane.lower.commarea.buttons -side left -fill y button .vpane.lower.commarea.buttons.rescan -text {Rescan} \ - -command do_rescan \ - -font font_ui + -command do_rescan pack .vpane.lower.commarea.buttons.rescan -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.rescan conf -state} button .vpane.lower.commarea.buttons.incall -text {Add Existing} \ - -command do_add_all \ - -font font_ui + -command do_add_all pack .vpane.lower.commarea.buttons.incall -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.incall conf -state} button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \ - -command do_signoff \ - -font font_ui + -command do_signoff pack .vpane.lower.commarea.buttons.signoff -side top -fill x button .vpane.lower.commarea.buttons.commit -text {Commit} \ - -command do_commit \ - -font font_ui + -command do_commit pack .vpane.lower.commarea.buttons.commit -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.commit conf -state} @@ -5613,22 +5496,19 @@ radiobutton .vpane.lower.commarea.buffer.header.new \ -text {New Commit} \ -command do_select_commit_type \ -variable selected_commit_type \ - -value new \ - -font font_ui + -value new lappend disable_on_lock \ [list .vpane.lower.commarea.buffer.header.new conf -state] radiobutton .vpane.lower.commarea.buffer.header.amend \ -text {Amend Last Commit} \ -command do_select_commit_type \ -variable selected_commit_type \ - -value amend \ - -font font_ui + -value amend lappend disable_on_lock \ [list .vpane.lower.commarea.buffer.header.amend conf -state] label $ui_coml \ -anchor w \ - -justify left \ - -font font_ui + -justify left proc trace_commit_type {varname args} { global ui_coml commit_type switch -glob -- $commit_type { @@ -5667,28 +5547,22 @@ set ctxm .vpane.lower.commarea.buffer.ctxm menu $ctxm -tearoff 0 $ctxm add command \ -label {Cut} \ - -font font_ui \ -command {tk_textCut $ui_comm} $ctxm add command \ -label {Copy} \ - -font font_ui \ -command {tk_textCopy $ui_comm} $ctxm add command \ -label {Paste} \ - -font font_ui \ -command {tk_textPaste $ui_comm} $ctxm add command \ -label {Delete} \ - -font font_ui \ -command {$ui_comm delete sel.first sel.last} $ctxm add separator $ctxm add command \ -label {Select All} \ - -font font_ui \ -command {focus $ui_comm;$ui_comm tag add sel 0.0 end} $ctxm add command \ -label {Copy All} \ - -font font_ui \ -command { $ui_comm tag add sel 0.0 end tk_textCopy $ui_comm @@ -5697,7 +5571,6 @@ $ctxm add command \ $ctxm add separator $ctxm add command \ -label {Sign Off} \ - -font font_ui \ -command do_signoff bind_button3 $ui_comm "tk_popup $ctxm %X %Y" @@ -5732,18 +5605,15 @@ label .vpane.lower.diff.header.status \ -background orange \ -width $max_status_desc \ -anchor w \ - -justify left \ - -font font_ui + -justify left label .vpane.lower.diff.header.file \ -background orange \ -anchor w \ - -justify left \ - -font font_ui + -justify left label .vpane.lower.diff.header.path \ -background orange \ -anchor w \ - -justify left \ - -font font_ui + -justify left pack .vpane.lower.diff.header.status -side left pack .vpane.lower.diff.header.file -side left pack .vpane.lower.diff.header.path -fill x @@ -5751,7 +5621,6 @@ set ctxm .vpane.lower.diff.header.ctxm menu $ctxm -tearoff 0 $ctxm add command \ -label {Copy} \ - -font font_ui \ -command { clipboard clear clipboard append \ @@ -5820,22 +5689,18 @@ set ctxm .vpane.lower.diff.body.ctxm menu $ctxm -tearoff 0 $ctxm add command \ -label {Refresh} \ - -font font_ui \ -command reshow_diff lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ -label {Copy} \ - -font font_ui \ -command {tk_textCopy $ui_diff} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ -label {Select All} \ - -font font_ui \ -command {focus $ui_diff;$ui_diff tag add sel 0.0 end} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ -label {Copy All} \ - -font font_ui \ -command { $ui_diff tag add sel 0.0 end tk_textCopy $ui_diff @@ -5845,25 +5710,21 @@ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add separator $ctxm add command \ -label {Apply/Reverse Hunk} \ - -font font_ui \ -command {apply_hunk $cursorX $cursorY} set ui_diff_applyhunk [$ctxm index last] lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state] $ctxm add separator $ctxm add command \ -label {Decrease Font Size} \ - -font font_ui \ -command {incr_font_size font_diff -1} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ -label {Increase Font Size} \ - -font font_ui \ -command {incr_font_size font_diff 1} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add separator $ctxm add command \ -label {Show Less Context} \ - -font font_ui \ -command {if {$repo_config(gui.diffcontext) >= 2} { incr repo_config(gui.diffcontext) -1 reshow_diff @@ -5871,7 +5732,6 @@ $ctxm add command \ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ -label {Show More Context} \ - -font font_ui \ -command { incr repo_config(gui.diffcontext) reshow_diff @@ -5879,7 +5739,6 @@ $ctxm add command \ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add separator $ctxm add command -label {Options...} \ - -font font_ui \ -command do_options bind_button3 $ui_diff " set cursorX %x @@ -5899,8 +5758,7 @@ label .status -textvariable ui_status_value \ -anchor w \ -justify left \ -borderwidth 1 \ - -relief sunken \ - -font font_ui + -relief sunken pack .status -anchor w -side bottom -fill x # -- Load geometry From dc6716b83d3f90f8b1b2c611fe4f9f7c9fb14f5e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 29 Apr 2007 01:54:57 -0400 Subject: [PATCH 09/77] git-gui: Refactor to use our git proc more often Whenever we want to execute a git subcommand from the plumbing layer (and on rare occasion, the more porcelain-ish layer) we tend to use our proc wrapper, just to make the code slightly cleaner at the call sites. I wasn't doing that in a couple of places, so this is a simple cleanup to correct that. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 94f80bb2b1..4c01b91f04 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1298,12 +1298,12 @@ A rescan will be automatically started now. # -- Create the commit. # - set cmd [list git commit-tree $tree_id] + set cmd [list commit-tree $tree_id] foreach p [concat $PARENT $MERGE_HEAD] { lappend cmd -p $p } lappend cmd <$msg_p - if {[catch {set cmt_id [eval exec $cmd]} err]} { + if {[catch {set cmt_id [eval git $cmd]} err]} { error_popup "commit-tree failed:\n\n$err" set ui_status_value {Commit failed.} unlock_index @@ -1323,8 +1323,9 @@ A rescan will be automatically started now. set subject $msg } append reflogm {: } $subject - set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD] - if {[catch {eval exec $cmd} err]} { + if {[catch { + git update-ref -m $reflogm HEAD $cmt_id $curHEAD + } err]} { error_popup "update-ref failed:\n\n$err" set ui_status_value {Commit failed.} unlock_index @@ -2018,13 +2019,13 @@ proc do_create_branch_action {w} { -message "Invalid starting revision: $rev" return } - set cmd [list git update-ref] - lappend cmd -m - lappend cmd "branch: Created from $rev" - lappend cmd "refs/heads/$newbranch" - lappend cmd $cmt - lappend cmd $null_sha1 - if {[catch {eval exec $cmd} err]} { + if {[catch { + git update-ref \ + -m "branch: Created from $rev" \ + "refs/heads/$newbranch" \ + $cmt \ + $null_sha1 + } err]} { tk_messageBox \ -icon error \ -type ok \ From c6a5e4030320c934bca70471d04aa9e7d8e2cd67 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 29 Apr 2007 03:02:10 -0400 Subject: [PATCH 10/77] git-gui: Track our own embedded values and rebuild when they change Like core-Git we now track the values that we embed into our shell script wrapper, and we "recompile" that wrapper if they are changed. This concept was lifted from git.git's Makefile, where a similar thing was done by Eygene Ryabinkin. Too bad it wasn't just done here in git-gui from the beginning, as the git.git Makefile support for GIT-GUI-VARS was really just because git-gui doesn't do it on its own. Signed-off-by: Shawn O. Pearce --- .gitignore | 1 + Makefile | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c714d382e8..57cced8c18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ GIT-VERSION-FILE +GIT-GUI-VARS git-citool git-gui diff --git a/Makefile b/Makefile index b29d7d1e68..3b6282e734 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,19 @@ $(GITGUI_BUILT_INS): git-gui $(QUIET_BUILT_IN)rm -f $@ && ln git-gui $@ # These can record GITGUI_VERSION -$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE +$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE GIT-GUI-VARS + +TRACK_VARS = \ + $(subst ','\'',SHELL_PATH='$(SHELL_PATH_SQ)') \ + $(subst ','\'',TCLTK_PATH='$(TCLTK_PATH_SQ)') \ +#end TRACK_VARS + +GIT-GUI-VARS: .FORCE-GIT-GUI-VARS + @VARS='$(TRACK_VARS)'; \ + if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \ + echo 1>&2 " * new locations or Tcl/Tk interpreter"; \ + echo 1>$@ "$$VARS"; \ + fi all:: $(ALL_PROGRAMS) @@ -67,7 +79,8 @@ dist-version: @echo $(GITGUI_VERSION) > $(TARDIR)/version clean:: - rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE + rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE GIT-GUI-VARS .PHONY: all install dist-version clean .PHONY: .FORCE-GIT-VERSION-FILE +.PHONY: .FORCE-GIT-GUI-VARS From 953f3d6ff939b3c51d6c733a3d0e3f54e7477034 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Wed, 18 Apr 2007 00:20:46 -0400 Subject: [PATCH 11/77] user-manual: more discussion of detached heads, fix typos Nicolas Pitre pointed out a couple typos and some room for improvement in the discussion of detached heads. Signed-off-by: "J. Bruce Fields" Cc: Nicolas Pitre --- Documentation/user-manual.txt | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index dff438f768..54fd413846 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -527,17 +527,16 @@ and git branch shows that you are no longer on a branch: ------------------------------------------------ $ cat .git/HEAD 427abfa28afedffadfca9dd8b067eb6d36bac53f -git branch +$ git branch * (no branch) master ------------------------------------------------ In this case we say that the HEAD is "detached". -This can be an easy way to check out a particular version without having -to make up a name for a new branch. However, keep in mind that when you -switch away from the (for example, by checking out something else), you -can lose track of what the HEAD used to point to. +This is an easy way to check out a particular version without having to +make up a name for the new branch. You can still create a new branch +(or tag) for this version later if you decide to. Examining branches from a remote repository ------------------------------------------- @@ -1560,8 +1559,19 @@ $ git show master@{2} # See where the branch pointed 2, $ git show master@{3} # 3, ... changes ago. $ gitk master@{yesterday} # See where it pointed yesterday, $ gitk master@{"1 week ago"} # ... or last week +$ git log --walk-reflogs master # show reflog entries for master ------------------------------------------------- +A separate reflog is kept for the HEAD, so + +------------------------------------------------- +$ git show HEAD@{"1 week ago"} +------------------------------------------------- + +will show what HEAD pointed to one week ago, not what the current branch +pointed to one week ago. This allows you to see the history of what +you've checked out. + The reflogs are kept by default for 30 days, after which they may be pruned. See gitlink:git-reflog[1] and gitlink:git-gc[1] to learn how to control this pruning, and see the "SPECIFYING REVISIONS" From e34caace588612e9bc492aa9188390de42546aad Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Wed, 18 Apr 2007 00:46:19 -0400 Subject: [PATCH 12/77] user-manual: add section ID's Any section lacking an id gets an annoying warning when you build the manual. More seriously, the table of contents then generates volatile id's which change with every build, with the effect that we get URL's that change all the time. The ID's are manually generated and sometimes inconsistent, but that's OK. XXX: what to do about the preface? Signed-off-by: "J. Bruce Fields" --- Documentation/user-manual.txt | 84 ++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 54fd413846..d353d08c92 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -23,12 +23,14 @@ pages. For a command such as "git clone", just use $ man git-clone ------------------------------------------------ +[[git-quick-start]] Git Quick Start =============== This is a quick summary of the major commands; the following chapters will explain how these work in more detail. +[[quick-creating-a-new-repository]] Creating a new repository ------------------------- @@ -50,6 +52,7 @@ $ git clone git://example.com/pub/project.git $ cd project ----------------------------------------------- +[[managing-branches]] Managing branches ----------------- @@ -112,6 +115,7 @@ $ git branch -r # list all remote branches ----------------------------------------------- +[[exploring-history]] Exploring history ----------------- @@ -147,6 +151,7 @@ $ git bisect bad # if this revision is bad. # repeat until done. ----------------------------------------------- +[[making-changes]] Making changes -------------- @@ -177,6 +182,7 @@ $ git commit d.txt # use latest content only of d.txt $ git commit -a # use latest content of all tracked files ----------------------------------------------- +[[merging]] Merging ------- @@ -187,6 +193,7 @@ $ git pull git://example.com/project.git master $ git pull . test # equivalent to git merge test ----------------------------------------------- +[[sharing-your-changes]] Sharing your changes -------------------- @@ -232,6 +239,7 @@ $ git remote add example ssh://example.com/project.git $ git push example test ----------------------------------------------- +[[repository-maintenance]] Repository maintenance ---------------------- @@ -247,9 +255,11 @@ Recompress, remove unused cruft: $ git gc ----------------------------------------------- +[[repositories-and-branches]] Repositories and Branches ========================= +[[how-to-get-a-git-repository]] How to get a git repository --------------------------- @@ -280,6 +290,7 @@ contains all the information about the history of the project. In most of the following, examples will be taken from one of the two repositories above. +[[how-to-check-out]] How to check out a different version of a project ------------------------------------------------- @@ -352,6 +363,7 @@ particular point in history, then resetting that branch may leave you with no way to find the history it used to point to; so use this command carefully. +[[understanding-commits]] Understanding History: Commits ------------------------------ @@ -407,6 +419,7 @@ In fact, in <> we shall see that everything stored in git history, including file data and directory contents, is stored in an object with a name that is a hash of its contents. +[[understanding-reachability]] Understanding history: commits, parents, and reachability ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -431,6 +444,7 @@ if commit X is an ancestor of commit Y. Equivalently, you could say that Y is a descendent of X, or that there is a chain of parents leading from commit Y to commit X. +[[history-diagrams]] Understanding history: History diagrams ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -450,6 +464,7 @@ lines drawn with - / and \. Time goes left to right: If we need to talk about a particular commit, the character "o" may be replaced with another letter or number. +[[what-is-a-branch]] Understanding history: What is a branch? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -463,6 +478,7 @@ the line of three commits leading up to that point as all being part of However, when no confusion will result, we often just use the term "branch" both for branches and for branch heads. +[[manipulating-branches]] Manipulating branches --------------------- @@ -538,6 +554,7 @@ This is an easy way to check out a particular version without having to make up a name for the new branch. You can still create a new branch (or tag) for this version later if you decide to. +[[examining-remote-branches]] Examining branches from a remote repository ------------------------------------------- @@ -611,6 +628,7 @@ remote-tracking branches to the latest version found in her repository. It will not touch any of your own branches--not even the "master" branch that was created for you on clone. +[[fetching-branches]] Fetching branches from other repositories ----------------------------------------- @@ -653,6 +671,7 @@ or delete these configuration options by editing .git/config with a text editor. (See the "CONFIGURATION FILE" section of gitlink:git-config[1] for details.) +[[exploring-git-history]] Exploring git history ===================== @@ -667,6 +686,7 @@ history of a project. We start with one specialized tool that is useful for finding the commit that introduced a bug into a project. +[[using-bisect]] How to use bisect to find a regression -------------------------------------- @@ -734,6 +754,7 @@ $ git reset --hard fb47ddb2db... then test, run "bisect good" or "bisect bad" as appropriate, and continue. +[[naming-commits]] Naming commits -------------- @@ -798,6 +819,7 @@ $ git rev-parse origin e05db0fd4f31dde7005f075a84f96b360d05984b ------------------------------------------------- +[[creating-tags]] Creating tags ------------- @@ -815,6 +837,7 @@ share with others, and possibly sign cryptographically, then you should create a tag object instead; see the gitlink:git-tag[1] man page for details. +[[browsing-revisions]] Browsing revisions ------------------ @@ -856,6 +879,7 @@ backwards through the parents; however, since git history can contain multiple independent lines of development, the particular order that commits are listed in may be somewhat arbitrary. +[[generating-diffs]] Generating diffs ---------------- @@ -877,6 +901,7 @@ but not from master. Note that if master also has commits which are not reachable from test, then the combined result of these patches will not be the same as the diff produced by the git-diff example. +[[viewing-old-file-versions]] Viewing old file versions ------------------------- @@ -892,9 +917,11 @@ $ git show v2.5:fs/locks.c Before the colon may be anything that names a commit, and after it may be any path to a file tracked by git. +[[history-examples]] Examples -------- +[[checking-for-equal-branches]] Check whether two branches point at the same history ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -927,6 +954,7 @@ $ git log origin...master will return no commits when the two branches are equal. +[[finding-tagged-descendants]] Find first tagged version including a given fix ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1012,9 +1040,11 @@ Which shows that e05db0fd is reachable from itself, from v1.5.0-rc1, and from v1.5.0-rc2, but not from v1.5.0-rc0. +[[Developing-with-git]] Developing with git =================== +[[telling-git-your-name]] Telling git your name --------------------- @@ -1033,6 +1063,7 @@ EOF details on the configuration file.) +[[creating-a-new-repository]] Creating a new repository ------------------------- @@ -1149,6 +1180,7 @@ $ git diff # difference between the index file and your $ git status # a brief per-file summary of the above. ------------------------------------------------- +[[creating-good-commit-messages]] Creating good commit messages ----------------------------- @@ -1159,6 +1191,7 @@ description. Tools that turn commits into email, for example, use the first line on the Subject line and the rest of the commit in the body. +[[how-to-merge]] How to merge ------------ @@ -1236,6 +1269,7 @@ your own if desired. The above is all you need to know to resolve a simple merge. But git also provides more information to help resolve conflicts: +[[conflict-resolution]] Getting conflict-resolution help during a merge ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1359,6 +1393,7 @@ throw away a commit you have already committed if that commit may itself have been merged into another branch, as doing so may confuse further merges. +[[fast-forwards]] Fast-forward merges ------------------- @@ -1374,6 +1409,7 @@ already contained in the other--then git just performs a moved forward to point at the head of the merged-in branch, without any new commits being created. +[[fixing-mistakes]] Fixing mistakes --------------- @@ -1398,6 +1434,7 @@ fundamentally different ways to fix the problem: change, and cannot correctly perform repeated merges from a branch that has had its history changed. +[[reverting-a-commit]] Fixing a mistake with a new commit ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1452,6 +1489,7 @@ It is also possible to edit commits further back in the history, but this is an advanced topic to be left for <>. +[[checkout-of-path]] Checking out an old version of a file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1478,6 +1516,7 @@ $ git show HEAD^:path/to/file which will display the given version of the file. +[[ensuring-good-performance]] Ensuring good performance ------------------------- @@ -1494,9 +1533,12 @@ $ git gc to recompress the archive. This can be very time-consuming, so you may prefer to run git-gc when you are not doing other work. + +[[ensuring-reliability]] Ensuring reliability -------------------- +[[checking-for-corruption]] Checking the repository for corruption ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1532,9 +1574,11 @@ other git operations are in progress in the same repository. For more about dangling objects, see <>. +[[recovering-lost-changes]] Recovering lost changes ~~~~~~~~~~~~~~~~~~~~~~~ +[[reflogs]] Reflogs ^^^^^^^ @@ -1582,6 +1626,7 @@ While normal history is shared by every repository that works on the same project, the reflog history is not shared: it tells you only about how the branches in your local repository have changed over time. +[[dangling-objects]] Examining dangling objects ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1623,6 +1668,7 @@ $ git branch recovered-branch 7281251ddd ------------------------------------------------ +[[sharing-development]] Sharing development with others =============================== @@ -1682,6 +1728,7 @@ $ git merge branch are roughly equivalent. The former is actually very commonly used. +[[submitting-patches]] Submitting patches to a project ------------------------------- @@ -1703,6 +1750,7 @@ use the gitlink:git-send-email[1] script to automate the process. Consult the mailing list for your project first to determine how they prefer such patches be handled. +[[importing-patches]] Importing patches to a project ------------------------------ @@ -1898,6 +1946,7 @@ See the explanations of the remote..url, branch..remote, and remote..push options in gitlink:git-config[1] for details. +[[setting-up-a-shared-repository]] Setting up a shared repository ------------------------------ @@ -1907,6 +1956,7 @@ all push to and pull from a single shared repository. See link:cvs-migration.txt[git for CVS users] for instructions on how to set this up. +[[setting-up-gitweb]] Allow web browsing of a repository ---------------------------------- @@ -1914,6 +1964,7 @@ The gitweb cgi script provides users an easy way to browse your project's files and history without having to install git; see the file gitweb/INSTALL in the git source tree for instructions on setting it up. +[[sharing-development-examples]] Examples -------- @@ -1931,6 +1982,7 @@ cause git's merge machinery (for example) to do the wrong thing. However, there is a situation in which it can be useful to violate this assumption. +[[patch-series]] Creating the perfect patch series --------------------------------- @@ -1963,6 +2015,7 @@ We will introduce some tools that can help you do this, explain how to use them, and then explain some of the problems that can arise because you are rewriting history. +[[using-git-rebase]] Keeping a patch series up to date using git-rebase -------------------------------------------------- @@ -2044,6 +2097,7 @@ return mywork to the state it had before you started the rebase: $ git rebase --abort ------------------------------------------------- +[[modifying-one-commit]] Modifying a single commit ------------------------- @@ -2089,6 +2143,7 @@ Note that the immutable nature of git history means that you haven't really "modified" existing commits; instead, you have replaced the old commits with new commits having new object names. +[[reordering-patch-series]] Reordering or selecting from a patch series ------------------------------------------- @@ -2118,6 +2173,7 @@ $ git reset --hard origin Then modify, reorder, or eliminate patches as preferred before applying them again with gitlink:git-am[1]. +[[patch-series-tools]] Other tools ----------- @@ -2125,6 +2181,7 @@ There are numerous other tools, such as stgit, which exist for the purpose of maintaining a patch series. These are outside of the scope of this manual. +[[problems-with-rewriting-history]] Problems with rewriting history ------------------------------- @@ -2173,9 +2230,11 @@ branches into their own work. For true distributed development that supports proper merging, published branches should never be rewritten. +[[advanced-branch-management]] Advanced branch management ========================== +[[fetching-individual-branches]] Fetching individual branches ---------------------------- @@ -2204,7 +2263,7 @@ already have a branch named example-master, it will attempt to "fast-forward" to the commit given by example.com's master branch. So next we explain what a fast-forward is: -[[fast-forwards]] +[[fast-forwards-2]] Understanding git history: fast-forwards ---------------------------------------- @@ -2242,6 +2301,7 @@ situation above this may mean losing the commits labeled "a" and "b", unless you've already created a reference of your own pointing to them. +[[forcing-fetch]] Forcing git fetch to do non-fast-forward updates ------------------------------------------------ @@ -2256,6 +2316,7 @@ Note the addition of the "+" sign. Be aware that commits that the old version of example/master pointed at may be lost, as we saw in the previous section. +[[remote-branch-configuration]] Configuring remote branches --------------------------- @@ -2329,6 +2390,7 @@ Git internals Git depends on two fundamental abstractions: the "object database", and the "current directory cache" aka "index". +[[the-object-database]] The Object Database ------------------- @@ -2398,6 +2460,7 @@ to just verifying their superficial consistency through the hash). The object types in some more detail: +[[blob-object]] Blob Object ----------- @@ -2419,6 +2482,7 @@ file is associated with in any way. A blob is typically created when gitlink:git-update-index[1] is run, and its data can be accessed by gitlink:git-cat-file[1]. +[[tree-object]] Tree Object ----------- @@ -2460,6 +2524,7 @@ A tree is created with gitlink:git-write-tree[1] and its data can be accessed by gitlink:git-ls-tree[1]. Two trees can be compared with gitlink:git-diff-tree[1]. +[[commit-object]] Commit Object ------------- @@ -2486,6 +2551,7 @@ file manager. A commit is created with gitlink:git-commit-tree[1] and its data can be accessed by gitlink:git-cat-file[1]. +[[trust]] Trust ----- @@ -2515,6 +2581,7 @@ like GPG/PGP. To assist in this, git also provides the tag object... +[[tag-object]] Tag Object ---------- @@ -2537,6 +2604,7 @@ and the signature can be verified by gitlink:git-verify-tag[1]. +[[the-index]] The "index" aka "Current Directory Cache" ----------------------------------------- @@ -2591,6 +2659,7 @@ been written back to the backing store. +[[the-workflow]] The Workflow ------------ @@ -2600,6 +2669,7 @@ index), but most operations move data to and from the index file. Either from the database or from the working directory. Thus there are four main combinations: +[[working-directory-to-index]] working directory -> index ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2633,6 +2703,7 @@ stat information. It will 'not' update the object status itself, and it will only update the fields that are used to quickly test whether an object still matches its old backing store object. +[[index-to-object-database]] index -> object database ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2648,6 +2719,7 @@ and it will return the name of the resulting top-level tree. You can use that tree to re-generate the index at any time by going in the other direction: +[[object-database-to-index]] object database -> index ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2664,6 +2736,7 @@ and your index file will now be equivalent to the tree that you saved earlier. However, that is only your 'index' file: your working directory contents have not been modified. +[[index-to-working-directory]] index -> working directory ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2693,6 +2766,7 @@ need to use the "-f" flag ('before' the "-a" flag or the filename) to Finally, there are a few odds and ends which are not purely moving from one representation to the other: +[[tying-it-all-together]] Tying it all together ~~~~~~~~~~~~~~~~~~~~~ @@ -2768,6 +2842,7 @@ various pieces fit together. ------------ +[[examining-the-data]] Examining the data ------------------ @@ -2803,6 +2878,7 @@ $ git-cat-file commit HEAD to see what the top commit was. +[[merging-multiple-trees]] Merging multiple trees ---------------------- @@ -2854,6 +2930,7 @@ index file, and you can just write the result out with `git-write-tree`. +[[merging-multiple-trees-2]] Merging multiple trees, continued --------------------------------- @@ -2924,6 +3001,7 @@ $ git-merge-index git-merge-one-file hello.c and that is what higher level `git merge -s resolve` is implemented with. +[[pack-files]] How git stores objects efficiently: pack files ---------------------------------------------- @@ -2984,7 +3062,7 @@ objects will work exactly as they did before. The gitlink:git-gc[1] command performs packing, pruning, and more for you, so is normally the only high-level command you need. -[[dangling-objects]] +[[dangling-objects-2]] Dangling objects ---------------- @@ -3065,8 +3143,10 @@ confusing and scary messages, but it won't actually do anything bad. In contrast, running "git prune" while somebody is actively changing the repository is a *BAD* idea). +[[glossary]] include::glossary.txt[] +[[todo]] Notes and todo list for this manual =================================== From 597230403be52a13b8373222b7604047bacb4bd4 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Mon, 30 Apr 2007 11:11:02 -0400 Subject: [PATCH 13/77] user-manual: clean up fast-forward and dangling-objects sections The previous commit calls attention to the fact that we have two sections each devoted to fast-forwards and to dangling objects. Revise and attempt to differentiate them a bit. Some more reorganization may be required later.... Signed-off-by: J. Bruce Fields --- Documentation/user-manual.txt | 73 +++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index d353d08c92..03206c5344 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -1402,12 +1402,11 @@ differently. Normally, a merge results in a merge commit, with two parents, one pointing at each of the two lines of development that were merged. -However, if one of the two lines of development is completely -contained within the other--so every commit present in the one is -already contained in the other--then git just performs a -<>; the head of the current branch is -moved forward to point at the head of the merged-in branch, without -any new commits being created. +However, if the current branch is a descendant of the other--so every +commit present in the one is already contained in the other--then git +just performs a "fast forward"; the head of the current branch is moved +forward to point at the head of the merged-in branch, without any new +commits being created. [[fixing-mistakes]] Fixing mistakes @@ -1559,8 +1558,10 @@ dangling tree b24c2473f1fd3d91352a624795be026d64c8841f ... ------------------------------------------------- -Dangling objects are objects that are harmless, but also unnecessary; -you can remove them at any time with gitlink:git-prune[1] or the --prune +Dangling objects are not a problem. At worst they may take up a little +extra disk space. They can sometimes provide a last-resort method of +recovery lost work--see <> for details. However, if +you want, you may remove them with gitlink:git-prune[1] or the --prune option to gitlink:git-gc[1]: ------------------------------------------------- @@ -1571,9 +1572,6 @@ This may be time-consuming. Unlike most other git operations (including git-gc when run without any options), it is not safe to prune while other git operations are in progress in the same repository. -For more about dangling objects, see <>. - - [[recovering-lost-changes]] Recovering lost changes ~~~~~~~~~~~~~~~~~~~~~~~ @@ -1626,16 +1624,16 @@ While normal history is shared by every repository that works on the same project, the reflog history is not shared: it tells you only about how the branches in your local repository have changed over time. -[[dangling-objects]] +[[dangling-object-recovery]] Examining dangling objects ^^^^^^^^^^^^^^^^^^^^^^^^^^ -In some situations the reflog may not be able to save you. For -example, suppose you delete a branch, then realize you need the history -it contained. The reflog is also deleted; however, if you have not -yet pruned the repository, then you may still be able to find -the lost commits; run git-fsck and watch for output that mentions -"dangling commits": +In some situations the reflog may not be able to save you. For example, +suppose you delete a branch, then realize you need the history it +contained. The reflog is also deleted; however, if you have not yet +pruned the repository, then you may still be able to find the lost +commits in the dangling objects that git-fsck reports. See +<> for the details. ------------------------------------------------- $ git fsck @@ -1667,6 +1665,9 @@ reference pointing to it, for example, a new branch: $ git branch recovered-branch 7281251ddd ------------------------------------------------ +Other types of dangling objects (blobs and trees) are also possible, and +dangling objects can arise in other situations. + [[sharing-development]] Sharing development with others @@ -2260,18 +2261,18 @@ $ git fetch git://example.com/proj.git master:example-master will create a new branch named "example-master" and store in it the branch named "master" from the repository at the given URL. If you already have a branch named example-master, it will attempt to -"fast-forward" to the commit given by example.com's master branch. So -next we explain what a fast-forward is: +<> to the commit given by example.com's +master branch. In more detail: -[[fast-forwards-2]] -Understanding git history: fast-forwards ----------------------------------------- +[[fetch-fast-forwards]] +git fetch and fast-forwards +--------------------------- In the previous example, when updating an existing branch, "git fetch" checks to make sure that the most recent commit on the remote branch is a descendant of the most recent commit on your copy of the branch before updating your copy of the branch to point at the new -commit. Git calls this process a "fast forward". +commit. Git calls this process a <>. A fast forward looks something like this: @@ -3062,7 +3063,7 @@ objects will work exactly as they did before. The gitlink:git-gc[1] command performs packing, pruning, and more for you, so is normally the only high-level command you need. -[[dangling-objects-2]] +[[dangling-objects]] Dangling objects ---------------- @@ -3072,11 +3073,10 @@ objects. They are not a problem. The most common cause of dangling objects is that you've rebased a branch, or you have pulled from somebody else who rebased a branch--see <>. In that case, the old head of the original -branch still exists, as does obviously everything it pointed to. The -branch pointer itself just doesn't, since you replaced it with another -one. +branch still exists, as does everything it pointed to. The branch +pointer itself just doesn't, since you replaced it with another one. -There are also other situations too that cause dangling objects. For +There are also other situations that cause dangling objects. For example, a "dangling blob" may arise because you did a "git add" of a file, but then, before you actually committed it and made it part of the bigger picture, you changed something else in that file and committed @@ -3098,15 +3098,22 @@ be how you recover your old tree (say, you did a rebase, and realized that you really didn't want to - you can look at what dangling objects you have, and decide to reset your head to some old dangling state). -For commits, the most useful thing to do with dangling objects tends to -be to do a simple +For commits, you can just use: ------------------------------------------------ $ gitk --not --all ------------------------------------------------ -For blobs and trees, you can't do the same, but you can examine them. -You can just do +This asks for all the history reachable from the given commit but not +from any branch, tag, or other reference. If you decide it's something +you want, you can always create a new reference to it, e.g., + +------------------------------------------------ +$ git branch recovered-branch +------------------------------------------------ + +For blobs and trees, you can't do the same, but you can still examine +them. You can just do ------------------------------------------------ $ git show From 58c19d1f95f4742efc6b3ca7d2fddbccd015e0b0 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Mon, 7 May 2007 00:16:33 -0400 Subject: [PATCH 14/77] user-manual: fix .gitconfig editing examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Santi Béjar points out that when telling people how to "introduce themselves" to git we're advising them to replace their entire .gitconfig file. Fix that. Cc: "Santi Béjar Signed-off-by: "J. Bruce Fields" --- Documentation/user-manual.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 03206c5344..c292b4d797 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -158,7 +158,7 @@ Making changes Make sure git knows who to blame: ------------------------------------------------ -$ cat >~/.gitconfig <<\EOF +$ cat >>~/.gitconfig <<\EOF [user] name = Your Name Comes Here email = you@yourdomain.example.com @@ -1049,14 +1049,13 @@ Telling git your name --------------------- Before creating any commits, you should introduce yourself to git. The -easiest way to do so is: +easiest way to do so is to make sure the following lines appear in a +file named .gitconfig in your home directory: ------------------------------------------------ -$ cat >~/.gitconfig <<\EOF [user] name = Your Name Comes Here email = you@yourdomain.example.com -EOF ------------------------------------------------ (See the "CONFIGURATION FILE" section of gitlink:git-config[1] for From c64415e29ecf8e26dc2c4451f9a04023dd941ac7 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Mon, 7 May 2007 00:56:45 -0400 Subject: [PATCH 15/77] user-manual: miscellaneous editing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I cherry-picked some additional miscellaneous fixes from those suggested by Santi Béjar, including fixes to: - correct discussion of repository/HEAD->repository shortcut - add mention of git-mergetool - add mention of --track - mention "-f" as well as "+" for fetch Cc: Santi Béjar Signed-off-by: "J. Bruce Fields" --- Documentation/user-manual.txt | 82 ++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index c292b4d797..67f5b9b6ab 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -57,7 +57,7 @@ Managing branches ----------------- ----------------------------------------------- -$ git branch # list all branches in this repo +$ git branch # list all local branches in this repo $ git checkout test # switch working directory to branch "test" $ git branch new # create branch "new" starting at current HEAD $ git branch -d new # delete branch "new" @@ -496,8 +496,8 @@ git branch :: including using a branch name or a tag name git branch -d :: delete the branch ; if the branch you are deleting - points to a commit which is not reachable from this branch, - this command will fail with a warning. + points to a commit which is not reachable from the current + branch, this command will fail with a warning. git branch -D :: even if the branch points to a commit not reachable from the current branch, you may know that that commit @@ -602,13 +602,9 @@ shorthand: The full name is occasionally useful if, for example, there ever exists a tag and a branch with the same name. -As another useful shortcut, if the repository "origin" posesses only -a single branch, you can refer to that branch as just "origin". - -More generally, if you have defined a remote repository named -"example", you can refer to the branch in that repository as -"example". And for a repository with multiple branches, this will -refer to the branch designated as the "HEAD" branch. +As another useful shortcut, the "HEAD" of a repository can be referred +to just using the name of that repository. So, for example, "origin" +is usually a shortcut for the HEAD branch in the repository "origin". For the complete list of paths which git checks for references, and the order it uses to decide which to choose when there are multiple @@ -832,10 +828,10 @@ $ git tag stable-1 1b2e1d63ff You can use stable-1 to refer to the commit 1b2e1d63ff. -This creates a "lightweight" tag. If the tag is a tag you wish to -share with others, and possibly sign cryptographically, then you -should create a tag object instead; see the gitlink:git-tag[1] man -page for details. +This creates a "lightweight" tag. If you would also like to include a +comment with the tag, and possibly sign it cryptographically, then you +should create a tag object instead; see the gitlink:git-tag[1] man page +for details. [[browsing-revisions]] Browsing revisions @@ -1176,6 +1172,8 @@ $ git diff --cached # difference between HEAD and the index; what $ git diff # difference between the index file and your # working directory; changes that would not # be included if you ran "commit" now. +$ git diff HEAD # difference between HEAD and working tree; what + # would be committed if you ran "commit -a" now. $ git status # a brief per-file summary of the above. ------------------------------------------------- @@ -1223,8 +1221,6 @@ If you examine the resulting commit using gitk, you will see that it has two parents, one pointing to the top of the current branch, and one to the top of the other branch. -In more detail: - [[resolving-a-merge]] Resolving a merge ----------------- @@ -1361,6 +1357,9 @@ $ gitk --merge These will display all commits which exist only on HEAD or on MERGE_HEAD, and which touch an unmerged file. +You may also use gitlink:git-mergetool, which lets you merge the +unmerged files using external tools such as emacs or kdiff3. + Each time you resolve the conflicts in a file and update the index: ------------------------------------------------- @@ -1705,9 +1704,16 @@ so often you can accomplish the above with just $ git pull ------------------------------------------------- -See the descriptions of the branch..remote and -branch..merge options in gitlink:git-config[1] to learn -how to control these defaults depending on the current branch. +See the descriptions of the branch..remote and branch..merge +options in gitlink:git-config[1] to learn how to control these defaults +depending on the current branch. Also note that the --track option to +gitlink:git-branch[1] and gitlink:git-checkout[1] can be used to +automatically set the default remote branch to pull from at the time +that a branch is created: + +------------------------------------------------- +$ git checkout --track -b origin/maint maint +------------------------------------------------- In addition to saving you keystrokes, "git pull" also helps you by producing a default commit message documenting the branch and @@ -1830,14 +1836,14 @@ Now, assume your personal repository is in the directory ~/proj. We first create a new clone of the repository: ------------------------------------------------- -$ git clone --bare proj-clone.git +$ git clone --bare proj.git ------------------------------------------------- -The resulting directory proj-clone.git will contains a "bare" git +The resulting directory proj.git will contains a "bare" git repository--it is just the contents of the ".git" directory, without a checked-out copy of a working directory. -Next, copy proj-clone.git to the server where you plan to host the +Next, copy proj.git to the server where you plan to host the public repository. You can use scp, rsync, or whatever is most convenient. @@ -1863,7 +1869,7 @@ adjustments to give web clients some extra information they need: ------------------------------------------------- $ mv proj.git /home/you/public_html/proj.git $ cd proj.git -$ git update-server-info +$ git --bare update-server-info $ chmod a+x hooks/post-update ------------------------------------------------- @@ -1930,7 +1936,7 @@ As with git-fetch, you may also set up configuration options to save typing; so, for example, after ------------------------------------------------- -$ cat >.git/config <>.git/config <hello.c~1 $ git-cat-file blob 06fa6a2... >hello.c~2 $ git-cat-file blob cc44c73... >hello.c~3 -$ merge hello.c~2 hello.c~1 hello.c~3 +$ git merge-file hello.c~2 hello.c~1 hello.c~3 ------------------------------------------------ This would leave the merge result in `hello.c~2` file, along From a1dc34fa951a2ee8a2c85fbb9d8fdf1d63b54b43 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Mon, 7 May 2007 01:01:42 -0400 Subject: [PATCH 16/77] user-manual: stop deprecating the manual It's just as much a work-in-progress, but at least now it's gotten enough technical review to shake out most of the really bad lies, so hopefully it doesn't do any actual damage. And if we encourage people to read it, they'll be more likely to whine about it, which will help get it fixed faster. Signed-off-by: "J. Bruce Fields" --- Documentation/git.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Documentation/git.txt b/Documentation/git.txt index aa65802c86..c2b7f3fec6 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -21,10 +21,9 @@ and full access to internals. See this link:tutorial.html[tutorial] to get started, then see link:everyday.html[Everyday Git] for a useful minimum set of commands, and "man git-commandname" for documentation of each command. CVS users may -also want to read link:cvs-migration.html[CVS migration]. -link:user-manual.html[Git User's Manual] is still work in -progress, but when finished hopefully it will guide a new user -in a coherent way to git enlightenment ;-). +also want to read link:cvs-migration.html[CVS migration]. See +link:user-manual.html[Git User's Manual] for a more in-depth +introduction. The COMMAND is either a name of a Git command (see below) or an alias as defined in the configuration file (see gitlink:git-config[1]). From 604d7a1ac0405c2ce048a1113e60d9d0c0d3e3d9 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 7 May 2007 01:28:34 -0400 Subject: [PATCH 17/77] Documentation: don't reference non-existent 'git-cvsapplycommit' Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-cvsexportcommit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index 555b8234f0..fd7f54093f 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -25,7 +25,7 @@ by default. Supports file additions, removals, and commits that affect binary files. -If the commit is a merge commit, you must tell git-cvsapplycommit what parent +If the commit is a merge commit, you must tell git-cvsexportcommit what parent should the changeset be done against. OPTIONS From bc3561f35991f97adfa2b22a8a5093f19717c71f Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Sun, 6 May 2007 23:11:55 +0200 Subject: [PATCH 18/77] Document git add -u introduced earlier. This command was implemented, but not documented in dfdac5d9b877641d3aad8ec49f64c2730a3487e3. Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- Documentation/git-add.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 755d7186f5..ea2701846f 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -7,7 +7,7 @@ git-add - Add file contents to the changeset to be committed next SYNOPSIS -------- -'git-add' [-n] [-v] [-f] [--interactive | -i] [--] ... +'git-add' [-n] [-v] [-f] [--interactive | -i] [-u] [--] ... DESCRIPTION ----------- @@ -56,6 +56,10 @@ OPTIONS Add modified contents in the working tree interactively to the index. +-u:: + Update all files that git already knows about. This is what + "git commit -a" does in preparation for making a commit. + \--:: This option can be used to separate command-line options from the list of files, (useful when filenames might be mistaken From e701ccc3883959b4ba5fada7a903e8e9eaddcba7 Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Sun, 6 May 2007 23:11:56 +0200 Subject: [PATCH 19/77] Added a reference to git-add in the documentation for git-update-index Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- Documentation/git-update-index.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index cd5e014d48..6cfbd9a842 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -27,6 +27,9 @@ Modifies the index or directory cache. Each file mentioned is updated into the index and any 'unmerged' or 'needs updating' state is cleared. +See also gitlink:git-add[1] for a more user-friendly way to do some of +the most common operations on the index. + The way "git-update-index" handles files it is told about can be modified using the various options: @@ -306,7 +309,8 @@ The command looks at `core.ignorestat` configuration variable. See See Also -------- -gitlink:git-config[1] +gitlink:git-config[1], +gitlink:git-add[1] Author From b991625611da1cba39c6ac50d0329e5d5ce5d0ab Mon Sep 17 00:00:00 2001 From: Michael Spang Date: Sun, 6 May 2007 22:35:04 -0400 Subject: [PATCH 20/77] dir.c: Omit non-excluded directories with dir->show_ignored This makes "git-ls-files --others --directory --ignored" behave as documented and consequently also fixes "git-clean -d -X". Previously, git-clean would remove non-excluded directories even when using the -X option. Signed-off-by: Michael Spang Signed-off-by: Junio C Hamano --- dir.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dir.c b/dir.c index d3063520b0..11fab7f4bf 100644 --- a/dir.c +++ b/dir.c @@ -448,6 +448,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co while ((de = readdir(fdir)) != NULL) { int len; + int exclude; if ((de->d_name[0] == '.') && (de->d_name[1] == 0 || @@ -461,7 +462,9 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co memcpy(fullname + baselen, de->d_name, len+1); if (simplify_away(fullname, baselen + len, simplify)) continue; - if (excluded(dir, fullname) != dir->show_ignored) { + + exclude = excluded(dir, fullname); + if (exclude != dir->show_ignored) { if (!dir->show_ignored || DTYPE(de) != DT_DIR) { continue; } @@ -484,6 +487,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co len++; switch (treat_directory(dir, fullname, baselen + len, simplify)) { case show_directory: + if (exclude != dir->show_ignored) + continue; break; case recurse_into_directory: contents += read_directory_recursive(dir, From ec0e0f25dce3686fa7ad8fb1783140076983822c Mon Sep 17 00:00:00 2001 From: Michael Spang Date: Sun, 6 May 2007 15:50:54 -0400 Subject: [PATCH 21/77] t7300: Basic tests for git-clean This tests the -d, -n, -f, -x, and -X options to git-clean. Signed-off-by: Michael Spang Signed-off-by: Junio C Hamano --- t/t7300-clean.sh | 180 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100755 t/t7300-clean.sh diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh new file mode 100755 index 0000000000..de70b38d1c --- /dev/null +++ b/t/t7300-clean.sh @@ -0,0 +1,180 @@ +#!/bin/sh +# +# Copyright (c) 2007 Michael Spang +# + +test_description='git-clean basic tests' + +. ./test-lib.sh + +test_expect_success 'setup' ' + + mkdir -p src && + touch src/part1.c Makefile && + echo build >.gitignore && + echo \*.o >>.gitignore && + git-add . && + git-commit -m setup && + touch src/part2.c README && + git-add . + +' + +test_expect_success 'git-clean' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git-clean && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git-clean -n' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git-clean -n && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f src/part3.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git-clean -d' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git-clean -d && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test ! -d docs && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git-clean -x' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git-clean -x && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test -f docs/manual.txt && + test ! -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git-clean -d -x' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git-clean -d -x && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test ! -d docs && + test ! -f obj.o && + test ! -d build + +' + +test_expect_success 'git-clean -X' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git-clean -X && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f src/part3.c && + test -f docs/manual.txt && + test ! -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git-clean -d -X' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git-clean -d -X && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f src/part3.c && + test -f docs/manual.txt && + test ! -f obj.o && + test ! -d build + +' + +test_expect_success 'clean.requireForce' ' + + git-config clean.requireForce true && + ! git-clean + +' + +test_expect_success 'clean.requireForce and -n' ' + + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git-clean -n && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f src/part3.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'clean.requireForce and -f' ' + + git-clean -f && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' + +test_done From 0fc4baebf318d211ad35af447cde7cd814aa57e9 Mon Sep 17 00:00:00 2001 From: Michael Spang Date: Sun, 6 May 2007 14:09:34 -0400 Subject: [PATCH 22/77] Fix minor documentation errors - git-ls-files.txt: typo in description of --ignored - git-clean.txt: s/forceRequire/requireForce/ Signed-off-by: Michael Spang Signed-off-by: Junio C Hamano --- Documentation/git-clean.txt | 2 +- Documentation/git-ls-files.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index 5aff026eb4..e3252d59da 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -26,7 +26,7 @@ OPTIONS Remove untracked directories in addition to untracked files. -f:: - If the git configuration specifies clean.forceRequire as true, + If the git configuration specifies clean.requireForce as true, git-clean will refuse to run unless given -f or -n. -n:: diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 79e0b7b71a..076cebca17 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -42,8 +42,8 @@ OPTIONS Show other files in the output -i|--ignored:: - Show ignored files in the output - Note the this also reverses any exclude list present. + Show ignored files in the output. + Note that this also reverses any exclude list present. -s|--stage:: Show stage files in the output From 3082acfa7c626a34aa419a163585051c2df2bf09 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Mon, 7 May 2007 19:33:24 +0200 Subject: [PATCH 23/77] Use GIT_OBJECT_DIR for temporary files of pack-objects Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index b827627670..7bff8eadd8 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -559,6 +559,12 @@ static off_t write_one(struct sha1file *f, return offset + size; } +static int open_object_dir_tmp(const char *path) +{ + snprintf(tmpname, sizeof(tmpname), "%s/%s", get_object_directory(), path); + return mkstemp(tmpname); +} + static off_t write_pack_file(void) { uint32_t i; @@ -571,9 +577,7 @@ static off_t write_pack_file(void) f = sha1fd(1, ""); do_progress >>= 1; } else { - int fd; - snprintf(tmpname, sizeof(tmpname), "tmp_pack_XXXXXX"); - fd = mkstemp(tmpname); + int fd = open_object_dir_tmp("tmp_pack_XXXXXX"); if (fd < 0) die("unable to create %s: %s\n", tmpname, strerror(errno)); pack_tmp_name = xstrdup(tmpname); @@ -623,10 +627,8 @@ static void write_index_file(off_t last_obj_offset, unsigned char *sha1) uint32_t array[256]; uint32_t i, index_version; SHA_CTX ctx; - int fd; - snprintf(tmpname, sizeof(tmpname), "tmp_idx_XXXXXX"); - fd = mkstemp(tmpname); + int fd = open_object_dir_tmp("tmp_idx_XXXXXX"); if (fd < 0) die("unable to create %s: %s\n", tmpname, strerror(errno)); idx_tmp_name = xstrdup(tmpname); From fc3abdf5cb16914fb22747d7c3fb44258712a36d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 May 2007 13:05:48 -0700 Subject: [PATCH 24/77] diff: release blobs after generating textual diff. This reduces the memory pressure when dealing with many paths. An unscientific test of running "diff-tree --stat --summary -M" between v2.6.19 and v2.6.20-rc1 in the linux kernel repository indicates that the number of minor faults are reduced by 2/3. Signed-off-by: Junio C Hamano --- diff.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/diff.c b/diff.c index 7bbe7590b2..2979bc57e4 100644 --- a/diff.c +++ b/diff.c @@ -1236,6 +1236,8 @@ static void builtin_diff(const char *name_a, } free_ab_and_return: + diff_free_filespec_data(one); + diff_free_filespec_data(two); free(a_one); free(b_two); return; @@ -1262,7 +1264,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b, diff_populate_filespec(two, 0); data->deleted = count_lines(one->data, one->size); data->added = count_lines(two->data, two->size); - return; + goto free_and_return; } if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); @@ -1284,6 +1286,10 @@ static void builtin_diffstat(const char *name_a, const char *name_b, ecb.priv = diffstat; xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); } + + free_and_return: + diff_free_filespec_data(one); + diff_free_filespec_data(two); } static void builtin_checkdiff(const char *name_a, const char *name_b, @@ -1306,7 +1312,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, die("unable to read files to diff"); if (file_is_binary(two)) - return; + goto free_and_return; else { /* Crazy xdl interfaces.. */ xpparam_t xpp; @@ -1320,6 +1326,9 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, ecb.priv = &data; xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); } + free_and_return: + diff_free_filespec_data(one); + diff_free_filespec_data(two); } struct diff_filespec *alloc_filespec(const char *path) @@ -1507,7 +1516,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) size_only = 0; if (s->data) - return err; + return 0; if (S_ISDIRLNK(s->mode)) return diff_populate_gitlink(s, size_only); @@ -1597,8 +1606,11 @@ void diff_free_filespec_data(struct diff_filespec *s) free(s->data); else if (s->should_munmap) munmap(s->data, s->size); - s->should_free = s->should_munmap = 0; - s->data = NULL; + + if (s->should_free || s->should_munmap) { + s->should_free = s->should_munmap = 0; + s->data = NULL; + } free(s->cnt_data); s->cnt_data = NULL; } From 6e0b8ed6d35d0ed94b22652d6e8545b610cc43ab Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 7 May 2007 01:14:21 -0700 Subject: [PATCH 25/77] diff.c: do not use a separate "size cache". diff_filespec has a slot to record the size of the data already, so make use of it instead of a separate size cache. Signed-off-by: Junio C Hamano --- diff.c | 72 ++++------------------------------------------------------ 1 file changed, 4 insertions(+), 68 deletions(-) diff --git a/diff.c b/diff.c index 2979bc57e4..8354e71e07 100644 --- a/diff.c +++ b/diff.c @@ -16,8 +16,6 @@ #define FAST_WORKING_DIRECTORY 1 #endif -static int use_size_cache; - static int diff_detect_rename_default; static int diff_rename_limit_default = -1; static int diff_use_color_default; @@ -1408,55 +1406,6 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int return 1; } -static struct sha1_size_cache { - unsigned char sha1[20]; - unsigned long size; -} **sha1_size_cache; -static int sha1_size_cache_nr, sha1_size_cache_alloc; - -static struct sha1_size_cache *locate_size_cache(unsigned char *sha1, - int find_only, - unsigned long size) -{ - int first, last; - struct sha1_size_cache *e; - - first = 0; - last = sha1_size_cache_nr; - while (last > first) { - int cmp, next = (last + first) >> 1; - e = sha1_size_cache[next]; - cmp = hashcmp(e->sha1, sha1); - if (!cmp) - return e; - if (cmp < 0) { - last = next; - continue; - } - first = next+1; - } - /* not found */ - if (find_only) - return NULL; - /* insert to make it at "first" */ - if (sha1_size_cache_alloc <= sha1_size_cache_nr) { - sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc); - sha1_size_cache = xrealloc(sha1_size_cache, - sha1_size_cache_alloc * - sizeof(*sha1_size_cache)); - } - sha1_size_cache_nr++; - if (first < sha1_size_cache_nr) - memmove(sha1_size_cache + first + 1, sha1_size_cache + first, - (sha1_size_cache_nr - first - 1) * - sizeof(*sha1_size_cache)); - e = xmalloc(sizeof(struct sha1_size_cache)); - sha1_size_cache[first] = e; - hashcpy(e->sha1, sha1); - e->size = size; - return e; -} - static int populate_from_stdin(struct diff_filespec *s) { #define INCREMENT 1024 @@ -1512,12 +1461,12 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) if (S_ISDIR(s->mode)) return -1; - if (!use_size_cache) - size_only = 0; - if (s->data) return 0; + if (size_only && 0 < s->size) + return 0; + if (S_ISDIRLNK(s->mode)) return diff_populate_gitlink(s, size_only); @@ -1579,19 +1528,8 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) } else { enum object_type type; - struct sha1_size_cache *e; - - if (size_only && use_size_cache && - (e = locate_size_cache(s->sha1, 1, 0)) != NULL) { - s->size = e->size; - return 0; - } - - if (size_only) { + if (size_only) type = sha1_object_info(s->sha1, &s->size); - if (use_size_cache && 0 < type) - locate_size_cache(s->sha1, 0, s->size); - } else { s->data = read_sha1_file(s->sha1, &type, &s->size); s->should_free = 1; @@ -2102,8 +2040,6 @@ int diff_setup_done(struct diff_options *options) */ read_cache(); } - if (options->setup & DIFF_SETUP_USE_SIZE_CACHE) - use_size_cache = 1; if (options->abbrev <= 0 || 40 < options->abbrev) options->abbrev = 40; /* full */ From 50b2b53897d6f0331c192f2964542ed62a9911c0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 May 2007 13:04:53 -0700 Subject: [PATCH 26/77] diff -M: release the preimage candidate blobs after rename detection. We released the postimage candidate blobs after we are done to reduce memory pressure. Do the same for preimage candidate blobs. Signed-off-by: Junio C Hamano --- diffcore-rename.c | 1 + 1 file changed, 1 insertion(+) diff --git a/diffcore-rename.c b/diffcore-rename.c index 79030412db..93c40d9e04 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -329,6 +329,7 @@ void diffcore_rename(struct diff_options *options) m->dst = i; m->score = estimate_similarity(one, two, minimum_score); + diff_free_filespec_data(one); } /* We do not need the text anymore */ diff_free_filespec_data(two); From a0cb94006ccc6bde6fe89b2a4d8bf7ea84c8b876 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 7 May 2007 01:24:27 -0700 Subject: [PATCH 27/77] diff -S: release the image after looking for needle in it Signed-off-by: Junio C Hamano --- diffcore-pickaxe.c | 1 + 1 file changed, 1 insertion(+) diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index 286919e714..c4a77d71da 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -44,6 +44,7 @@ static unsigned int contains(struct diff_filespec *one, } } } + diff_free_filespec_data(one); return cnt; } From d966e6aa66d397908b4fbf69cef2e2da88737321 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 7 May 2007 21:13:40 -0400 Subject: [PATCH 28/77] Properly handle '0' filenames in import-tars Randal L. Schwartz pointed out multiple times that we should be testing the length of the name string here, not if it is "true". The problem is the string '0' is actually false in Perl when we try to evaluate it in this context, as '0' is 0 numerically and the number 0 is treated as a false value. This would cause us to break out of the import loop early if anyone had a file or directory named "0". Signed-off-by: Shawn O. Pearce --- contrib/fast-import/import-tars.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl index e46492048c..f0b9a43abd 100755 --- a/contrib/fast-import/import-tars.perl +++ b/contrib/fast-import/import-tars.perl @@ -51,7 +51,7 @@ foreach my $tar_file (@ARGV) $prefix) = unpack 'Z100 Z8 Z8 Z8 Z12 Z12 Z8 Z1 Z100 Z6 Z2 Z32 Z32 Z8 Z8 Z*', $_; - last unless $name; + last unless length($name); if ($name eq '././@LongLink') { # GNU tar extension if (read(I, $_, 512) != 512) { From 78bc403aaffe4dc7854da118e977685b38bdc5b7 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 7 May 2007 01:10:03 +0200 Subject: [PATCH 29/77] gitweb: Add parsing of raw combined diff format to parse_difftree_raw_line Add parsing line of raw combined diff ("git diff-tree -c/-cc" output) as described in section "diff format for merges" in diff-format.txt to parse_difftree_raw_line subroutine. Returned hash (or hashref) has for combined diff 'nparents' key which holds number of parents in a merge. At keys 'from_mode' and 'from_id' there are arrayrefs holding modes and ids, respectively. There is no 'similarity' value, and there is only 'to_file' value and no 'from_file' value. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ba5cc43e5b..dfba399d8e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1495,6 +1495,17 @@ sub parse_difftree_raw_line { $res{'file'} = unquote($7); } } + # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh' + # combined diff (for merge commit) + elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) { + $res{'nparents'} = length($1); + $res{'from_mode'} = [ split(' ', $2) ]; + $res{'to_mode'} = pop @{$res{'from_mode'}}; + $res{'from_id'} = [ split(' ', $3) ]; + $res{'to_id'} = pop @{$res{'from_id'}}; + $res{'status'} = [ split('', $4) ]; + $res{'to_file'} = unquote($5); + } # 'c512b523472485aef4fff9e57b229d9d243c967f' elsif ($line =~ m/^([0-9a-fA-F]{40})$/) { $res{'commit'} = $1; From ed224deac9436215f8c0b098baa0aecfcff1c293 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 7 May 2007 01:10:04 +0200 Subject: [PATCH 30/77] gitweb: Add combined diff support to git_difftree_body You have to pass all parents as final parameters of git_difftree_body subroutine; the number of parents of a diff must be equal to the number derived from parsing git-diff-tree output, raw combined diff for git_difftree_body to display combined diff correctly (but it is not checked). Currently the possibility of displaying diffree of combined diff is not used in gitweb code; git_difftree_body is always caled for ordinary diff, and with only one parent. Description of output for combined diff: ---------------------------------------- The difftree table for combined diff starts with a cell with pathname of changed blob (changed file), which if possible is hidden link (class="list") to the 'blob' view of final version (if it exists), like for difftree for ordinary diff. If file was deleted in the final commit then filename is not hyperlinked. There is no cell with single file status (new, deleted, mode change, rename), as for combined diff as there is no single status: different parents might have different status. If git_difftree_body was called from git_commitdiff (for 'commitdiff' action) there is inner link to anchor to appropriate fragment (patch) in patchset body; the "patch" link does not replace "diff" link like for ordinary diff. Each of "diff" links is in separate cell, contrary to output for ordinary diff in which all links are (at least for now) in a single cell. For each parent, if file was not present we leave cell empty. If file was deleted in the result, we provide link to 'blob' view. Otherwise we provide link to 'commitdiff' view, even if patch (diff) consist only of extended diff header, and contents is not changed (pure rename, pure mode change). The only difference is that link to "blobdiff" view with no contents change is with 'nochange' class. At last, there is provided link to current version of file as "blob" link, if the file was not deleted in the result, and lik to history of a file, if there exists one. (The link to file history might be confused, at least for now, by renames.) Note that git-diff-tree raw output dor combined diff does not provide filename before change for renames and copies; we use git_get_path_by_hash to get "src" filename for renames (this means additional call to git-ls-tree for a _whole_ tree). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.css | 17 +++++++ gitweb/gitweb.perl | 121 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 136 insertions(+), 2 deletions(-) diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 2b023bd98a..e795b70b2b 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -181,6 +181,23 @@ table.diff_tree { font-family: monospace; } +table.combined.diff_tree td { + padding-right: 24px; +} + +table.combined.diff_tree td.link { + padding: 0px 2px; +} + +table.combined.diff_tree td.nochange a { + color: #6666ff; +} + +table.combined.diff_tree td.nochange a:hover, +table.combined.diff_tree td.nochange a:visited { + color: #d06666; +} + table.blame { border-collapse: collapse; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index dfba399d8e..c6a2fef823 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1015,6 +1015,30 @@ sub git_get_hash_by_path { return $3; } +# get path of entry with given hash at given tree-ish (ref) +# used to get 'from' filename for combined diff (merge commit) for renames +sub git_get_path_by_hash { + my $base = shift || return; + my $hash = shift || return; + + local $/ = "\0"; + + open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z', $base + or return undef; + while (my $line = <$fd>) { + chomp $line; + + #'040000 tree 595596a6a9117ddba9fe379b6b012b558bac8423 gitweb' + #'100644 blob e02e90f0429be0d2a69b76571101f20b8f75530f gitweb/README' + if ($line =~ m/(?:[0-9]+) (?:.+) $hash\t(.+)$/) { + close $fd; + return $1; + } + } + close $fd; + return undef; +} + ## ...................................................................... ## git utility functions, directly accessing git repository @@ -2210,7 +2234,8 @@ sub git_print_tree_entry { ## functions printing large fragments of HTML sub git_difftree_body { - my ($difftree, $hash, $parent) = @_; + my ($difftree, $hash, @parents) = @_; + my ($parent) = $parents[0]; my ($have_blame) = gitweb_check_feature('blame'); print "
\n"; if ($#{$difftree} > 10) { @@ -2218,7 +2243,9 @@ sub git_difftree_body { } print "
\n"; - print "\n"; + print "
1 ? "combined " : "") . + "diff_tree\">\n"; my $alternate = 1; my $patchno = 0; foreach my $line (@{$difftree}) { @@ -2231,6 +2258,96 @@ sub git_difftree_body { } $alternate ^= 1; + if (exists $diff{'nparents'}) { # combined diff + + if ($diff{'to_id'} ne ('0' x 40)) { + # file exists in the result (child) commit + print "\n"; + } else { + print "\n"; + } + + if ($action eq 'commitdiff') { + # link to patch + $patchno++; + print "\n"; + } + + my $has_history = 0; + my $not_deleted = 0; + for (my $i = 0; $i < $diff{'nparents'}; $i++) { + my $hash_parent = $parents[$i]; + my $from_hash = $diff{'from_id'}[$i]; + my $from_path = undef; + my $status = $diff{'status'}[$i]; + + $has_history ||= ($status ne 'A'); + $not_deleted ||= ($status ne 'D'); + + if ($status eq 'R' || $status eq 'C') { + $from_path = git_get_path_by_hash($hash_parent, $from_hash); + } + + if ($status eq 'A') { + print "\n"; + } elsif ($status eq 'D') { + print "\n"; + } else { + if ($diff{'to_id'} eq $from_hash) { + print "\n"; + } + } + + print "\n"; + + print "\n"; + next; # instead of 'else' clause, to avoid extra indent + } + # else ordinary diff + my ($to_mode_oct, $to_mode_str, $to_file_type); my ($from_mode_oct, $from_mode_str, $from_file_type); if ($diff{'to_mode'} ne ('0' x 6)) { From e72c0eaf9bbdb2a9467f3f026b39b1c1755c1105 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 7 May 2007 01:10:05 +0200 Subject: [PATCH 31/77] gitweb: Add combined diff support to git_patchset_body Calling convention for combined diff similar to the one for git_difftree_body subroutine: difftree info (first parameter) must be result of calling git-diff-tree with -c/--cc option, and all parents of a commit must be passed as last parameters. See also description in "gitweb: Add combined diff support to git_difftree_body" This ability is not used yet. Generating "src" file name for renames in combined diff was separated into fill_from_file_info subroutine; git_difftree_body was modified to use it. Currently git_difftree_body and git_patchset_body fills this info separately. The from-file line in two-line from-file/to-file header is not hyperlinked: there can be more than one "from"/"src" file. This differs from HTML output of ordinary (not combined) diff. format_diff_line subroutine needs extra $from/$to parameters to format combined diff patch line correctly. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 221 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 181 insertions(+), 40 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c6a2fef823..53ae0b83f1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -897,19 +897,34 @@ sub format_subject_html { sub format_diff_line { my $line = shift; my ($from, $to) = @_; - my $char = substr($line, 0, 1); my $diff_class = ""; chomp $line; - if ($char eq '+') { - $diff_class = " add"; - } elsif ($char eq "-") { - $diff_class = " rem"; - } elsif ($char eq "@") { - $diff_class = " chunk_header"; - } elsif ($char eq "\\") { - $diff_class = " incomplete"; + if ($from && $to && ref($from->{'href'}) eq "ARRAY") { + # combined diff + my $prefix = substr($line, 0, scalar @{$from->{'href'}}); + if ($line =~ m/^\@{3}/) { + $diff_class = " chunk_header"; + } elsif ($line =~ m/^\\/) { + $diff_class = " incomplete"; + } elsif ($prefix =~ tr/+/+/) { + $diff_class = " add"; + } elsif ($prefix =~ tr/-/-/) { + $diff_class = " rem"; + } + } else { + # assume ordinary diff + my $char = substr($line, 0, 1); + if ($char eq '+') { + $diff_class = " add"; + } elsif ($char eq '-') { + $diff_class = " rem"; + } elsif ($char eq '@') { + $diff_class = " chunk_header"; + } elsif ($char eq "\\") { + $diff_class = " incomplete"; + } } $line = untabify($line); if ($from && $to && $line =~ m/^\@{2} /) { @@ -930,6 +945,39 @@ sub format_diff_line { $line = "@@ $from_text $to_text @@" . "" . esc_html($section, -nbsp=>1) . ""; return "
$line
\n"; + } elsif ($from && $to && $line =~ m/^\@{3}/) { + my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/; + my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines); + + @from_text = split(' ', $ranges); + for (my $i = 0; $i < @from_text; ++$i) { + ($from_start[$i], $from_nlines[$i]) = + (split(',', substr($from_text[$i], 1)), 0); + } + + $to_text = pop @from_text; + $to_start = pop @from_start; + $to_nlines = pop @from_nlines; + + $line = "$prefix "; + for (my $i = 0; $i < @from_text; ++$i) { + if ($from->{'href'}[$i]) { + $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]", + -class=>"list"}, $from_text[$i]); + } else { + $line .= $from_text[$i]; + } + $line .= " "; + } + if ($to->{'href'}) { + $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start", + -class=>"list"}, $to_text); + } else { + $line .= $to_text; + } + $line .= " $prefix" . + "" . esc_html($section, -nbsp=>1) . ""; + return "
$line
\n"; } return "
" . esc_html($line, -nbsp=>1) . "
\n"; } @@ -2233,6 +2281,39 @@ sub git_print_tree_entry { ## ...................................................................... ## functions printing large fragments of HTML +sub fill_from_file_info { + my ($diff, @parents) = @_; + + $diff->{'from_file'} = [ ]; + $diff->{'from_file'}[$diff->{'nparents'} - 1] = undef; + for (my $i = 0; $i < $diff->{'nparents'}; $i++) { + if ($diff->{'status'}[$i] eq 'R' || + $diff->{'status'}[$i] eq 'C') { + $diff->{'from_file'}[$i] = + git_get_path_by_hash($parents[$i], $diff->{'from_id'}[$i]); + } + } + + return $diff; +} + +# parameters can be strings, or references to arrays of strings +sub from_ids_eq { + my ($a, $b) = @_; + + if (ref($a) eq "ARRAY" && ref($b) eq "ARRAY" && @$a == @$b) { + for (my $i = 0; $i < @$a; ++$i) { + return 0 unless ($a->[$i] eq $b->[$i]); + } + return 1; + } elsif (!ref($a) && !ref($b)) { + return $a eq $b; + } else { + return 0; + } +} + + sub git_difftree_body { my ($difftree, $hash, @parents) = @_; my ($parent) = $parents[0]; @@ -2260,6 +2341,8 @@ sub git_difftree_body { if (exists $diff{'nparents'}) { # combined diff + fill_from_file_info(\%diff, @parents); + if ($diff{'to_id'} ne ('0' x 40)) { # file exists in the result (child) commit print "
\n"; } elsif ($status eq 'D') { @@ -2517,7 +2596,8 @@ sub git_difftree_body { } sub git_patchset_body { - my ($fd, $difftree, $hash, $hash_parent) = @_; + my ($fd, $difftree, $hash, @hash_parents) = @_; + my ($hash_parent) = $hash_parents[0]; my $patch_idx = 0; my $patch_number = 0; @@ -2555,6 +2635,9 @@ sub git_patchset_body { if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) { $from_id = $1; $to_id = $2; + } elsif ($patch_line =~ m/^index ((?:[0-9a-fA-F]{40},)+[0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) { + $from_id = [ split(',', $1) ]; + $to_id = $2; } push @diff_header, $patch_line; @@ -2564,8 +2647,8 @@ sub git_patchset_body { # check if current patch belong to current raw line # and parse raw git-diff line if needed if (defined $diffinfo && - $diffinfo->{'from_id'} eq $from_id && - $diffinfo->{'to_id'} eq $to_id) { + from_ids_eq($diffinfo->{'from_id'}, $from_id) && + $diffinfo->{'to_id'} eq $to_id) { # this is split patch print "
\n"; } else { @@ -2579,15 +2662,34 @@ sub git_patchset_body { } else { $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]); } - $from{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'}; - $to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'}; - if ($diffinfo->{'status'} ne "A") { # not new (added) file - $from{'href'} = href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo->{'from_id'}, - file_name=>$from{'file'}); + if ($diffinfo->{'nparents'}) { + # combined diff + $from{'file'} = []; + $from{'href'} = []; + fill_from_file_info($diffinfo, @hash_parents) + unless exists $diffinfo->{'from_file'}; + for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) { + $from{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'}; + if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file + $from{'href'}[$i] = href(action=>"blob", + hash_base=>$hash_parents[$i], + hash=>$diffinfo->{'from_id'}[$i], + file_name=>$from{'file'}[$i]); + } else { + $from{'href'}[$i] = undef; + } + } } else { - delete $from{'href'}; + $from{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'}; + if ($diffinfo->{'status'} ne "A") { # not new (added) file + $from{'href'} = href(action=>"blob", hash_base=>$hash_parent, + hash=>$diffinfo->{'from_id'}, + file_name=>$from{'file'}); + } else { + delete $from{'href'}; + } } + $to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'}; if ($diffinfo->{'status'} ne "D") { # not deleted file $to{'href'} = href(action=>"blob", hash_base=>$hash, hash=>$diffinfo->{'to_id'}, @@ -2602,19 +2704,34 @@ sub git_patchset_body { # print "git diff" header $patch_line = shift @diff_header; - $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!; - if ($from{'href'}) { - $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"}, - 'a/' . esc_path($from{'file'})); - } else { # file was added - $patch_line .= 'a/' . esc_path($from{'file'}); - } - $patch_line .= ' '; - if ($to{'href'}) { - $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"}, - 'b/' . esc_path($to{'file'})); - } else { # file was deleted - $patch_line .= 'b/' . esc_path($to{'file'}); + if ($diffinfo->{'nparents'}) { + + # combined diff + $patch_line =~ s!^(diff (.*?) )"?.*$!$1!; + if ($to{'href'}) { + $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"}, + esc_path($to{'file'})); + } else { # file was deleted + $patch_line .= esc_path($to{'file'}); + } + + } else { + + $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!; + if ($from{'href'}) { + $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"}, + 'a/' . esc_path($from{'file'})); + } else { # file was added + $patch_line .= 'a/' . esc_path($from{'file'}); + } + $patch_line .= ' '; + if ($to{'href'}) { + $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"}, + 'b/' . esc_path($to{'file'})); + } else { # file was deleted + $patch_line .= 'b/' . esc_path($to{'file'}); + } + } print "
$patch_line
\n"; @@ -2631,14 +2748,37 @@ sub git_patchset_body { $patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"path"}, esc_path($to{'file'})); } - # match + # match single if ($patch_line =~ m/\s(\d{6})$/) { $patch_line .= ' (' . file_type_long($1) . ')'; } # match - if ($patch_line =~ m/^index/) { + if ($patch_line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) { + # can match only for combined diff + $patch_line = 'index '; + for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) { + if ($from{'href'}[$i]) { + $patch_line .= $cgi->a({-href=>$from{'href'}[$i], + -class=>"hash"}, + substr($diffinfo->{'from_id'}[$i],0,7)); + } else { + $patch_line .= '0' x 7; + } + # separator + $patch_line .= ',' if ($i < $diffinfo->{'nparents'} - 1); + } + $patch_line .= '..'; + if ($to{'href'}) { + $patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"hash"}, + substr($diffinfo->{'to_id'},0,7)); + } else { + $patch_line .= '0' x 7; + } + + } elsif ($patch_line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) { + # can match only for ordinary diff my ($from_link, $to_link); if ($from{'href'}) { $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"}, @@ -2674,7 +2814,8 @@ sub git_patchset_body { } next PATCH if ($patch_line =~ m/^diff /); #assert($patch_line =~ m/^---/) if DEBUG; - if ($from{'href'} && $patch_line =~ m!^--- "?a/!) { + if (!$diffinfo->{'nparents'} && # not from-file line for combined diff + $from{'href'} && $patch_line =~ m!^--- "?a/!) { $patch_line = '--- a/' . $cgi->a({-href=>$from{'href'}, -class=>"path"}, esc_path($from{'file'})); From 493e01db51ae003255a27dbcc81953fbfdab958a Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 7 May 2007 01:10:06 +0200 Subject: [PATCH 32/77] gitweb: Make it possible to use pre-parsed info in git_difftree_body Make it possible to use pre-parsed, or generated by hand, difftree info in git_difftree_body, similarly to how was and is it done in git_patchset_body. Use just introduced feature in git_commitdiff to parse difftree info (raw diff output) only once: difftree info is now parsed in git_commitdiff directly, and parsed information is passed to both git_difftree_body and git_patchset_body. (Till now only git_blobdiff made use of git_patchset_body ability to use pre-parsed or hand generated info.) Additionally this makes rename info for combined diff with renames (or copies) calculated only once in git_difftree_body; the $difftree is modified and git_patchset_body makes use of added info. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 139 ++++++++++++++++++++++++--------------------- 1 file changed, 73 insertions(+), 66 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 53ae0b83f1..b3e2e07a2b 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2330,7 +2330,13 @@ sub git_difftree_body { my $alternate = 1; my $patchno = 0; foreach my $line (@{$difftree}) { - my %diff = parse_difftree_raw_line($line); + my $diff; + if (ref($line) eq "HASH") { + # pre-parsed (or generated by hand) + $diff = $line; + } else { + $diff = parse_difftree_raw_line($line); + } if ($alternate) { print "
\n"; @@ -2339,21 +2345,22 @@ sub git_difftree_body { } $alternate ^= 1; - if (exists $diff{'nparents'}) { # combined diff + if (exists $diff->{'nparents'}) { # combined diff - fill_from_file_info(\%diff, @parents); + fill_from_file_info($diff, @parents) + unless exists $diff->{'from_file'}; - if ($diff{'to_id'} ne ('0' x 40)) { + if ($diff->{'to_id'} ne ('0' x 40)) { # file exists in the result (child) commit print "\n"; } else { print "\n"; } @@ -2368,11 +2375,11 @@ sub git_difftree_body { my $has_history = 0; my $not_deleted = 0; - for (my $i = 0; $i < $diff{'nparents'}; $i++) { + for (my $i = 0; $i < $diff->{'nparents'}; $i++) { my $hash_parent = $parents[$i]; - my $from_hash = $diff{'from_id'}[$i]; - my $from_path = $diff{'from_file'}[$i]; - my $status = $diff{'status'}[$i]; + my $from_hash = $diff->{'from_id'}[$i]; + my $from_path = $diff->{'from_file'}[$i]; + my $status = $diff->{'status'}[$i]; $has_history ||= ($status ne 'A'); $not_deleted ||= ($status ne 'D'); @@ -2388,17 +2395,17 @@ sub git_difftree_body { "blob" . ($i+1)) . " | \n"; } else { - if ($diff{'to_id'} eq $from_hash) { + if ($diff->{'to_id'} eq $from_hash) { print "\n"; @@ -2408,15 +2415,15 @@ sub git_difftree_body { print "\n"; print "\n"; print "\n"; - } elsif ($diff{'status'} eq "D") { # deleted + } elsif ($diff->{'status'} eq "D") { # deleted my $mode_chng = "[deleted $from_file_type]"; print "\n"; print "\n"; print "\n"; - } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed + } elsif ($diff->{'status'} eq "M" || $diff->{'status'} eq "T") { # modified, or type changed my $mode_chnge = ""; - if ($diff{'from_mode'} != $diff{'to_mode'}) { + if ($diff->{'from_mode'} != $diff->{'to_mode'}) { $mode_chnge = "[changed"; if ($from_file_type ne $to_file_type) { $mode_chnge .= " from $from_file_type to $to_file_type"; @@ -2511,9 +2518,9 @@ sub git_difftree_body { $mode_chnge .= "]\n"; } print "\n"; print "\n"; print "\n"; - } elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied + } elsif ($diff->{'status'} eq "R" || $diff->{'status'} eq "C") { # renamed or copied my %status_name = ('R' => 'moved', 'C' => 'copied'); - my $nstatus = $status_name{$diff{'status'}}; + my $nstatus = $status_name{$diff->{'status'}}; my $mode_chng = ""; - if ($diff{'from_mode'} != $diff{'to_mode'}) { + if ($diff->{'from_mode'} != $diff->{'to_mode'}) { # mode also for directories, so we cannot use $to_mode_str $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777); } print "\n" . + hash=>$diff->{'to_id'}, file_name=>$diff->{'to_file'}), + -class => "list"}, esc_path($diff->{'to_file'})) . "\n" . "\n" . + hash=>$diff->{'from_id'}, file_name=>$diff->{'from_file'}), + -class => "list"}, esc_path($diff->{'from_file'})) . + " with " . (int $diff->{'similarity'}) . "% similarity$mode_chng]\n" . "\n"; @@ -4401,7 +4408,7 @@ sub git_commitdiff { chomp $line; # empty line ends raw part of diff-tree output last unless $line; - push @difftree, $line; + push @difftree, scalar parse_difftree_raw_line($line); } } elsif ($format eq 'plain') { From fb1dde4a908ddcfaa884f67734a60dc6bd639b59 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 7 May 2007 01:10:07 +0200 Subject: [PATCH 33/77] gitweb: Show combined diff for merge commits in 'commitdiff' view When 'commitdiff' action is requested without 'hp' (hash parent) parameter, and commit given by 'h' (hash) parameter is merge commit, show merge as combined diff. Earlier for merge commits without 'hp' parameter diff to first parent was shown. Note that in compact combined (--cc) format 'uninteresting' hunks omission mechanism can make that there is no patch corresponding to line in raw format (difftree) output. That is why (at least for now) we use --combined and not --cc format for showing commitdiff for merge commits. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b3e2e07a2b..c0e2473b40 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4391,8 +4391,10 @@ sub git_commitdiff { } } + my $hash_parent_param = $hash_parent; if (!defined $hash_parent) { - $hash_parent = $co{'parent'} || '--root'; + $hash_parent_param = + @{$co{'parents'}} > 1 ? '-c' : $co{'parent'} || '--root'; } # read commitdiff @@ -4401,7 +4403,7 @@ sub git_commitdiff { if ($format eq 'html') { open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, "--no-commit-id", "--patch-with-raw", "--full-index", - $hash_parent, $hash, "--" + $hash_parent_param, $hash, "--" or die_error(undef, "Open git-diff-tree failed"); while (my $line = <$fd>) { @@ -4413,7 +4415,7 @@ sub git_commitdiff { } elsif ($format eq 'plain') { open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, - '-p', $hash_parent, $hash, "--" + '-p', $hash_parent_param, $hash, "--" or die_error(undef, "Open git-diff-tree failed"); } else { @@ -4469,10 +4471,10 @@ TEXT # write patch if ($format eq 'html') { - git_difftree_body(\@difftree, $hash, $hash_parent); + git_difftree_body(\@difftree, $hash, $hash_parent || @{$co{'parents'}}); print "
\n"; - git_patchset_body($fd, \@difftree, $hash, $hash_parent); + git_patchset_body($fd, \@difftree, $hash, $hash_parent || @{$co{'parents'}}); close $fd; print "\n"; # class="page_body" git_footer_html(); From 208ecb2e860364aeffb22eb1f644db077b1c5162 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 7 May 2007 01:10:08 +0200 Subject: [PATCH 34/77] gitweb: Show combined diff for merge commits in 'commit' view When commit shown is a merge commit (has more than one parent), display combined difftree output (result of git-diff-tree -c). Earlier (since commit 549ab4a30703012ff3a12b5455d319216805a8db) difftree output (against first parent) was not printed for merges. Examples of non-trivial merges: 5bac4a671907604b5fb4e24ff682d5b0e8431931 (includes rename) addafaf92eeb86033da91323d0d3ad7a496dae83 (five parents) 95f97567c1887d77f3a46b42d8622c76414d964d (evil merge) Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c0e2473b40..90243fdf98 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4026,14 +4026,13 @@ sub git_commit { $parent = "--root"; } my @difftree; - if (@$parents <= 1) { - # difftree output is not printed for merges - open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", - @diff_opts, $parent, $hash, "--" - or die_error(undef, "Open git-diff-tree failed"); - @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading git-diff-tree failed"); - } + open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", + @diff_opts, + (@$parents <= 1 ? $parent : '-c'), + $hash, "--" + or die_error(undef, "Open git-diff-tree failed"); + @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading git-diff-tree failed"); # non-textual hash id's can be cached my $expires; @@ -4111,10 +4110,7 @@ sub git_commit { git_print_log($co{'comment'}); print "\n"; - if (@$parents <= 1) { - # do not output difftree/whatchanged for merges - git_difftree_body(\@difftree, $hash, $parent); - } + git_difftree_body(\@difftree, $hash, @$parents); git_footer_html(); } From f522c9b5ed367172f969397589ae3d686b867ac0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 7 May 2007 23:35:48 -0400 Subject: [PATCH 35/77] git-gui: Refactor into multiple files to save my sanity I'm finding it difficult to work with a 6,000+ line Tcl script and not go insane while looking for a particular block of code. Since most of the program is organized into different units of functionality and not all users will need all units immediately on startup we can improve things by splitting procs out into multiple files and let auto_load handle things for us. This should help not only to better organize the source, but it may also improve startup times for some users as the Tcl parser does not need to read as much script before it can show the UI. In many cases the user can avoid reading at least half of git-gui now. Unfortunately we now need a library directory in our runtime location. This is currently assumed to be $(sharedir)/git-gui/lib and its expected that the Makefile invoker will setup some sort of reasonable sharedir value for us, or let us assume its going to be $(gitexecdir)/../share. We now also require a tclsh (in TCL_PATH) to just run the Makefile, as we use tclsh to generate the tclIndex for our lib directory. I'm hoping this is not an unncessary burden on end-users who are building from source. I haven't really made any functionality changes here, this is just a huge migration of code from one file to many smaller files. All of the new changes are to setup the library path and install the library files. Signed-off-by: Shawn O. Pearce --- .gitignore | 1 + Makefile | 23 +- git-gui.sh | 3816 +-------------------------------------------- lib/blame.tcl | 390 +++++ lib/branch.tcl | 572 +++++++ lib/browser.tcl | 263 ++++ lib/commit.tcl | 410 +++++ lib/console.tcl | 185 +++ lib/database.tcl | 89 ++ lib/diff.tcl | 336 ++++ lib/error.tcl | 101 ++ lib/index.tcl | 409 +++++ lib/merge.tcl | 281 ++++ lib/option.tcl | 290 ++++ lib/remote.tcl | 159 ++ lib/shortcut.tcl | 141 ++ lib/transport.tcl | 162 ++ 17 files changed, 3836 insertions(+), 3792 deletions(-) create mode 100644 lib/blame.tcl create mode 100644 lib/branch.tcl create mode 100644 lib/browser.tcl create mode 100644 lib/commit.tcl create mode 100644 lib/console.tcl create mode 100644 lib/database.tcl create mode 100644 lib/diff.tcl create mode 100644 lib/error.tcl create mode 100644 lib/index.tcl create mode 100644 lib/merge.tcl create mode 100644 lib/option.tcl create mode 100644 lib/remote.tcl create mode 100644 lib/shortcut.tcl create mode 100644 lib/transport.tcl diff --git a/.gitignore b/.gitignore index 57cced8c18..020b86deae 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ GIT-VERSION-FILE GIT-GUI-VARS git-citool git-gui +lib/tclIndex diff --git a/Makefile b/Makefile index 3b6282e734..ba1e33ba84 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE SCRIPT_SH = git-gui.sh GITGUI_BUILT_INS = git-citool ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH)) +ALL_LIBFILES = $(wildcard lib/*.tcl) ifndef SHELL_PATH SHELL_PATH = /bin/sh @@ -19,6 +20,10 @@ ifndef gitexecdir gitexecdir := $(shell git --exec-path) endif +ifndef sharedir + sharedir := $(dir $(gitexecdir))/share +endif + ifndef INSTALL INSTALL = install endif @@ -26,8 +31,10 @@ endif ifndef V QUIET_GEN = @echo ' ' GEN $@; QUIET_BUILT_IN = @echo ' ' BUILTIN $@; + QUIET_INDEX = @echo ' ' INDEX $(dir $@); endif +TCL_PATH ?= tclsh TCLTK_PATH ?= wish ifeq ($(findstring $(MAKEFLAGS),s),s) @@ -40,11 +47,15 @@ gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) +libdir ?= $(sharedir)/git-gui/lib +libdir_SQ = $(subst ','\'',$(libdir)) + $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(QUIET_GEN)rm -f $@ $@+ && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|^exec wish "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \ -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \ + -e 's|@@GITGUI_LIBDIR@@|$(libdir_SQ)|' \ $@.sh >$@+ && \ chmod +x $@+ && \ mv $@+ $@ @@ -52,12 +63,16 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(GITGUI_BUILT_INS): git-gui $(QUIET_BUILT_IN)rm -f $@ && ln git-gui $@ +lib/tclIndex: $(ALL_LIBFILES) + $(QUIET_INDEX)echo auto_mkindex lib '*.tcl' | $(TCL_PATH) + # These can record GITGUI_VERSION $(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE GIT-GUI-VARS TRACK_VARS = \ $(subst ','\'',SHELL_PATH='$(SHELL_PATH_SQ)') \ $(subst ','\'',TCLTK_PATH='$(TCLTK_PATH_SQ)') \ + $(subst ','\'',libdir='$(libdir_SQ)') \ #end TRACK_VARS GIT-GUI-VARS: .FORCE-GIT-GUI-VARS @@ -67,19 +82,23 @@ GIT-GUI-VARS: .FORCE-GIT-GUI-VARS echo 1>$@ "$$VARS"; \ fi -all:: $(ALL_PROGRAMS) +all:: $(ALL_PROGRAMS) lib/tclIndex install: all $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;) + $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(libdir_SQ)' + $(INSTALL) -m644 lib/tclIndex '$(DESTDIR_SQ)$(libdir_SQ)' + $(foreach p,$(ALL_LIBFILES), $(INSTALL) -m644 $p '$(DESTDIR_SQ)$(libdir_SQ)' ;) dist-version: @mkdir -p $(TARDIR) @echo $(GITGUI_VERSION) > $(TARDIR)/version clean:: - rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE GIT-GUI-VARS + rm -f $(ALL_PROGRAMS) lib/tclIndex + rm -f GIT-VERSION-FILE GIT-GUI-VARS .PHONY: all install dist-version clean .PHONY: .FORCE-GIT-VERSION-FILE diff --git a/git-gui.sh b/git-gui.sh index 4c01b91f04..72673c627a 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -20,6 +20,30 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA} +###################################################################### +## +## configure our library + +set oguilib {@@GITGUI_LIBDIR@@} +if {[string match @@* $oguilib]} { + set oguilib [file join [file dirname [file normalize $argv0]] lib] +} +set auto_path [concat [list $oguilib] $auto_path] + +if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { + unset _verbose + rename auto_load real__auto_load + proc auto_load {name args} { + puts stderr "auto_load $name" + return [uplevel 1 real__auto_load $name $args] + } + rename source real__source + proc source {name} { + puts stderr "source $name" + uplevel 1 real__source $name + } +} + ###################################################################### ## ## read only globals @@ -179,56 +203,6 @@ proc load_config {include_global} { } } -proc save_config {} { - global default_config font_descs - global repo_config global_config - global repo_config_new global_config_new - - foreach option $font_descs { - set name [lindex $option 0] - set font [lindex $option 1] - font configure $font \ - -family $global_config_new(gui.$font^^family) \ - -size $global_config_new(gui.$font^^size) - font configure ${font}bold \ - -family $global_config_new(gui.$font^^family) \ - -size $global_config_new(gui.$font^^size) - set global_config_new(gui.$name) [font configure $font] - unset global_config_new(gui.$font^^family) - unset global_config_new(gui.$font^^size) - } - - foreach name [array names default_config] { - set value $global_config_new($name) - if {$value ne $global_config($name)} { - if {$value eq $default_config($name)} { - catch {git config --global --unset $name} - } else { - regsub -all "\[{}\]" $value {"} value - git config --global $name $value - } - set global_config($name) $value - if {$value eq $repo_config($name)} { - catch {git config --unset $name} - set repo_config($name) $value - } - } - } - - foreach name [array names default_config] { - set value $repo_config_new($name) - if {$value ne $repo_config($name)} { - if {$value eq $global_config($name)} { - catch {git config --unset $name} - } else { - regsub -all "\[{}\]" $value {"} value - git config $name $value - } - set repo_config($name) $value - } - } -} - ###################################################################### ## ## handy utils @@ -237,64 +211,6 @@ proc git {args} { return [eval exec git $args] } -proc error_popup {msg} { - set title [appname] - if {[reponame] ne {}} { - append title " ([reponame])" - } - set cmd [list tk_messageBox \ - -icon error \ - -type ok \ - -title "$title: error" \ - -message $msg] - if {[winfo ismapped .]} { - lappend cmd -parent . - } - eval $cmd -} - -proc warn_popup {msg} { - set title [appname] - if {[reponame] ne {}} { - append title " ([reponame])" - } - set cmd [list tk_messageBox \ - -icon warning \ - -type ok \ - -title "$title: warning" \ - -message $msg] - if {[winfo ismapped .]} { - lappend cmd -parent . - } - eval $cmd -} - -proc info_popup {msg {parent .}} { - set title [appname] - if {[reponame] ne {}} { - append title " ([reponame])" - } - tk_messageBox \ - -parent $parent \ - -icon info \ - -type ok \ - -title $title \ - -message $msg -} - -proc ask_popup {msg} { - set title [appname] - if {[reponame] ne {}} { - append title " ([reponame])" - } - return [tk_messageBox \ - -parent . \ - -icon question \ - -type yesno \ - -title $title \ - -message $msg] -} - auto_load tk_optionMenu rename tk_optionMenu real__tkOptionMenu proc tk_optionMenu {w varName args} { @@ -664,7 +580,7 @@ proc read_ls_others {fd after} { } proc rescan_done {fd buf after} { - global rescan_active + global rescan_active current_diff_path global file_states repo_config upvar $buf to_clear @@ -676,7 +592,7 @@ proc rescan_done {fd buf after} { prune_selection unlock_index display_all_files - reshow_diff + if {$current_diff_path ne {}} reshow_diff uplevel #0 $after } @@ -690,759 +606,6 @@ proc prune_selection {} { } } -###################################################################### -## -## diff - -proc clear_diff {} { - global ui_diff current_diff_path current_diff_header - global ui_index ui_workdir - - $ui_diff conf -state normal - $ui_diff delete 0.0 end - $ui_diff conf -state disabled - - set current_diff_path {} - set current_diff_header {} - - $ui_index tag remove in_diff 0.0 end - $ui_workdir tag remove in_diff 0.0 end -} - -proc reshow_diff {} { - global ui_status_value file_states file_lists - global current_diff_path current_diff_side - - set p $current_diff_path - if {$p eq {}} { - # No diff is being shown. - } elseif {$current_diff_side eq {} - || [catch {set s $file_states($p)}] - || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} { - clear_diff - } else { - show_diff $p $current_diff_side - } -} - -proc handle_empty_diff {} { - global current_diff_path file_states file_lists - - set path $current_diff_path - set s $file_states($path) - if {[lindex $s 0] ne {_M}} return - - info_popup "No differences detected. - -[short_path $path] has no changes. - -The modification date of this file was updated by another application, but the content within the file was not changed. - -A rescan will be automatically started to find other files which may have the same state." - - clear_diff - display_file $path __ - rescan {set ui_status_value {Ready.}} 0 -} - -proc show_diff {path w {lno {}}} { - global file_states file_lists - global is_3way_diff diff_active repo_config - global ui_diff ui_status_value ui_index ui_workdir - global current_diff_path current_diff_side current_diff_header - - if {$diff_active || ![lock_index read]} return - - clear_diff - if {$lno == {}} { - set lno [lsearch -sorted -exact $file_lists($w) $path] - if {$lno >= 0} { - incr lno - } - } - if {$lno >= 1} { - $w tag add in_diff $lno.0 [expr {$lno + 1}].0 - } - - set s $file_states($path) - set m [lindex $s 0] - set is_3way_diff 0 - set diff_active 1 - set current_diff_path $path - set current_diff_side $w - set current_diff_header {} - set ui_status_value "Loading diff of [escape_path $path]..." - - # - Git won't give us the diff, there's nothing to compare to! - # - if {$m eq {_O}} { - set max_sz [expr {128 * 1024}] - if {[catch { - set fd [open $path r] - set content [read $fd $max_sz] - close $fd - set sz [file size $path] - } err ]} { - set diff_active 0 - unlock_index - set ui_status_value "Unable to display [escape_path $path]" - error_popup "Error loading file:\n\n$err" - return - } - $ui_diff conf -state normal - if {![catch {set type [exec file $path]}]} { - set n [string length $path] - if {[string equal -length $n $path $type]} { - set type [string range $type $n end] - regsub {^:?\s*} $type {} type - } - $ui_diff insert end "* $type\n" d_@ - } - if {[string first "\0" $content] != -1} { - $ui_diff insert end \ - "* Binary file (not showing content)." \ - d_@ - } else { - if {$sz > $max_sz} { - $ui_diff insert end \ -"* Untracked file is $sz bytes. -* Showing only first $max_sz bytes. -" d_@ - } - $ui_diff insert end $content - if {$sz > $max_sz} { - $ui_diff insert end " -* Untracked file clipped here by [appname]. -* To see the entire file, use an external editor. -" d_@ - } - } - $ui_diff conf -state disabled - set diff_active 0 - unlock_index - set ui_status_value {Ready.} - return - } - - set cmd [list | git] - if {$w eq $ui_index} { - lappend cmd diff-index - lappend cmd --cached - } elseif {$w eq $ui_workdir} { - if {[string index $m 0] eq {U}} { - lappend cmd diff - } else { - lappend cmd diff-files - } - } - - lappend cmd -p - lappend cmd --no-color - if {$repo_config(gui.diffcontext) > 0} { - lappend cmd "-U$repo_config(gui.diffcontext)" - } - if {$w eq $ui_index} { - lappend cmd [PARENT] - } - lappend cmd -- - lappend cmd $path - - if {[catch {set fd [open $cmd r]} err]} { - set diff_active 0 - unlock_index - set ui_status_value "Unable to display [escape_path $path]" - error_popup "Error loading diff:\n\n$err" - return - } - - fconfigure $fd \ - -blocking 0 \ - -encoding binary \ - -translation binary - fileevent $fd readable [list read_diff $fd] -} - -proc read_diff {fd} { - global ui_diff ui_status_value diff_active - global is_3way_diff current_diff_header - - $ui_diff conf -state normal - while {[gets $fd line] >= 0} { - # -- Cleanup uninteresting diff header lines. - # - if { [string match {diff --git *} $line] - || [string match {diff --cc *} $line] - || [string match {diff --combined *} $line] - || [string match {--- *} $line] - || [string match {+++ *} $line]} { - append current_diff_header $line "\n" - continue - } - if {[string match {index *} $line]} continue - if {$line eq {deleted file mode 120000}} { - set line "deleted symlink" - } - - # -- Automatically detect if this is a 3 way diff. - # - if {[string match {@@@ *} $line]} {set is_3way_diff 1} - - if {[string match {mode *} $line] - || [string match {new file *} $line] - || [string match {deleted file *} $line] - || [string match {Binary files * and * differ} $line] - || $line eq {\ No newline at end of file} - || [regexp {^\* Unmerged path } $line]} { - set tags {} - } elseif {$is_3way_diff} { - set op [string range $line 0 1] - switch -- $op { - { } {set tags {}} - {@@} {set tags d_@} - { +} {set tags d_s+} - { -} {set tags d_s-} - {+ } {set tags d_+s} - {- } {set tags d_-s} - {--} {set tags d_--} - {++} { - if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} { - set line [string replace $line 0 1 { }] - set tags d$op - } else { - set tags d_++ - } - } - default { - puts "error: Unhandled 3 way diff marker: {$op}" - set tags {} - } - } - } else { - set op [string index $line 0] - switch -- $op { - { } {set tags {}} - {@} {set tags d_@} - {-} {set tags d_-} - {+} { - if {[regexp {^\+([<>]{7} |={7})} $line _g op]} { - set line [string replace $line 0 0 { }] - set tags d$op - } else { - set tags d_+ - } - } - default { - puts "error: Unhandled 2 way diff marker: {$op}" - set tags {} - } - } - } - $ui_diff insert end $line $tags - if {[string index $line end] eq "\r"} { - $ui_diff tag add d_cr {end - 2c} - } - $ui_diff insert end "\n" $tags - } - $ui_diff conf -state disabled - - if {[eof $fd]} { - close $fd - set diff_active 0 - unlock_index - set ui_status_value {Ready.} - - if {[$ui_diff index end] eq {2.0}} { - handle_empty_diff - } - } -} - -proc apply_hunk {x y} { - global current_diff_path current_diff_header current_diff_side - global ui_diff ui_index file_states - - if {$current_diff_path eq {} || $current_diff_header eq {}} return - if {![lock_index apply_hunk]} return - - set apply_cmd {git apply --cached --whitespace=nowarn} - set mi [lindex $file_states($current_diff_path) 0] - if {$current_diff_side eq $ui_index} { - set mode unstage - lappend apply_cmd --reverse - if {[string index $mi 0] ne {M}} { - unlock_index - return - } - } else { - set mode stage - if {[string index $mi 1] ne {M}} { - unlock_index - return - } - } - - set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0] - set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0] - if {$s_lno eq {}} { - unlock_index - return - } - - set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end] - if {$e_lno eq {}} { - set e_lno end - } - - if {[catch { - set p [open "| $apply_cmd" w] - fconfigure $p -translation binary -encoding binary - puts -nonewline $p $current_diff_header - puts -nonewline $p [$ui_diff get $s_lno $e_lno] - close $p} err]} { - error_popup "Failed to $mode selected hunk.\n\n$err" - unlock_index - return - } - - $ui_diff conf -state normal - $ui_diff delete $s_lno $e_lno - $ui_diff conf -state disabled - - if {[$ui_diff get 1.0 end] eq "\n"} { - set o _ - } else { - set o ? - } - - if {$current_diff_side eq $ui_index} { - set mi ${o}M - } elseif {[string index $mi 0] eq {_}} { - set mi M$o - } else { - set mi ?$o - } - unlock_index - display_file $current_diff_path $mi - if {$o eq {_}} { - clear_diff - } -} - -###################################################################### -## -## commit - -proc load_last_commit {} { - global HEAD PARENT MERGE_HEAD commit_type ui_comm - global repo_config - - if {[llength $PARENT] == 0} { - error_popup {There is nothing to amend. - -You are about to create the initial commit. There is no commit before this to amend. -} - return - } - - repository_state curType curHEAD curMERGE_HEAD - if {$curType eq {merge}} { - error_popup {Cannot amend while merging. - -You are currently in the middle of a merge that has not been fully completed. You cannot amend the prior commit unless you first abort the current merge activity. -} - return - } - - set msg {} - set parents [list] - if {[catch { - set fd [open "| git cat-file commit $curHEAD" r] - fconfigure $fd -encoding binary -translation lf - if {[catch {set enc $repo_config(i18n.commitencoding)}]} { - set enc utf-8 - } - while {[gets $fd line] > 0} { - if {[string match {parent *} $line]} { - lappend parents [string range $line 7 end] - } elseif {[string match {encoding *} $line]} { - set enc [string tolower [string range $line 9 end]] - } - } - set msg [encoding convertfrom $enc [read $fd]] - set msg [string trim $msg] - close $fd - } err]} { - error_popup "Error loading commit data for amend:\n\n$err" - return - } - - set HEAD $curHEAD - set PARENT $parents - set MERGE_HEAD [list] - switch -- [llength $parents] { - 0 {set commit_type amend-initial} - 1 {set commit_type amend} - default {set commit_type amend-merge} - } - - $ui_comm delete 0.0 end - $ui_comm insert end $msg - $ui_comm edit reset - $ui_comm edit modified false - rescan {set ui_status_value {Ready.}} -} - -proc create_new_commit {} { - global commit_type ui_comm - - set commit_type normal - $ui_comm delete 0.0 end - $ui_comm edit reset - $ui_comm edit modified false - rescan {set ui_status_value {Ready.}} -} - -set GIT_COMMITTER_IDENT {} - -proc committer_ident {} { - global GIT_COMMITTER_IDENT - - if {$GIT_COMMITTER_IDENT eq {}} { - if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} { - error_popup "Unable to obtain your identity:\n\n$err" - return {} - } - if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \ - $me me GIT_COMMITTER_IDENT]} { - error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me" - return {} - } - } - - return $GIT_COMMITTER_IDENT -} - -proc commit_tree {} { - global HEAD commit_type file_states ui_comm repo_config - global ui_status_value pch_error - - if {[committer_ident] eq {}} return - if {![lock_index update]} return - - # -- Our in memory state should match the repository. - # - repository_state curType curHEAD curMERGE_HEAD - if {[string match amend* $commit_type] - && $curType eq {normal} - && $curHEAD eq $HEAD} { - } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { - info_popup {Last scanned state does not match repository state. - -Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created. - -The rescan will be automatically started now. -} - unlock_index - rescan {set ui_status_value {Ready.}} - return - } - - # -- At least one file should differ in the index. - # - set files_ready 0 - foreach path [array names file_states] { - switch -glob -- [lindex $file_states($path) 0] { - _? {continue} - A? - - D? - - M? {set files_ready 1} - U? { - error_popup "Unmerged files cannot be committed. - -File [short_path $path] has merge conflicts. You must resolve them and add the file before committing. -" - unlock_index - return - } - default { - error_popup "Unknown file state [lindex $s 0] detected. - -File [short_path $path] cannot be committed by this program. -" - } - } - } - if {!$files_ready && ![string match *merge $curType]} { - info_popup {No changes to commit. - -You must add at least 1 file before you can commit. -} - unlock_index - return - } - - # -- A message is required. - # - set msg [string trim [$ui_comm get 1.0 end]] - regsub -all -line {[ \t\r]+$} $msg {} msg - if {$msg eq {}} { - error_popup {Please supply a commit message. - -A good commit message has the following format: - -- First line: Describe in one sentance what you did. -- Second line: Blank -- Remaining lines: Describe why this change is good. -} - unlock_index - return - } - - # -- Run the pre-commit hook. - # - set pchook [gitdir hooks pre-commit] - - # On Cygwin [file executable] might lie so we need to ask - # the shell if the hook is executable. Yes that's annoying. - # - if {[is_Cygwin] && [file isfile $pchook]} { - set pchook [list sh -c [concat \ - "if test -x \"$pchook\";" \ - "then exec \"$pchook\" 2>&1;" \ - "fi"]] - } elseif {[file executable $pchook]} { - set pchook [list $pchook |& cat] - } else { - commit_writetree $curHEAD $msg - return - } - - set ui_status_value {Calling pre-commit hook...} - set pch_error {} - set fd_ph [open "| $pchook" r] - fconfigure $fd_ph -blocking 0 -translation binary - fileevent $fd_ph readable \ - [list commit_prehook_wait $fd_ph $curHEAD $msg] -} - -proc commit_prehook_wait {fd_ph curHEAD msg} { - global pch_error ui_status_value - - append pch_error [read $fd_ph] - fconfigure $fd_ph -blocking 1 - if {[eof $fd_ph]} { - if {[catch {close $fd_ph}]} { - set ui_status_value {Commit declined by pre-commit hook.} - hook_failed_popup pre-commit $pch_error - unlock_index - } else { - commit_writetree $curHEAD $msg - } - set pch_error {} - return - } - fconfigure $fd_ph -blocking 0 -} - -proc commit_writetree {curHEAD msg} { - global ui_status_value - - set ui_status_value {Committing changes...} - set fd_wt [open "| git write-tree" r] - fileevent $fd_wt readable \ - [list commit_committree $fd_wt $curHEAD $msg] -} - -proc commit_committree {fd_wt curHEAD msg} { - global HEAD PARENT MERGE_HEAD commit_type - global all_heads current_branch - global ui_status_value ui_comm selected_commit_type - global file_states selected_paths rescan_active - global repo_config - - gets $fd_wt tree_id - if {$tree_id eq {} || [catch {close $fd_wt} err]} { - error_popup "write-tree failed:\n\n$err" - set ui_status_value {Commit failed.} - unlock_index - return - } - - # -- Verify this wasn't an empty change. - # - if {$commit_type eq {normal}} { - set old_tree [git rev-parse "$PARENT^{tree}"] - if {$tree_id eq $old_tree} { - info_popup {No changes to commit. - -No files were modified by this commit and it was not a merge commit. - -A rescan will be automatically started now. -} - unlock_index - rescan {set ui_status_value {No changes to commit.}} - return - } - } - - # -- Build the message. - # - set msg_p [gitdir COMMIT_EDITMSG] - set msg_wt [open $msg_p w] - if {[catch {set enc $repo_config(i18n.commitencoding)}]} { - set enc utf-8 - } - fconfigure $msg_wt -encoding binary -translation binary - puts -nonewline $msg_wt [encoding convertto $enc $msg] - close $msg_wt - - # -- Create the commit. - # - set cmd [list commit-tree $tree_id] - foreach p [concat $PARENT $MERGE_HEAD] { - lappend cmd -p $p - } - lappend cmd <$msg_p - if {[catch {set cmt_id [eval git $cmd]} err]} { - error_popup "commit-tree failed:\n\n$err" - set ui_status_value {Commit failed.} - unlock_index - return - } - - # -- Update the HEAD ref. - # - set reflogm commit - if {$commit_type ne {normal}} { - append reflogm " ($commit_type)" - } - set i [string first "\n" $msg] - if {$i >= 0} { - set subject [string range $msg 0 [expr {$i - 1}]] - } else { - set subject $msg - } - append reflogm {: } $subject - if {[catch { - git update-ref -m $reflogm HEAD $cmt_id $curHEAD - } err]} { - error_popup "update-ref failed:\n\n$err" - set ui_status_value {Commit failed.} - unlock_index - return - } - - # -- Cleanup after ourselves. - # - catch {file delete $msg_p} - catch {file delete [gitdir MERGE_HEAD]} - catch {file delete [gitdir MERGE_MSG]} - catch {file delete [gitdir SQUASH_MSG]} - catch {file delete [gitdir GITGUI_MSG]} - - # -- Let rerere do its thing. - # - if {[file isdirectory [gitdir rr-cache]]} { - catch {git rerere} - } - - # -- Run the post-commit hook. - # - set pchook [gitdir hooks post-commit] - if {[is_Cygwin] && [file isfile $pchook]} { - set pchook [list sh -c [concat \ - "if test -x \"$pchook\";" \ - "then exec \"$pchook\";" \ - "fi"]] - } elseif {![file executable $pchook]} { - set pchook {} - } - if {$pchook ne {}} { - catch {exec $pchook &} - } - - $ui_comm delete 0.0 end - $ui_comm edit reset - $ui_comm edit modified false - - if {[is_enabled singlecommit]} do_quit - - # -- Make sure our current branch exists. - # - if {$commit_type eq {initial}} { - lappend all_heads $current_branch - set all_heads [lsort -unique $all_heads] - populate_branch_menu - } - - # -- Update in memory status - # - set selected_commit_type new - set commit_type normal - set HEAD $cmt_id - set PARENT $cmt_id - set MERGE_HEAD [list] - - foreach path [array names file_states] { - set s $file_states($path) - set m [lindex $s 0] - switch -glob -- $m { - _O - - _M - - _D {continue} - __ - - A_ - - M_ - - D_ { - unset file_states($path) - catch {unset selected_paths($path)} - } - DO { - set file_states($path) [list _O [lindex $s 1] {} {}] - } - AM - - AD - - MM - - MD { - set file_states($path) [list \ - _[string index $m 1] \ - [lindex $s 1] \ - [lindex $s 3] \ - {}] - } - } - } - - display_all_files - unlock_index - reshow_diff - set ui_status_value \ - "Created commit [string range $cmt_id 0 7]: $subject" -} - -###################################################################### -## -## fetch push - -proc fetch_from {remote} { - set w [new_console \ - "fetch $remote" \ - "Fetching new changes from $remote"] - set cmd [list git fetch] - lappend cmd $remote - console_exec $w $cmd console_done -} - -proc push_to {remote} { - set w [new_console \ - "push $remote" \ - "Pushing changes to $remote"] - set cmd [list git push] - lappend cmd -v - lappend cmd $remote - console_exec $w $cmd console_done -} - ###################################################################### ## ## ui helpers @@ -1636,2041 +799,6 @@ proc display_all_files {} { $ui_workdir conf -state disabled } -proc update_indexinfo {msg pathList after} { - global update_index_cp ui_status_value - - if {![lock_index update]} return - - set update_index_cp 0 - set pathList [lsort $pathList] - set totalCnt [llength $pathList] - set batch [expr {int($totalCnt * .01) + 1}] - if {$batch > 25} {set batch 25} - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - 0.0] - set fd [open "| git update-index -z --index-info" w] - fconfigure $fd \ - -blocking 0 \ - -buffering full \ - -buffersize 512 \ - -encoding binary \ - -translation binary - fileevent $fd writable [list \ - write_update_indexinfo \ - $fd \ - $pathList \ - $totalCnt \ - $batch \ - $msg \ - $after \ - ] -} - -proc write_update_indexinfo {fd pathList totalCnt batch msg after} { - global update_index_cp ui_status_value - global file_states current_diff_path - - if {$update_index_cp >= $totalCnt} { - close $fd - unlock_index - uplevel #0 $after - return - } - - for {set i $batch} \ - {$update_index_cp < $totalCnt && $i > 0} \ - {incr i -1} { - set path [lindex $pathList $update_index_cp] - incr update_index_cp - - set s $file_states($path) - switch -glob -- [lindex $s 0] { - A? {set new _O} - M? {set new _M} - D_ {set new _D} - D? {set new _?} - ?? {continue} - } - set info [lindex $s 2] - if {$info eq {}} continue - - puts -nonewline $fd "$info\t[encoding convertto $path]\0" - display_file $path $new - } - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - [expr {100.0 * $update_index_cp / $totalCnt}]] -} - -proc update_index {msg pathList after} { - global update_index_cp ui_status_value - - if {![lock_index update]} return - - set update_index_cp 0 - set pathList [lsort $pathList] - set totalCnt [llength $pathList] - set batch [expr {int($totalCnt * .01) + 1}] - if {$batch > 25} {set batch 25} - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - 0.0] - set fd [open "| git update-index --add --remove -z --stdin" w] - fconfigure $fd \ - -blocking 0 \ - -buffering full \ - -buffersize 512 \ - -encoding binary \ - -translation binary - fileevent $fd writable [list \ - write_update_index \ - $fd \ - $pathList \ - $totalCnt \ - $batch \ - $msg \ - $after \ - ] -} - -proc write_update_index {fd pathList totalCnt batch msg after} { - global update_index_cp ui_status_value - global file_states current_diff_path - - if {$update_index_cp >= $totalCnt} { - close $fd - unlock_index - uplevel #0 $after - return - } - - for {set i $batch} \ - {$update_index_cp < $totalCnt && $i > 0} \ - {incr i -1} { - set path [lindex $pathList $update_index_cp] - incr update_index_cp - - switch -glob -- [lindex $file_states($path) 0] { - AD {set new __} - ?D {set new D_} - _O - - AM {set new A_} - U? { - if {[file exists $path]} { - set new M_ - } else { - set new D_ - } - } - ?M {set new M_} - ?? {continue} - } - puts -nonewline $fd "[encoding convertto $path]\0" - display_file $path $new - } - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - [expr {100.0 * $update_index_cp / $totalCnt}]] -} - -proc checkout_index {msg pathList after} { - global update_index_cp ui_status_value - - if {![lock_index update]} return - - set update_index_cp 0 - set pathList [lsort $pathList] - set totalCnt [llength $pathList] - set batch [expr {int($totalCnt * .01) + 1}] - if {$batch > 25} {set batch 25} - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - 0.0] - set cmd [list git checkout-index] - lappend cmd --index - lappend cmd --quiet - lappend cmd --force - lappend cmd -z - lappend cmd --stdin - set fd [open "| $cmd " w] - fconfigure $fd \ - -blocking 0 \ - -buffering full \ - -buffersize 512 \ - -encoding binary \ - -translation binary - fileevent $fd writable [list \ - write_checkout_index \ - $fd \ - $pathList \ - $totalCnt \ - $batch \ - $msg \ - $after \ - ] -} - -proc write_checkout_index {fd pathList totalCnt batch msg after} { - global update_index_cp ui_status_value - global file_states current_diff_path - - if {$update_index_cp >= $totalCnt} { - close $fd - unlock_index - uplevel #0 $after - return - } - - for {set i $batch} \ - {$update_index_cp < $totalCnt && $i > 0} \ - {incr i -1} { - set path [lindex $pathList $update_index_cp] - incr update_index_cp - switch -glob -- [lindex $file_states($path) 0] { - U? {continue} - ?M - - ?D { - puts -nonewline $fd "[encoding convertto $path]\0" - display_file $path ?_ - } - } - } - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - [expr {100.0 * $update_index_cp / $totalCnt}]] -} - -###################################################################### -## -## branch management - -proc is_tracking_branch {name} { - global tracking_branches - - if {![catch {set info $tracking_branches($name)}]} { - return 1 - } - foreach t [array names tracking_branches] { - if {[string match {*/\*} $t] && [string match $t $name]} { - return 1 - } - } - return 0 -} - -proc load_all_heads {} { - global all_heads - - set all_heads [list] - set fd [open "| git for-each-ref --format=%(refname) refs/heads" r] - while {[gets $fd line] > 0} { - if {[is_tracking_branch $line]} continue - if {![regsub ^refs/heads/ $line {} name]} continue - lappend all_heads $name - } - close $fd - - set all_heads [lsort $all_heads] -} - -proc populate_branch_menu {} { - global all_heads disable_on_lock - - set m .mbar.branch - set last [$m index last] - for {set i 0} {$i <= $last} {incr i} { - if {[$m type $i] eq {separator}} { - $m delete $i last - set new_dol [list] - foreach a $disable_on_lock { - if {[lindex $a 0] ne $m || [lindex $a 2] < $i} { - lappend new_dol $a - } - } - set disable_on_lock $new_dol - break - } - } - - if {$all_heads ne {}} { - $m add separator - } - foreach b $all_heads { - $m add radiobutton \ - -label $b \ - -command [list switch_branch $b] \ - -variable current_branch \ - -value $b - lappend disable_on_lock \ - [list $m entryconf [$m index last] -state] - } -} - -proc all_tracking_branches {} { - global tracking_branches - - set all_trackings {} - set cmd {} - foreach name [array names tracking_branches] { - if {[regsub {/\*$} $name {} name]} { - lappend cmd $name - } else { - regsub ^refs/(heads|remotes)/ $name {} name - lappend all_trackings $name - } - } - - if {$cmd ne {}} { - set fd [open "| git for-each-ref --format=%(refname) $cmd" r] - while {[gets $fd name] > 0} { - regsub ^refs/(heads|remotes)/ $name {} name - lappend all_trackings $name - } - close $fd - } - - return [lsort -unique $all_trackings] -} - -proc load_all_tags {} { - set all_tags [list] - set fd [open "| git for-each-ref --format=%(refname) refs/tags" r] - while {[gets $fd line] > 0} { - if {![regsub ^refs/tags/ $line {} name]} continue - lappend all_tags $name - } - close $fd - - return [lsort $all_tags] -} - -proc do_create_branch_action {w} { - global all_heads null_sha1 repo_config - global create_branch_checkout create_branch_revtype - global create_branch_head create_branch_trackinghead - global create_branch_name create_branch_revexp - global create_branch_tag - - set newbranch $create_branch_name - if {$newbranch eq {} - || $newbranch eq $repo_config(gui.newbranchtemplate)} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Please supply a branch name." - focus $w.desc.name_t - return - } - if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Branch '$newbranch' already exists." - focus $w.desc.name_t - return - } - if {[catch {git check-ref-format "heads/$newbranch"}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "We do not like '$newbranch' as a branch name." - focus $w.desc.name_t - return - } - - set rev {} - switch -- $create_branch_revtype { - head {set rev $create_branch_head} - tracking {set rev $create_branch_trackinghead} - tag {set rev $create_branch_tag} - expression {set rev $create_branch_revexp} - } - if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Invalid starting revision: $rev" - return - } - if {[catch { - git update-ref \ - -m "branch: Created from $rev" \ - "refs/heads/$newbranch" \ - $cmt \ - $null_sha1 - } err]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Failed to create '$newbranch'.\n\n$err" - return - } - - lappend all_heads $newbranch - set all_heads [lsort $all_heads] - populate_branch_menu - destroy $w - if {$create_branch_checkout} { - switch_branch $newbranch - } -} - -proc radio_selector {varname value args} { - upvar #0 $varname var - set var $value -} - -trace add variable create_branch_head write \ - [list radio_selector create_branch_revtype head] -trace add variable create_branch_trackinghead write \ - [list radio_selector create_branch_revtype tracking] -trace add variable create_branch_tag write \ - [list radio_selector create_branch_revtype tag] - -trace add variable delete_branch_head write \ - [list radio_selector delete_branch_checktype head] -trace add variable delete_branch_trackinghead write \ - [list radio_selector delete_branch_checktype tracking] - -proc do_create_branch {} { - global all_heads current_branch repo_config - global create_branch_checkout create_branch_revtype - global create_branch_head create_branch_trackinghead - global create_branch_name create_branch_revexp - global create_branch_tag - - set w .branch_editor - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Create New Branch} \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.create -text Create \ - -default active \ - -command [list do_create_branch_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.desc -text {Branch Description} - label $w.desc.name_l -text {Name:} - entry $w.desc.name_t \ - -borderwidth 1 \ - -relief sunken \ - -width 40 \ - -textvariable create_branch_name \ - -validate key \ - -validatecommand { - if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0} - return 1 - } - grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5} - grid columnconfigure $w.desc 1 -weight 1 - pack $w.desc -anchor nw -fill x -pady 5 -padx 5 - - labelframe $w.from -text {Starting Revision} - radiobutton $w.from.head_r \ - -text {Local Branch:} \ - -value head \ - -variable create_branch_revtype - eval tk_optionMenu $w.from.head_m create_branch_head $all_heads - grid $w.from.head_r $w.from.head_m -sticky w - set all_trackings [all_tracking_branches] - if {$all_trackings ne {}} { - set create_branch_trackinghead [lindex $all_trackings 0] - radiobutton $w.from.tracking_r \ - -text {Tracking Branch:} \ - -value tracking \ - -variable create_branch_revtype - eval tk_optionMenu $w.from.tracking_m \ - create_branch_trackinghead \ - $all_trackings - grid $w.from.tracking_r $w.from.tracking_m -sticky w - } - set all_tags [load_all_tags] - if {$all_tags ne {}} { - set create_branch_tag [lindex $all_tags 0] - radiobutton $w.from.tag_r \ - -text {Tag:} \ - -value tag \ - -variable create_branch_revtype - eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags - grid $w.from.tag_r $w.from.tag_m -sticky w - } - radiobutton $w.from.exp_r \ - -text {Revision Expression:} \ - -value expression \ - -variable create_branch_revtype - entry $w.from.exp_t \ - -borderwidth 1 \ - -relief sunken \ - -width 50 \ - -textvariable create_branch_revexp \ - -validate key \ - -validatecommand { - if {%d == 1 && [regexp {\s} %S]} {return 0} - if {%d == 1 && [string length %S] > 0} { - set create_branch_revtype expression - } - return 1 - } - grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5} - grid columnconfigure $w.from 1 -weight 1 - pack $w.from -anchor nw -fill x -pady 5 -padx 5 - - labelframe $w.postActions -text {Post Creation Actions} - checkbutton $w.postActions.checkout \ - -text {Checkout after creation} \ - -variable create_branch_checkout - pack $w.postActions.checkout -anchor nw - pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 - - set create_branch_checkout 1 - set create_branch_head $current_branch - set create_branch_revtype head - set create_branch_name $repo_config(gui.newbranchtemplate) - set create_branch_revexp {} - - bind $w " - grab $w - $w.desc.name_t icursor end - focus $w.desc.name_t - " - bind $w "destroy $w" - bind $w "do_create_branch_action $w;break" - wm title $w "[appname] ([reponame]): Create Branch" - tkwait window $w -} - -proc do_delete_branch_action {w} { - global all_heads - global delete_branch_checktype delete_branch_head delete_branch_trackinghead - - set check_rev {} - switch -- $delete_branch_checktype { - head {set check_rev $delete_branch_head} - tracking {set check_rev $delete_branch_trackinghead} - always {set check_rev {:none}} - } - if {$check_rev eq {:none}} { - set check_cmt {} - } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Invalid check revision: $check_rev" - return - } - - set to_delete [list] - set not_merged [list] - foreach i [$w.list.l curselection] { - set b [$w.list.l get $i] - if {[catch {set o [git rev-parse --verify $b]}]} continue - if {$check_cmt ne {}} { - if {$b eq $check_rev} continue - if {[catch {set m [git merge-base $o $check_cmt]}]} continue - if {$o ne $m} { - lappend not_merged $b - continue - } - } - lappend to_delete [list $b $o] - } - if {$not_merged ne {}} { - set msg "The following branches are not completely merged into $check_rev: - - - [join $not_merged "\n - "]" - tk_messageBox \ - -icon info \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message $msg - } - if {$to_delete eq {}} return - if {$delete_branch_checktype eq {always}} { - set msg {Recovering deleted branches is difficult. - -Delete the selected branches?} - if {[tk_messageBox \ - -icon warning \ - -type yesno \ - -title [wm title $w] \ - -parent $w \ - -message $msg] ne yes} { - return - } - } - - set failed {} - foreach i $to_delete { - set b [lindex $i 0] - set o [lindex $i 1] - if {[catch {git update-ref -d "refs/heads/$b" $o} err]} { - append failed " - $b: $err\n" - } else { - set x [lsearch -sorted -exact $all_heads $b] - if {$x >= 0} { - set all_heads [lreplace $all_heads $x $x] - } - } - } - - if {$failed ne {}} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Failed to delete branches:\n$failed" - } - - set all_heads [lsort $all_heads] - populate_branch_menu - destroy $w -} - -proc do_delete_branch {} { - global all_heads tracking_branches current_branch - global delete_branch_checktype delete_branch_head delete_branch_trackinghead - - set w .branch_editor - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Delete Local Branch} \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.create -text Delete \ - -command [list do_delete_branch_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.list -text {Local Branches} - listbox $w.list.l \ - -height 10 \ - -width 70 \ - -selectmode extended \ - -yscrollcommand [list $w.list.sby set] - foreach h $all_heads { - if {$h ne $current_branch} { - $w.list.l insert end $h - } - } - scrollbar $w.list.sby -command [list $w.list.l yview] - pack $w.list.sby -side right -fill y - pack $w.list.l -side left -fill both -expand 1 - pack $w.list -fill both -expand 1 -pady 5 -padx 5 - - labelframe $w.validate -text {Delete Only If} - radiobutton $w.validate.head_r \ - -text {Merged Into Local Branch:} \ - -value head \ - -variable delete_branch_checktype - eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads - grid $w.validate.head_r $w.validate.head_m -sticky w - set all_trackings [all_tracking_branches] - if {$all_trackings ne {}} { - set delete_branch_trackinghead [lindex $all_trackings 0] - radiobutton $w.validate.tracking_r \ - -text {Merged Into Tracking Branch:} \ - -value tracking \ - -variable delete_branch_checktype - eval tk_optionMenu $w.validate.tracking_m \ - delete_branch_trackinghead \ - $all_trackings - grid $w.validate.tracking_r $w.validate.tracking_m -sticky w - } - radiobutton $w.validate.always_r \ - -text {Always (Do not perform merge checks)} \ - -value always \ - -variable delete_branch_checktype - grid $w.validate.always_r -columnspan 2 -sticky w - grid columnconfigure $w.validate 1 -weight 1 - pack $w.validate -anchor nw -fill x -pady 5 -padx 5 - - set delete_branch_head $current_branch - set delete_branch_checktype head - - bind $w "grab $w; focus $w" - bind $w "destroy $w" - wm title $w "[appname] ([reponame]): Delete Branch" - tkwait window $w -} - -proc switch_branch {new_branch} { - global HEAD commit_type current_branch repo_config - - if {![lock_index switch]} return - - # -- Our in memory state should match the repository. - # - repository_state curType curHEAD curMERGE_HEAD - if {[string match amend* $commit_type] - && $curType eq {normal} - && $curHEAD eq $HEAD} { - } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { - info_popup {Last scanned state does not match repository state. - -Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed. - -The rescan will be automatically started now. -} - unlock_index - rescan {set ui_status_value {Ready.}} - return - } - - # -- Don't do a pointless switch. - # - if {$current_branch eq $new_branch} { - unlock_index - return - } - - if {$repo_config(gui.trustmtime) eq {true}} { - switch_branch_stage2 {} $new_branch - } else { - set ui_status_value {Refreshing file status...} - set cmd [list git update-index] - lappend cmd -q - lappend cmd --unmerged - lappend cmd --ignore-missing - lappend cmd --refresh - set fd_rf [open "| $cmd" r] - fconfigure $fd_rf -blocking 0 -translation binary - fileevent $fd_rf readable \ - [list switch_branch_stage2 $fd_rf $new_branch] - } -} - -proc switch_branch_stage2 {fd_rf new_branch} { - global ui_status_value HEAD - - if {$fd_rf ne {}} { - read $fd_rf - if {![eof $fd_rf]} return - close $fd_rf - } - - set ui_status_value "Updating working directory to '$new_branch'..." - set cmd [list git read-tree] - lappend cmd -m - lappend cmd -u - lappend cmd --exclude-per-directory=.gitignore - lappend cmd $HEAD - lappend cmd $new_branch - set fd_rt [open "| $cmd" r] - fconfigure $fd_rt -blocking 0 -translation binary - fileevent $fd_rt readable \ - [list switch_branch_readtree_wait $fd_rt $new_branch] -} - -proc switch_branch_readtree_wait {fd_rt new_branch} { - global selected_commit_type commit_type HEAD MERGE_HEAD PARENT - global current_branch - global ui_comm ui_status_value - - # -- We never get interesting output on stdout; only stderr. - # - read $fd_rt - fconfigure $fd_rt -blocking 1 - if {![eof $fd_rt]} { - fconfigure $fd_rt -blocking 0 - return - } - - # -- The working directory wasn't in sync with the index and - # we'd have to overwrite something to make the switch. A - # merge is required. - # - if {[catch {close $fd_rt} err]} { - regsub {^fatal: } $err {} err - warn_popup "File level merge required. - -$err - -Staying on branch '$current_branch'." - set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)." - unlock_index - return - } - - # -- Update the symbolic ref. Core git doesn't even check for failure - # here, it Just Works(tm). If it doesn't we are in some really ugly - # state that is difficult to recover from within git-gui. - # - if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} { - error_popup "Failed to set current branch. - -This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file. - -This should not have occurred. [appname] will now close and give up. - -$err" - do_quit - return - } - - # -- Update our repository state. If we were previously in amend mode - # we need to toss the current buffer and do a full rescan to update - # our file lists. If we weren't in amend mode our file lists are - # accurate and we can avoid the rescan. - # - unlock_index - set selected_commit_type new - if {[string match amend* $commit_type]} { - $ui_comm delete 0.0 end - $ui_comm edit reset - $ui_comm edit modified false - rescan {set ui_status_value "Checked out branch '$current_branch'."} - } else { - repository_state commit_type HEAD MERGE_HEAD - set PARENT $HEAD - set ui_status_value "Checked out branch '$current_branch'." - } -} - -###################################################################### -## -## remote management - -proc load_all_remotes {} { - global repo_config - global all_remotes tracking_branches - - set all_remotes [list] - array unset tracking_branches - - set rm_dir [gitdir remotes] - if {[file isdirectory $rm_dir]} { - set all_remotes [glob \ - -types f \ - -tails \ - -nocomplain \ - -directory $rm_dir *] - - foreach name $all_remotes { - catch { - set fd [open [file join $rm_dir $name] r] - while {[gets $fd line] >= 0} { - if {![regexp {^Pull:[ ]*([^:]+):(.+)$} \ - $line line src dst]} continue - if {![regexp ^refs/ $dst]} { - set dst "refs/heads/$dst" - } - set tracking_branches($dst) [list $name $src] - } - close $fd - } - } - } - - foreach line [array names repo_config remote.*.url] { - if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue - lappend all_remotes $name - - if {[catch {set fl $repo_config(remote.$name.fetch)}]} { - set fl {} - } - foreach line $fl { - if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue - if {![regexp ^refs/ $dst]} { - set dst "refs/heads/$dst" - } - set tracking_branches($dst) [list $name $src] - } - } - - set all_remotes [lsort -unique $all_remotes] -} - -proc populate_fetch_menu {} { - global all_remotes repo_config - - set m .mbar.fetch - foreach r $all_remotes { - set enable 0 - if {![catch {set a $repo_config(remote.$r.url)}]} { - if {![catch {set a $repo_config(remote.$r.fetch)}]} { - set enable 1 - } - } else { - catch { - set fd [open [gitdir remotes $r] r] - while {[gets $fd n] >= 0} { - if {[regexp {^Pull:[ \t]*([^:]+):} $n]} { - set enable 1 - break - } - } - close $fd - } - } - - if {$enable} { - $m add command \ - -label "Fetch from $r..." \ - -command [list fetch_from $r] - } - } -} - -proc populate_push_menu {} { - global all_remotes repo_config - - set m .mbar.push - set fast_count 0 - foreach r $all_remotes { - set enable 0 - if {![catch {set a $repo_config(remote.$r.url)}]} { - if {![catch {set a $repo_config(remote.$r.push)}]} { - set enable 1 - } - } else { - catch { - set fd [open [gitdir remotes $r] r] - while {[gets $fd n] >= 0} { - if {[regexp {^Push:[ \t]*([^:]+):} $n]} { - set enable 1 - break - } - } - close $fd - } - } - - if {$enable} { - if {!$fast_count} { - $m add separator - } - $m add command \ - -label "Push to $r..." \ - -command [list push_to $r] - incr fast_count - } - } -} - -proc start_push_anywhere_action {w} { - global push_urltype push_remote push_url push_thin push_tags - - set r_url {} - switch -- $push_urltype { - remote {set r_url $push_remote} - url {set r_url $push_url} - } - if {$r_url eq {}} return - - set cmd [list git push] - lappend cmd -v - if {$push_thin} { - lappend cmd --thin - } - if {$push_tags} { - lappend cmd --tags - } - lappend cmd $r_url - set cnt 0 - foreach i [$w.source.l curselection] { - set b [$w.source.l get $i] - lappend cmd "refs/heads/$b:refs/heads/$b" - incr cnt - } - if {$cnt == 0} { - return - } elseif {$cnt == 1} { - set unit branch - } else { - set unit branches - } - - set cons [new_console "push $r_url" "Pushing $cnt $unit to $r_url"] - console_exec $cons $cmd console_done - destroy $w -} - -trace add variable push_remote write \ - [list radio_selector push_urltype remote] - -proc do_push_anywhere {} { - global all_heads all_remotes current_branch - global push_urltype push_remote push_url push_thin push_tags - - set w .push_setup - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Push Branches} -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.create -text Push \ - -default active \ - -command [list start_push_anywhere_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -default normal \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.source -text {Source Branches} - listbox $w.source.l \ - -height 10 \ - -width 70 \ - -selectmode extended \ - -yscrollcommand [list $w.source.sby set] - foreach h $all_heads { - $w.source.l insert end $h - if {$h eq $current_branch} { - $w.source.l select set end - } - } - scrollbar $w.source.sby -command [list $w.source.l yview] - pack $w.source.sby -side right -fill y - pack $w.source.l -side left -fill both -expand 1 - pack $w.source -fill both -expand 1 -pady 5 -padx 5 - - labelframe $w.dest -text {Destination Repository} - if {$all_remotes ne {}} { - radiobutton $w.dest.remote_r \ - -text {Remote:} \ - -value remote \ - -variable push_urltype - eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes - grid $w.dest.remote_r $w.dest.remote_m -sticky w - if {[lsearch -sorted -exact $all_remotes origin] != -1} { - set push_remote origin - } else { - set push_remote [lindex $all_remotes 0] - } - set push_urltype remote - } else { - set push_urltype url - } - radiobutton $w.dest.url_r \ - -text {Arbitrary URL:} \ - -value url \ - -variable push_urltype - entry $w.dest.url_t \ - -borderwidth 1 \ - -relief sunken \ - -width 50 \ - -textvariable push_url \ - -validate key \ - -validatecommand { - if {%d == 1 && [regexp {\s} %S]} {return 0} - if {%d == 1 && [string length %S] > 0} { - set push_urltype url - } - return 1 - } - grid $w.dest.url_r $w.dest.url_t -sticky we -padx {0 5} - grid columnconfigure $w.dest 1 -weight 1 - pack $w.dest -anchor nw -fill x -pady 5 -padx 5 - - labelframe $w.options -text {Transfer Options} - checkbutton $w.options.thin \ - -text {Use thin pack (for slow network connections)} \ - -variable push_thin - grid $w.options.thin -columnspan 2 -sticky w - checkbutton $w.options.tags \ - -text {Include tags} \ - -variable push_tags - grid $w.options.tags -columnspan 2 -sticky w - grid columnconfigure $w.options 1 -weight 1 - pack $w.options -anchor nw -fill x -pady 5 -padx 5 - - set push_url {} - set push_thin 0 - set push_tags 0 - - bind $w "grab $w; focus $w.buttons.create" - bind $w "destroy $w" - bind $w [list start_push_anywhere_action $w] - wm title $w "[appname] ([reponame]): Push" - tkwait window $w -} - -###################################################################### -## -## merge - -proc can_merge {} { - global HEAD commit_type file_states - - if {[string match amend* $commit_type]} { - info_popup {Cannot merge while amending. - -You must finish amending this commit before starting any type of merge. -} - return 0 - } - - if {[committer_ident] eq {}} {return 0} - if {![lock_index merge]} {return 0} - - # -- Our in memory state should match the repository. - # - repository_state curType curHEAD curMERGE_HEAD - if {$commit_type ne $curType || $HEAD ne $curHEAD} { - info_popup {Last scanned state does not match repository state. - -Another Git program has modified this repository since the last scan. A rescan must be performed before a merge can be performed. - -The rescan will be automatically started now. -} - unlock_index - rescan {set ui_status_value {Ready.}} - return 0 - } - - foreach path [array names file_states] { - switch -glob -- [lindex $file_states($path) 0] { - _O { - continue; # and pray it works! - } - U? { - error_popup "You are in the middle of a conflicted merge. - -File [short_path $path] has merge conflicts. - -You must resolve them, add the file, and commit to complete the current merge. Only then can you begin another merge. -" - unlock_index - return 0 - } - ?? { - error_popup "You are in the middle of a change. - -File [short_path $path] is modified. - -You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise. -" - unlock_index - return 0 - } - } - } - - return 1 -} - -proc visualize_local_merge {w} { - set revs {} - foreach i [$w.source.l curselection] { - lappend revs [$w.source.l get $i] - } - if {$revs eq {}} return - lappend revs --not HEAD - do_gitk $revs -} - -proc start_local_merge_action {w} { - global HEAD ui_status_value current_branch - - set cmd [list git merge] - set names {} - set revcnt 0 - foreach i [$w.source.l curselection] { - set b [$w.source.l get $i] - lappend cmd $b - lappend names $b - incr revcnt - } - - if {$revcnt == 0} { - return - } elseif {$revcnt == 1} { - set unit branch - } elseif {$revcnt <= 15} { - set unit branches - - if {[tk_dialog \ - $w.confirm_octopus \ - [wm title $w] \ - "Use octopus merge strategy? - -You are merging $revcnt branches at once. This requires using the octopus merge driver, which may not succeed if there are file-level conflicts. -" \ - question \ - 0 \ - {Cancel} \ - {Use octopus} \ - ] != 1} return - } else { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Too many branches selected. - -You have requested to merge $revcnt branches in an octopus merge. This exceeds Git's internal limit of 15 branches per merge. - -Please select fewer branches. To merge more than 15 branches, merge the branches in batches. -" - return - } - - set msg "Merging $current_branch, [join $names {, }]" - set ui_status_value "$msg..." - set cons [new_console "Merge" $msg] - console_exec $cons $cmd [list finish_merge $revcnt] - bind $w {} - destroy $w -} - -proc finish_merge {revcnt w ok} { - console_done $w $ok - if {$ok} { - set msg {Merge completed successfully.} - } else { - if {$revcnt != 1} { - info_popup "Octopus merge failed. - -Your merge of $revcnt branches has failed. - -There are file-level conflicts between the branches which must be resolved manually. - -The working directory will now be reset. - -You can attempt this merge again by merging only one branch at a time." $w - - set fd [open "| git read-tree --reset -u HEAD" r] - fconfigure $fd -blocking 0 -translation binary - fileevent $fd readable [list reset_hard_wait $fd] - set ui_status_value {Aborting... please wait...} - return - } - - set msg {Merge failed. Conflict resolution is required.} - } - unlock_index - rescan [list set ui_status_value $msg] -} - -proc do_local_merge {} { - global current_branch - - if {![can_merge]} return - - set w .merge_setup - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header \ - -text "Merge Into $current_branch" \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.visualize -text Visualize \ - -command [list visualize_local_merge $w] - pack $w.buttons.visualize -side left - button $w.buttons.create -text Merge \ - -command [list start_local_merge_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.source -text {Source Branches} - listbox $w.source.l \ - -height 10 \ - -width 70 \ - -selectmode extended \ - -yscrollcommand [list $w.source.sby set] - scrollbar $w.source.sby -command [list $w.source.l yview] - pack $w.source.sby -side right -fill y - pack $w.source.l -side left -fill both -expand 1 - pack $w.source -fill both -expand 1 -pady 5 -padx 5 - - set cmd [list git for-each-ref] - lappend cmd {--format=%(objectname) %(*objectname) %(refname)} - lappend cmd refs/heads - lappend cmd refs/remotes - lappend cmd refs/tags - set fr_fd [open "| $cmd" r] - fconfigure $fr_fd -translation binary - while {[gets $fr_fd line] > 0} { - set line [split $line { }] - set sha1([lindex $line 0]) [lindex $line 2] - set sha1([lindex $line 1]) [lindex $line 2] - } - close $fr_fd - - set to_show {} - set fr_fd [open "| git rev-list --all --not HEAD"] - while {[gets $fr_fd line] > 0} { - if {[catch {set ref $sha1($line)}]} continue - regsub ^refs/(heads|remotes|tags)/ $ref {} ref - lappend to_show $ref - } - close $fr_fd - - foreach ref [lsort -unique $to_show] { - $w.source.l insert end $ref - } - - bind $w "grab $w" - bind $w "unlock_index;destroy $w" - bind $w unlock_index - wm title $w "[appname] ([reponame]): Merge" - tkwait window $w -} - -proc do_reset_hard {} { - global HEAD commit_type file_states - - if {[string match amend* $commit_type]} { - info_popup {Cannot abort while amending. - -You must finish amending this commit. -} - return - } - - if {![lock_index abort]} return - - if {[string match *merge* $commit_type]} { - set op merge - } else { - set op commit - } - - if {[ask_popup "Abort $op? - -Aborting the current $op will cause *ALL* uncommitted changes to be lost. - -Continue with aborting the current $op?"] eq {yes}} { - set fd [open "| git read-tree --reset -u HEAD" r] - fconfigure $fd -blocking 0 -translation binary - fileevent $fd readable [list reset_hard_wait $fd] - set ui_status_value {Aborting... please wait...} - } else { - unlock_index - } -} - -proc reset_hard_wait {fd} { - global ui_comm - - read $fd - if {[eof $fd]} { - close $fd - unlock_index - - $ui_comm delete 0.0 end - $ui_comm edit modified false - - catch {file delete [gitdir MERGE_HEAD]} - catch {file delete [gitdir rr-cache MERGE_RR]} - catch {file delete [gitdir SQUASH_MSG]} - catch {file delete [gitdir MERGE_MSG]} - catch {file delete [gitdir GITGUI_MSG]} - - rescan {set ui_status_value {Abort completed. Ready.}} - } -} - -###################################################################### -## -## browser - -set next_browser_id 0 - -proc new_browser {commit} { - global next_browser_id cursor_ptr M1B - global browser_commit browser_status browser_stack browser_path browser_busy - - if {[winfo ismapped .]} { - set w .browser[incr next_browser_id] - set tl $w - toplevel $w - } else { - set w {} - set tl . - } - set w_list $w.list.l - set browser_commit($w_list) $commit - set browser_status($w_list) {Starting...} - set browser_stack($w_list) {} - set browser_path($w_list) $browser_commit($w_list): - set browser_busy($w_list) 1 - - label $w.path -textvariable browser_path($w_list) \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken \ - -font font_uibold - pack $w.path -anchor w -side top -fill x - - frame $w.list - text $w_list -background white -borderwidth 0 \ - -cursor $cursor_ptr \ - -state disabled \ - -wrap none \ - -height 20 \ - -width 70 \ - -xscrollcommand [list $w.list.sbx set] \ - -yscrollcommand [list $w.list.sby set] - $w_list tag conf in_sel \ - -background [$w_list cget -foreground] \ - -foreground [$w_list cget -background] - scrollbar $w.list.sbx -orient h -command [list $w_list xview] - scrollbar $w.list.sby -orient v -command [list $w_list yview] - pack $w.list.sbx -side bottom -fill x - pack $w.list.sby -side right -fill y - pack $w_list -side left -fill both -expand 1 - pack $w.list -side top -fill both -expand 1 - - label $w.status -textvariable browser_status($w_list) \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken - pack $w.status -anchor w -side bottom -fill x - - bind $w_list "browser_click 0 $w_list @%x,%y;break" - bind $w_list "browser_click 1 $w_list @%x,%y;break" - bind $w_list <$M1B-Up> "browser_parent $w_list;break" - bind $w_list <$M1B-Left> "browser_parent $w_list;break" - bind $w_list "browser_move -1 $w_list;break" - bind $w_list "browser_move 1 $w_list;break" - bind $w_list <$M1B-Right> "browser_enter $w_list;break" - bind $w_list "browser_enter $w_list;break" - bind $w_list "browser_page -1 $w_list;break" - bind $w_list "browser_page 1 $w_list;break" - bind $w_list break - bind $w_list break - - bind $tl "focus $w" - bind $tl " - array unset browser_buffer $w_list - array unset browser_files $w_list - array unset browser_status $w_list - array unset browser_stack $w_list - array unset browser_path $w_list - array unset browser_commit $w_list - array unset browser_busy $w_list - " - wm title $tl "[appname] ([reponame]): File Browser" - ls_tree $w_list $browser_commit($w_list) {} -} - -proc browser_move {dir w} { - global browser_files browser_busy - - if {$browser_busy($w)} return - set lno [lindex [split [$w index in_sel.first] .] 0] - incr lno $dir - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { - $w tag remove in_sel 0.0 end - $w tag add in_sel $lno.0 [expr {$lno + 1}].0 - $w see $lno.0 - } -} - -proc browser_page {dir w} { - global browser_files browser_busy - - if {$browser_busy($w)} return - $w yview scroll $dir pages - set lno [expr {int( - [lindex [$w yview] 0] - * [llength $browser_files($w)] - + 1)}] - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { - $w tag remove in_sel 0.0 end - $w tag add in_sel $lno.0 [expr {$lno + 1}].0 - $w see $lno.0 - } -} - -proc browser_parent {w} { - global browser_files browser_status browser_path - global browser_stack browser_busy - - if {$browser_busy($w)} return - set info [lindex $browser_files($w) 0] - if {[lindex $info 0] eq {parent}} { - set parent [lindex $browser_stack($w) end-1] - set browser_stack($w) [lrange $browser_stack($w) 0 end-2] - if {$browser_stack($w) eq {}} { - regsub {:.*$} $browser_path($w) {:} browser_path($w) - } else { - regsub {/[^/]+$} $browser_path($w) {} browser_path($w) - } - set browser_status($w) "Loading $browser_path($w)..." - ls_tree $w [lindex $parent 0] [lindex $parent 1] - } -} - -proc browser_enter {w} { - global browser_files browser_status browser_path - global browser_commit browser_stack browser_busy - - if {$browser_busy($w)} return - set lno [lindex [split [$w index in_sel.first] .] 0] - set info [lindex $browser_files($w) [expr {$lno - 1}]] - if {$info ne {}} { - switch -- [lindex $info 0] { - parent { - browser_parent $w - } - tree { - set name [lindex $info 2] - set escn [escape_path $name] - set browser_status($w) "Loading $escn..." - append browser_path($w) $escn - ls_tree $w [lindex $info 1] $name - } - blob { - set name [lindex $info 2] - set p {} - foreach n $browser_stack($w) { - append p [lindex $n 1] - } - append p $name - show_blame $browser_commit($w) $p - } - } - } -} - -proc browser_click {was_double_click w pos} { - global browser_files browser_busy - - if {$browser_busy($w)} return - set lno [lindex [split [$w index $pos] .] 0] - focus $w - - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { - $w tag remove in_sel 0.0 end - $w tag add in_sel $lno.0 [expr {$lno + 1}].0 - if {$was_double_click} { - browser_enter $w - } - } -} - -proc ls_tree {w tree_id name} { - global browser_buffer browser_files browser_stack browser_busy - - set browser_buffer($w) {} - set browser_files($w) {} - set browser_busy($w) 1 - - $w conf -state normal - $w tag remove in_sel 0.0 end - $w delete 0.0 end - if {$browser_stack($w) ne {}} { - $w image create end \ - -align center -padx 5 -pady 1 \ - -name icon0 \ - -image file_uplevel - $w insert end {[Up To Parent]} - lappend browser_files($w) parent - } - lappend browser_stack($w) [list $tree_id $name] - $w conf -state disabled - - set cmd [list git ls-tree -z $tree_id] - set fd [open "| $cmd" r] - fconfigure $fd -blocking 0 -translation binary -encoding binary - fileevent $fd readable [list read_ls_tree $fd $w] -} - -proc read_ls_tree {fd w} { - global browser_buffer browser_files browser_status browser_busy - - if {![winfo exists $w]} { - catch {close $fd} - return - } - - append browser_buffer($w) [read $fd] - set pck [split $browser_buffer($w) "\0"] - set browser_buffer($w) [lindex $pck end] - - set n [llength $browser_files($w)] - $w conf -state normal - foreach p [lrange $pck 0 end-1] { - set info [split $p "\t"] - set path [lindex $info 1] - set info [split [lindex $info 0] { }] - set type [lindex $info 1] - set object [lindex $info 2] - - switch -- $type { - blob { - set image file_mod - } - tree { - set image file_dir - append path / - } - default { - set image file_question - } - } - - if {$n > 0} {$w insert end "\n"} - $w image create end \ - -align center -padx 5 -pady 1 \ - -name icon[incr n] \ - -image $image - $w insert end [escape_path $path] - lappend browser_files($w) [list $type $object $path] - } - $w conf -state disabled - - if {[eof $fd]} { - close $fd - set browser_status($w) Ready. - set browser_busy($w) 0 - array unset browser_buffer $w - if {$n > 0} { - $w tag add in_sel 1.0 2.0 - focus -force $w - } - } -} - -proc show_blame {commit path} { - global next_browser_id blame_status blame_data - - if {[winfo ismapped .]} { - set w .browser[incr next_browser_id] - set tl $w - toplevel $w - } else { - set w {} - set tl . - } - set blame_status($w) {Loading current file content...} - - label $w.path -text "$commit:$path" \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken \ - -font font_uibold - pack $w.path -side top -fill x - - frame $w.out - text $w.out.loaded_t \ - -background white -borderwidth 0 \ - -state disabled \ - -wrap none \ - -height 40 \ - -width 1 \ - -font font_diff - $w.out.loaded_t tag conf annotated -background grey - - text $w.out.linenumber_t \ - -background white -borderwidth 0 \ - -state disabled \ - -wrap none \ - -height 40 \ - -width 5 \ - -font font_diff - $w.out.linenumber_t tag conf linenumber -justify right - - text $w.out.file_t \ - -background white -borderwidth 0 \ - -state disabled \ - -wrap none \ - -height 40 \ - -width 80 \ - -xscrollcommand [list $w.out.sbx set] \ - -font font_diff - - scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview] - scrollbar $w.out.sby -orient v \ - -command [list scrollbar2many [list \ - $w.out.loaded_t \ - $w.out.linenumber_t \ - $w.out.file_t \ - ] yview] - grid \ - $w.out.linenumber_t \ - $w.out.loaded_t \ - $w.out.file_t \ - $w.out.sby \ - -sticky nsew - grid conf $w.out.sbx -column 2 -sticky we - grid columnconfigure $w.out 2 -weight 1 - grid rowconfigure $w.out 0 -weight 1 - pack $w.out -fill both -expand 1 - - label $w.status -textvariable blame_status($w) \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken - pack $w.status -side bottom -fill x - - frame $w.cm - text $w.cm.t \ - -background white -borderwidth 0 \ - -state disabled \ - -wrap none \ - -height 10 \ - -width 80 \ - -xscrollcommand [list $w.cm.sbx set] \ - -yscrollcommand [list $w.cm.sby set] \ - -font font_diff - scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview] - scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview] - pack $w.cm.sby -side right -fill y - pack $w.cm.sbx -side bottom -fill x - pack $w.cm.t -expand 1 -fill both - pack $w.cm -side bottom -fill x - - menu $w.ctxm -tearoff 0 - $w.ctxm add command -label "Copy Commit" \ - -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY" - - foreach i [list \ - $w.out.loaded_t \ - $w.out.linenumber_t \ - $w.out.file_t] { - $i tag conf in_sel \ - -background [$i cget -foreground] \ - -foreground [$i cget -background] - $i conf -yscrollcommand \ - [list many2scrollbar [list \ - $w.out.loaded_t \ - $w.out.linenumber_t \ - $w.out.file_t \ - ] yview $w.out.sby] - bind $i " - blame_click {$w} \\ - $w.cm.t \\ - $w.out.linenumber_t \\ - $w.out.file_t \\ - $i @%x,%y - focus $i - " - bind_button3 $i " - set cursorX %x - set cursorY %y - set cursorW %W - tk_popup $w.ctxm %X %Y - " - } - - bind $w.cm.t "focus $w.cm.t" - bind $tl "focus $tl" - bind $tl " - array unset blame_status {$w} - array unset blame_data $w,* - " - wm title $tl "[appname] ([reponame]): File Viewer" - - set blame_data($w,commit_count) 0 - set blame_data($w,commit_list) {} - set blame_data($w,total_lines) 0 - set blame_data($w,blame_lines) 0 - set blame_data($w,highlight_commit) {} - set blame_data($w,highlight_line) -1 - - set cmd [list git cat-file blob "$commit:$path"] - set fd [open "| $cmd" r] - fconfigure $fd -blocking 0 -translation lf -encoding binary - fileevent $fd readable [list read_blame_catfile \ - $fd $w $commit $path \ - $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t] -} - -proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} { - global blame_status blame_data - - if {![winfo exists $w_file]} { - catch {close $fd} - return - } - - set n $blame_data($w,total_lines) - $w_load conf -state normal - $w_line conf -state normal - $w_file conf -state normal - while {[gets $fd line] >= 0} { - regsub "\r\$" $line {} line - incr n - $w_load insert end "\n" - $w_line insert end "$n\n" linenumber - $w_file insert end "$line\n" - } - $w_load conf -state disabled - $w_line conf -state disabled - $w_file conf -state disabled - set blame_data($w,total_lines) $n - - if {[eof $fd]} { - close $fd - blame_incremental_status $w - set cmd [list git blame -M -C --incremental] - lappend cmd $commit -- $path - set fd [open "| $cmd" r] - fconfigure $fd -blocking 0 -translation lf -encoding binary - fileevent $fd readable [list read_blame_incremental $fd $w \ - $w_load $w_cmit $w_line $w_file] - } -} - -proc read_blame_incremental {fd w w_load w_cmit w_line w_file} { - global blame_status blame_data - - if {![winfo exists $w_file]} { - catch {close $fd} - return - } - - while {[gets $fd line] >= 0} { - if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \ - cmit original_line final_line line_count]} { - set blame_data($w,commit) $cmit - set blame_data($w,original_line) $original_line - set blame_data($w,final_line) $final_line - set blame_data($w,line_count) $line_count - - if {[catch {set g $blame_data($w,$cmit,order)}]} { - $w_line tag conf g$cmit - $w_file tag conf g$cmit - $w_line tag raise in_sel - $w_file tag raise in_sel - $w_file tag raise sel - set blame_data($w,$cmit,order) $blame_data($w,commit_count) - incr blame_data($w,commit_count) - lappend blame_data($w,commit_list) $cmit - } - } elseif {[string match {filename *} $line]} { - set file [string range $line 9 end] - set n $blame_data($w,line_count) - set lno $blame_data($w,final_line) - set cmit $blame_data($w,commit) - - while {$n > 0} { - if {[catch {set g g$blame_data($w,line$lno,commit)}]} { - $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c" - } else { - $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c" - $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c" - } - - set blame_data($w,line$lno,commit) $cmit - set blame_data($w,line$lno,file) $file - $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c" - $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c" - - if {$blame_data($w,highlight_line) == -1} { - if {[lindex [$w_file yview] 0] == 0} { - $w_file see $lno.0 - blame_showcommit $w $w_cmit $w_line $w_file $lno - } - } elseif {$blame_data($w,highlight_line) == $lno} { - blame_showcommit $w $w_cmit $w_line $w_file $lno - } - - incr n -1 - incr lno - incr blame_data($w,blame_lines) - } - - set hc $blame_data($w,highlight_commit) - if {$hc ne {} - && [expr {$blame_data($w,$hc,order) + 1}] - == $blame_data($w,$cmit,order)} { - blame_showcommit $w $w_cmit $w_line $w_file \ - $blame_data($w,highlight_line) - } - } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} { - set blame_data($w,$blame_data($w,commit),$header) $data - } - } - - if {[eof $fd]} { - close $fd - set blame_status($w) {Annotation complete.} - } else { - blame_incremental_status $w - } -} - -proc blame_incremental_status {w} { - global blame_status blame_data - - set have $blame_data($w,blame_lines) - set total $blame_data($w,total_lines) - set pdone 0 - if {$total} {set pdone [expr {100 * $have / $total}]} - - set blame_status($w) [format \ - "Loading annotations... %i of %i lines annotated (%2i%%)" \ - $have $total $pdone] -} - -proc blame_click {w w_cmit w_line w_file cur_w pos} { - set lno [lindex [split [$cur_w index $pos] .] 0] - if {$lno eq {}} return - - $w_line tag remove in_sel 0.0 end - $w_file tag remove in_sel 0.0 end - $w_line tag add in_sel $lno.0 "$lno.0 + 1 line" - $w_file tag add in_sel $lno.0 "$lno.0 + 1 line" - - blame_showcommit $w $w_cmit $w_line $w_file $lno -} - -set blame_colors { - #ff4040 - #ff40ff - #4040ff -} - -proc blame_showcommit {w w_cmit w_line w_file lno} { - global blame_colors blame_data repo_config - - set cmit $blame_data($w,highlight_commit) - if {$cmit ne {}} { - set idx $blame_data($w,$cmit,order) - set i 0 - foreach c $blame_colors { - set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] - $w_line tag conf g$h -background white - $w_file tag conf g$h -background white - incr i - } - } - - $w_cmit conf -state normal - $w_cmit delete 0.0 end - if {[catch {set cmit $blame_data($w,line$lno,commit)}]} { - set cmit {} - $w_cmit insert end "Loading annotation..." - } else { - set idx $blame_data($w,$cmit,order) - set i 0 - foreach c $blame_colors { - set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] - $w_line tag conf g$h -background $c - $w_file tag conf g$h -background $c - incr i - } - - set author_name {} - set author_email {} - set author_time {} - catch {set author_name $blame_data($w,$cmit,author)} - catch {set author_email $blame_data($w,$cmit,author-mail)} - catch {set author_time [clock format $blame_data($w,$cmit,author-time)]} - - set committer_name {} - set committer_email {} - set committer_time {} - catch {set committer_name $blame_data($w,$cmit,committer)} - catch {set committer_email $blame_data($w,$cmit,committer-mail)} - catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]} - - if {[catch {set msg $blame_data($w,$cmit,message)}]} { - set msg {} - catch { - set fd [open "| git cat-file commit $cmit" r] - fconfigure $fd -encoding binary -translation lf - if {[catch {set enc $repo_config(i18n.commitencoding)}]} { - set enc utf-8 - } - while {[gets $fd line] > 0} { - if {[string match {encoding *} $line]} { - set enc [string tolower [string range $line 9 end]] - } - } - set msg [encoding convertfrom $enc [read $fd]] - set msg [string trim $msg] - close $fd - - set author_name [encoding convertfrom $enc $author_name] - set committer_name [encoding convertfrom $enc $committer_name] - - set blame_data($w,$cmit,author) $author_name - set blame_data($w,$cmit,committer) $committer_name - } - set blame_data($w,$cmit,message) $msg - } - - $w_cmit insert end "commit $cmit\n" - $w_cmit insert end "Author: $author_name $author_email $author_time\n" - $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n" - $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n" - $w_cmit insert end "\n" - $w_cmit insert end $msg - } - $w_cmit conf -state disabled - - set blame_data($w,highlight_line) $lno - set blame_data($w,highlight_commit) $cmit -} - -proc blame_copycommit {w i pos} { - global blame_data - set lno [lindex [split [$i index $pos] .] 0] - if {![catch {set commit $blame_data($w,line$lno,commit)}]} { - clipboard clear - clipboard append \ - -format STRING \ - -type STRING \ - -- $commit - } -} - ###################################################################### ## ## icons @@ -3845,230 +973,6 @@ proc incr_font_size {font {amt 1}} { font configure ${font}bold -size $sz } -proc hook_failed_popup {hook msg} { - set w .hookfail - toplevel $w - - frame $w.m - label $w.m.l1 -text "$hook hook failed:" \ - -anchor w \ - -justify left \ - -font font_uibold - text $w.m.t \ - -background white -borderwidth 1 \ - -relief sunken \ - -width 80 -height 10 \ - -font font_diff \ - -yscrollcommand [list $w.m.sby set] - label $w.m.l2 \ - -text {You must correct the above errors before committing.} \ - -anchor w \ - -justify left \ - -font font_uibold - scrollbar $w.m.sby -command [list $w.m.t yview] - pack $w.m.l1 -side top -fill x - pack $w.m.l2 -side bottom -fill x - pack $w.m.sby -side right -fill y - pack $w.m.t -side left -fill both -expand 1 - pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 - - $w.m.t insert 1.0 $msg - $w.m.t conf -state disabled - - button $w.ok -text OK \ - -width 15 \ - -command "destroy $w" - pack $w.ok -side bottom -anchor e -pady 10 -padx 10 - - bind $w "grab $w; focus $w" - bind $w "destroy $w" - wm title $w "[appname] ([reponame]): error" - tkwait window $w -} - -set next_console_id 0 - -proc new_console {short_title long_title} { - global next_console_id console_data - set w .console[incr next_console_id] - set console_data($w) [list $short_title $long_title] - return [console_init $w] -} - -proc console_init {w} { - global console_cr console_data M1B - - set console_cr($w) 1.0 - toplevel $w - frame $w.m - label $w.m.l1 -text "[lindex $console_data($w) 1]:" \ - -anchor w \ - -justify left \ - -font font_uibold - text $w.m.t \ - -background white -borderwidth 1 \ - -relief sunken \ - -width 80 -height 10 \ - -font font_diff \ - -state disabled \ - -yscrollcommand [list $w.m.sby set] - label $w.m.s -text {Working... please wait...} \ - -anchor w \ - -justify left \ - -font font_uibold - scrollbar $w.m.sby -command [list $w.m.t yview] - pack $w.m.l1 -side top -fill x - pack $w.m.s -side bottom -fill x - pack $w.m.sby -side right -fill y - pack $w.m.t -side left -fill both -expand 1 - pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 - - menu $w.ctxm -tearoff 0 - $w.ctxm add command -label "Copy" \ - -command "tk_textCopy $w.m.t" - $w.ctxm add command -label "Select All" \ - -command "focus $w.m.t;$w.m.t tag add sel 0.0 end" - $w.ctxm add command -label "Copy All" \ - -command " - $w.m.t tag add sel 0.0 end - tk_textCopy $w.m.t - $w.m.t tag remove sel 0.0 end - " - - button $w.ok -text {Close} \ - -state disabled \ - -command "destroy $w" - pack $w.ok -side bottom -anchor e -pady 10 -padx 10 - - bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y" - bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break" - bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break" - bind $w "focus $w" - wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]" - return $w -} - -proc console_exec {w cmd after} { - # -- Cygwin's Tcl tosses the enviroment when we exec our child. - # But most users need that so we have to relogin. :-( - # - if {[is_Cygwin]} { - set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"] - } - - # -- Tcl won't let us redirect both stdout and stderr to - # the same pipe. So pass it through cat... - # - set cmd [concat | $cmd |& cat] - - set fd_f [open $cmd r] - fconfigure $fd_f -blocking 0 -translation binary - fileevent $fd_f readable [list console_read $w $fd_f $after] -} - -proc console_read {w fd after} { - global console_cr - - set buf [read $fd] - if {$buf ne {}} { - if {![winfo exists $w]} {console_init $w} - $w.m.t conf -state normal - set c 0 - set n [string length $buf] - while {$c < $n} { - set cr [string first "\r" $buf $c] - set lf [string first "\n" $buf $c] - if {$cr < 0} {set cr [expr {$n + 1}]} - if {$lf < 0} {set lf [expr {$n + 1}]} - - if {$lf < $cr} { - $w.m.t insert end [string range $buf $c $lf] - set console_cr($w) [$w.m.t index {end -1c}] - set c $lf - incr c - } else { - $w.m.t delete $console_cr($w) end - $w.m.t insert end "\n" - $w.m.t insert end [string range $buf $c $cr] - set c $cr - incr c - } - } - $w.m.t conf -state disabled - $w.m.t see end - } - - fconfigure $fd -blocking 1 - if {[eof $fd]} { - if {[catch {close $fd}]} { - set ok 0 - } else { - set ok 1 - } - uplevel #0 $after $w $ok - return - } - fconfigure $fd -blocking 0 -} - -proc console_chain {cmdlist w {ok 1}} { - if {$ok} { - if {[llength $cmdlist] == 0} { - console_done $w $ok - return - } - - set cmd [lindex $cmdlist 0] - set cmdlist [lrange $cmdlist 1 end] - - if {[lindex $cmd 0] eq {console_exec}} { - console_exec $w \ - [lindex $cmd 1] \ - [list console_chain $cmdlist] - } else { - uplevel #0 $cmd $cmdlist $w $ok - } - } else { - console_done $w $ok - } -} - -proc console_done {args} { - global console_cr console_data - - switch -- [llength $args] { - 2 { - set w [lindex $args 0] - set ok [lindex $args 1] - } - 3 { - set w [lindex $args 1] - set ok [lindex $args 2] - } - default { - error "wrong number of args: console_done ?ignored? w ok" - } - } - - if {$ok} { - if {[winfo exists $w]} { - $w.m.s conf -background green -text {Success} - $w.ok conf -state normal - focus $w.ok - } - } else { - if {![winfo exists $w]} { - console_init $w - } - $w.m.s conf -background red -text {Error: Command Failed} - $w.ok conf -state normal - focus $w.ok - } - - array unset console_cr $w - array unset console_data $w -} - ###################################################################### ## ## ui commands @@ -4100,93 +1004,6 @@ proc do_gitk {revs} { } } -proc do_stats {} { - set fd [open "| git count-objects -v" r] - while {[gets $fd line] > 0} { - if {[regexp {^([^:]+): (\d+)$} $line _ name value]} { - set stats($name) $value - } - } - close $fd - - set packed_sz 0 - foreach p [glob -directory [gitdir objects pack] \ - -type f \ - -nocomplain -- *] { - incr packed_sz [file size $p] - } - if {$packed_sz > 0} { - set stats(size-pack) [expr {$packed_sz / 1024}] - } - - set w .stats_view - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Database Statistics} - pack $w.header -side top -fill x - - frame $w.buttons -border 1 - button $w.buttons.close -text Close \ - -default active \ - -command [list destroy $w] - button $w.buttons.gc -text {Compress Database} \ - -default normal \ - -command "destroy $w;do_gc" - pack $w.buttons.close -side right - pack $w.buttons.gc -side left - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - frame $w.stat -borderwidth 1 -relief solid - foreach s { - {count {Number of loose objects}} - {size {Disk space used by loose objects} { KiB}} - {in-pack {Number of packed objects}} - {packs {Number of packs}} - {size-pack {Disk space used by packed objects} { KiB}} - {prune-packable {Packed objects waiting for pruning}} - {garbage {Garbage files}} - } { - set name [lindex $s 0] - set label [lindex $s 1] - if {[catch {set value $stats($name)}]} continue - if {[llength $s] > 2} { - set value "$value[lindex $s 2]" - } - - label $w.stat.l_$name -text "$label:" -anchor w - label $w.stat.v_$name -text $value -anchor w - grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5} - } - pack $w.stat -pady 10 -padx 10 - - bind $w "grab $w; focus $w.buttons.close" - bind $w [list destroy $w] - bind $w [list destroy $w] - wm title $w "[appname] ([reponame]): Database Statistics" - tkwait window $w -} - -proc do_gc {} { - set w [new_console {gc} {Compressing the object database}] - console_chain { - {console_exec {git pack-refs --prune}} - {console_exec {git reflog expire --all}} - {console_exec {git repack -a -d -l}} - {console_exec {git rerere gc}} - } $w -} - -proc do_fsck_objects {} { - set w [new_console {fsck-objects} \ - {Verifying the object database with fsck-objects}] - set cmd [list git fsck-objects] - lappend cmd --full - lappend cmd --cache - lappend cmd --strict - console_exec $w $cmd console_done -} - set is_quitting 0 proc do_quit {} { @@ -4234,591 +1051,10 @@ proc do_rescan {} { rescan {set ui_status_value {Ready.}} } -proc unstage_helper {txt paths} { - global file_states current_diff_path - - if {![lock_index begin-update]} return - - set pathList [list] - set after {} - foreach path $paths { - switch -glob -- [lindex $file_states($path) 0] { - A? - - M? - - D? { - lappend pathList $path - if {$path eq $current_diff_path} { - set after {reshow_diff;} - } - } - } - } - if {$pathList eq {}} { - unlock_index - } else { - update_indexinfo \ - $txt \ - $pathList \ - [concat $after {set ui_status_value {Ready.}}] - } -} - -proc do_unstage_selection {} { - global current_diff_path selected_paths - - if {[array size selected_paths] > 0} { - unstage_helper \ - {Unstaging selected files from commit} \ - [array names selected_paths] - } elseif {$current_diff_path ne {}} { - unstage_helper \ - "Unstaging [short_path $current_diff_path] from commit" \ - [list $current_diff_path] - } -} - -proc add_helper {txt paths} { - global file_states current_diff_path - - if {![lock_index begin-update]} return - - set pathList [list] - set after {} - foreach path $paths { - switch -glob -- [lindex $file_states($path) 0] { - _O - - ?M - - ?D - - U? { - lappend pathList $path - if {$path eq $current_diff_path} { - set after {reshow_diff;} - } - } - } - } - if {$pathList eq {}} { - unlock_index - } else { - update_index \ - $txt \ - $pathList \ - [concat $after {set ui_status_value {Ready to commit.}}] - } -} - -proc do_add_selection {} { - global current_diff_path selected_paths - - if {[array size selected_paths] > 0} { - add_helper \ - {Adding selected files} \ - [array names selected_paths] - } elseif {$current_diff_path ne {}} { - add_helper \ - "Adding [short_path $current_diff_path]" \ - [list $current_diff_path] - } -} - -proc do_add_all {} { - global file_states - - set paths [list] - foreach path [array names file_states] { - switch -glob -- [lindex $file_states($path) 0] { - U? {continue} - ?M - - ?D {lappend paths $path} - } - } - add_helper {Adding all changed files} $paths -} - -proc revert_helper {txt paths} { - global file_states current_diff_path - - if {![lock_index begin-update]} return - - set pathList [list] - set after {} - foreach path $paths { - switch -glob -- [lindex $file_states($path) 0] { - U? {continue} - ?M - - ?D { - lappend pathList $path - if {$path eq $current_diff_path} { - set after {reshow_diff;} - } - } - } - } - - set n [llength $pathList] - if {$n == 0} { - unlock_index - return - } elseif {$n == 1} { - set s "[short_path [lindex $pathList]]" - } else { - set s "these $n files" - } - - set reply [tk_dialog \ - .confirm_revert \ - "[appname] ([reponame])" \ - "Revert changes in $s? - -Any unadded changes will be permanently lost by the revert." \ - question \ - 1 \ - {Do Nothing} \ - {Revert Changes} \ - ] - if {$reply == 1} { - checkout_index \ - $txt \ - $pathList \ - [concat $after {set ui_status_value {Ready.}}] - } else { - unlock_index - } -} - -proc do_revert_selection {} { - global current_diff_path selected_paths - - if {[array size selected_paths] > 0} { - revert_helper \ - {Reverting selected files} \ - [array names selected_paths] - } elseif {$current_diff_path ne {}} { - revert_helper \ - "Reverting [short_path $current_diff_path]" \ - [list $current_diff_path] - } -} - -proc do_signoff {} { - global ui_comm - - set me [committer_ident] - if {$me eq {}} return - - set sob "Signed-off-by: $me" - set last [$ui_comm get {end -1c linestart} {end -1c}] - if {$last ne $sob} { - $ui_comm edit separator - if {$last ne {} - && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} { - $ui_comm insert end "\n" - } - $ui_comm insert end "\n$sob" - $ui_comm edit separator - $ui_comm see end - } -} - -proc do_select_commit_type {} { - global commit_type selected_commit_type - - if {$selected_commit_type eq {new} - && [string match amend* $commit_type]} { - create_new_commit - } elseif {$selected_commit_type eq {amend} - && ![string match amend* $commit_type]} { - load_last_commit - - # The amend request was rejected... - # - if {![string match amend* $commit_type]} { - set selected_commit_type new - } - } -} - proc do_commit {} { commit_tree } -proc do_about {} { - global appvers copyright - global tcl_patchLevel tk_patchLevel - - set w .about_dialog - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text "About [appname]" \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.close -text {Close} \ - -default active \ - -command [list destroy $w] - pack $w.buttons.close -side right - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - label $w.desc \ - -text "git-gui - a graphical user interface for Git. -$copyright" \ - -padx 5 -pady 5 \ - -justify left \ - -anchor w \ - -borderwidth 1 \ - -relief solid - pack $w.desc -side top -fill x -padx 5 -pady 5 - - set v {} - append v "git-gui version $appvers\n" - append v "[git version]\n" - append v "\n" - if {$tcl_patchLevel eq $tk_patchLevel} { - append v "Tcl/Tk version $tcl_patchLevel" - } else { - append v "Tcl version $tcl_patchLevel" - append v ", Tk version $tk_patchLevel" - } - - label $w.vers \ - -text $v \ - -padx 5 -pady 5 \ - -justify left \ - -anchor w \ - -borderwidth 1 \ - -relief solid - pack $w.vers -side top -fill x -padx 5 -pady 5 - - menu $w.ctxm -tearoff 0 - $w.ctxm add command \ - -label {Copy} \ - -command " - clipboard clear - clipboard append -format STRING -type STRING -- \[$w.vers cget -text\] - " - - bind $w "grab $w; focus $w.buttons.close" - bind $w "destroy $w" - bind $w "destroy $w" - bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w" - wm title $w "About [appname]" - tkwait window $w -} - -proc do_options {} { - global repo_config global_config font_descs - global repo_config_new global_config_new - - array unset repo_config_new - array unset global_config_new - foreach name [array names repo_config] { - set repo_config_new($name) $repo_config($name) - } - load_config 1 - foreach name [array names repo_config] { - switch -- $name { - gui.diffcontext {continue} - } - set repo_config_new($name) $repo_config($name) - } - foreach name [array names global_config] { - set global_config_new($name) $global_config($name) - } - - set w .options_editor - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text "Options" \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.restore -text {Restore Defaults} \ - -default normal \ - -command do_restore_defaults - pack $w.buttons.restore -side left - button $w.buttons.save -text Save \ - -default active \ - -command [list do_save_config $w] - pack $w.buttons.save -side right - button $w.buttons.cancel -text {Cancel} \ - -default normal \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.repo -text "[reponame] Repository" - labelframe $w.global -text {Global (All Repositories)} - pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5 - pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 - - set optid 0 - foreach option { - {t user.name {User Name}} - {t user.email {Email Address}} - - {b merge.summary {Summarize Merge Commits}} - {i-1..5 merge.verbosity {Merge Verbosity}} - - {b gui.trustmtime {Trust File Modification Timestamps}} - {i-1..99 gui.diffcontext {Number of Diff Context Lines}} - {t gui.newbranchtemplate {New Branch Name Template}} - } { - set type [lindex $option 0] - set name [lindex $option 1] - set text [lindex $option 2] - incr optid - foreach f {repo global} { - switch -glob -- $type { - b { - checkbutton $w.$f.$optid -text $text \ - -variable ${f}_config_new($name) \ - -onvalue true \ - -offvalue false - pack $w.$f.$optid -side top -anchor w - } - i-* { - regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max - frame $w.$f.$optid - label $w.$f.$optid.l -text "$text:" - pack $w.$f.$optid.l -side left -anchor w -fill x - spinbox $w.$f.$optid.v \ - -textvariable ${f}_config_new($name) \ - -from $min \ - -to $max \ - -increment 1 \ - -width [expr {1 + [string length $max]}] - bind $w.$f.$optid.v {%W selection range 0 end} - pack $w.$f.$optid.v -side right -anchor e -padx 5 - pack $w.$f.$optid -side top -anchor w -fill x - } - t { - frame $w.$f.$optid - label $w.$f.$optid.l -text "$text:" - entry $w.$f.$optid.v \ - -borderwidth 1 \ - -relief sunken \ - -width 20 \ - -textvariable ${f}_config_new($name) - pack $w.$f.$optid.l -side left -anchor w - pack $w.$f.$optid.v -side left -anchor w \ - -fill x -expand 1 \ - -padx 5 - pack $w.$f.$optid -side top -anchor w -fill x - } - } - } - } - - set all_fonts [lsort [font families]] - foreach option $font_descs { - set name [lindex $option 0] - set font [lindex $option 1] - set text [lindex $option 2] - - set global_config_new(gui.$font^^family) \ - [font configure $font -family] - set global_config_new(gui.$font^^size) \ - [font configure $font -size] - - frame $w.global.$name - label $w.global.$name.l -text "$text:" - pack $w.global.$name.l -side left -anchor w -fill x - eval tk_optionMenu $w.global.$name.family \ - global_config_new(gui.$font^^family) \ - $all_fonts - spinbox $w.global.$name.size \ - -textvariable global_config_new(gui.$font^^size) \ - -from 2 -to 80 -increment 1 \ - -width 3 - bind $w.global.$name.size {%W selection range 0 end} - pack $w.global.$name.size -side right -anchor e - pack $w.global.$name.family -side right -anchor e - pack $w.global.$name -side top -anchor w -fill x - } - - bind $w "grab $w; focus $w.buttons.save" - bind $w "destroy $w" - bind $w [list do_save_config $w] - wm title $w "[appname] ([reponame]): Options" - tkwait window $w -} - -proc do_restore_defaults {} { - global font_descs default_config repo_config - global repo_config_new global_config_new - - foreach name [array names default_config] { - set repo_config_new($name) $default_config($name) - set global_config_new($name) $default_config($name) - } - - foreach option $font_descs { - set name [lindex $option 0] - set repo_config(gui.$name) $default_config(gui.$name) - } - apply_config - - foreach option $font_descs { - set name [lindex $option 0] - set font [lindex $option 1] - set global_config_new(gui.$font^^family) \ - [font configure $font -family] - set global_config_new(gui.$font^^size) \ - [font configure $font -size] - } -} - -proc do_save_config {w} { - if {[catch {save_config} err]} { - error_popup "Failed to completely save options:\n\n$err" - } - reshow_diff - destroy $w -} - -proc do_windows_shortcut {} { - global argv0 - - set fn [tk_getSaveFile \ - -parent . \ - -title "[appname] ([reponame]): Create Desktop Icon" \ - -initialfile "Git [reponame].bat"] - if {$fn != {}} { - if {[catch { - set fd [open $fn w] - puts $fd "@ECHO Entering [reponame]" - puts $fd "@ECHO Starting git-gui... please wait..." - puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%" - puts $fd "@SET GIT_DIR=[file normalize [gitdir]]" - puts -nonewline $fd "@\"[info nameofexecutable]\"" - puts $fd " \"[file normalize $argv0]\"" - close $fd - } err]} { - error_popup "Cannot write script:\n\n$err" - } - } -} - -proc do_cygwin_shortcut {} { - global argv0 - - if {[catch { - set desktop [exec cygpath \ - --windows \ - --absolute \ - --long-name \ - --desktop] - }]} { - set desktop . - } - set fn [tk_getSaveFile \ - -parent . \ - -title "[appname] ([reponame]): Create Desktop Icon" \ - -initialdir $desktop \ - -initialfile "Git [reponame].bat"] - if {$fn != {}} { - if {[catch { - set fd [open $fn w] - set sh [exec cygpath \ - --windows \ - --absolute \ - /bin/sh] - set me [exec cygpath \ - --unix \ - --absolute \ - $argv0] - set gd [exec cygpath \ - --unix \ - --absolute \ - [gitdir]] - set gw [exec cygpath \ - --windows \ - --absolute \ - [file dirname [gitdir]]] - regsub -all ' $me "'\\''" me - regsub -all ' $gd "'\\''" gd - puts $fd "@ECHO Entering $gw" - puts $fd "@ECHO Starting git-gui... please wait..." - puts -nonewline $fd "@\"$sh\" --login -c \"" - puts -nonewline $fd "GIT_DIR='$gd'" - puts -nonewline $fd " '$me'" - puts $fd "&\"" - close $fd - } err]} { - error_popup "Cannot write script:\n\n$err" - } - } -} - -proc do_macosx_app {} { - global argv0 env - - set fn [tk_getSaveFile \ - -parent . \ - -title "[appname] ([reponame]): Create Desktop Icon" \ - -initialdir [file join $env(HOME) Desktop] \ - -initialfile "Git [reponame].app"] - if {$fn != {}} { - if {[catch { - set Contents [file join $fn Contents] - set MacOS [file join $Contents MacOS] - set exe [file join $MacOS git-gui] - - file mkdir $MacOS - - set fd [open [file join $Contents Info.plist] w] - puts $fd { - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - git-gui - CFBundleIdentifier - org.spearce.git-gui - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - APPL - CFBundleSignature - ???? - CFBundleVersion - 1.0 - NSPrincipalClass - NSApplication - -} - close $fd - - set fd [open $exe w] - set gd [file normalize [gitdir]] - set ep [file normalize [gitexec]] - regsub -all ' $gd "'\\''" gd - regsub -all ' $ep "'\\''" ep - puts $fd "#!/bin/sh" - foreach name [array names env] { - if {[string match GIT_* $name]} { - regsub -all ' $env($name) "'\\''" v - puts $fd "export $name='$v'" - } - } - puts $fd "export PATH='$ep':\$PATH" - puts $fd "export GIT_DIR='$gd'" - puts $fd "exec [file normalize $argv0]" - close $fd - - file attributes $exe -permissions u+x,g+x,o+x - } err]} { - error_popup "Cannot write icon:\n\n$err" - } - } -} - proc toggle_or_diff {w x y} { global file_states file_lists current_diff_path ui_index ui_workdir global last_clicked selected_paths diff --git a/lib/blame.tcl b/lib/blame.tcl new file mode 100644 index 0000000000..c276fa9852 --- /dev/null +++ b/lib/blame.tcl @@ -0,0 +1,390 @@ +# git-gui blame viewer +# Copyright (C) 2006, 2007 Shawn Pearce + +proc show_blame {commit path} { + global next_browser_id blame_status blame_data + + if {[winfo ismapped .]} { + set w .browser[incr next_browser_id] + set tl $w + toplevel $w + } else { + set w {} + set tl . + } + set blame_status($w) {Loading current file content...} + + label $w.path -text "$commit:$path" \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken \ + -font font_uibold + pack $w.path -side top -fill x + + frame $w.out + text $w.out.loaded_t \ + -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 1 \ + -font font_diff + $w.out.loaded_t tag conf annotated -background grey + + text $w.out.linenumber_t \ + -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 5 \ + -font font_diff + $w.out.linenumber_t tag conf linenumber -justify right + + text $w.out.file_t \ + -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 80 \ + -xscrollcommand [list $w.out.sbx set] \ + -font font_diff + + scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview] + scrollbar $w.out.sby -orient v \ + -command [list scrollbar2many [list \ + $w.out.loaded_t \ + $w.out.linenumber_t \ + $w.out.file_t \ + ] yview] + grid \ + $w.out.linenumber_t \ + $w.out.loaded_t \ + $w.out.file_t \ + $w.out.sby \ + -sticky nsew + grid conf $w.out.sbx -column 2 -sticky we + grid columnconfigure $w.out 2 -weight 1 + grid rowconfigure $w.out 0 -weight 1 + pack $w.out -fill both -expand 1 + + label $w.status -textvariable blame_status($w) \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken + pack $w.status -side bottom -fill x + + frame $w.cm + text $w.cm.t \ + -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 10 \ + -width 80 \ + -xscrollcommand [list $w.cm.sbx set] \ + -yscrollcommand [list $w.cm.sby set] \ + -font font_diff + scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview] + scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview] + pack $w.cm.sby -side right -fill y + pack $w.cm.sbx -side bottom -fill x + pack $w.cm.t -expand 1 -fill both + pack $w.cm -side bottom -fill x + + menu $w.ctxm -tearoff 0 + $w.ctxm add command -label "Copy Commit" \ + -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY" + + foreach i [list \ + $w.out.loaded_t \ + $w.out.linenumber_t \ + $w.out.file_t] { + $i tag conf in_sel \ + -background [$i cget -foreground] \ + -foreground [$i cget -background] + $i conf -yscrollcommand \ + [list many2scrollbar [list \ + $w.out.loaded_t \ + $w.out.linenumber_t \ + $w.out.file_t \ + ] yview $w.out.sby] + bind $i " + blame_click {$w} \\ + $w.cm.t \\ + $w.out.linenumber_t \\ + $w.out.file_t \\ + $i @%x,%y + focus $i + " + bind_button3 $i " + set cursorX %x + set cursorY %y + set cursorW %W + tk_popup $w.ctxm %X %Y + " + } + + bind $w.cm.t "focus $w.cm.t" + bind $tl "focus $tl" + bind $tl " + array unset blame_status {$w} + array unset blame_data $w,* + " + wm title $tl "[appname] ([reponame]): File Viewer" + + set blame_data($w,commit_count) 0 + set blame_data($w,commit_list) {} + set blame_data($w,total_lines) 0 + set blame_data($w,blame_lines) 0 + set blame_data($w,highlight_commit) {} + set blame_data($w,highlight_line) -1 + + set cmd [list git cat-file blob "$commit:$path"] + set fd [open "| $cmd" r] + fconfigure $fd -blocking 0 -translation lf -encoding binary + fileevent $fd readable [list read_blame_catfile \ + $fd $w $commit $path \ + $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t] +} + +proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} { + global blame_status blame_data + + if {![winfo exists $w_file]} { + catch {close $fd} + return + } + + set n $blame_data($w,total_lines) + $w_load conf -state normal + $w_line conf -state normal + $w_file conf -state normal + while {[gets $fd line] >= 0} { + regsub "\r\$" $line {} line + incr n + $w_load insert end "\n" + $w_line insert end "$n\n" linenumber + $w_file insert end "$line\n" + } + $w_load conf -state disabled + $w_line conf -state disabled + $w_file conf -state disabled + set blame_data($w,total_lines) $n + + if {[eof $fd]} { + close $fd + blame_incremental_status $w + set cmd [list git blame -M -C --incremental] + lappend cmd $commit -- $path + set fd [open "| $cmd" r] + fconfigure $fd -blocking 0 -translation lf -encoding binary + fileevent $fd readable [list read_blame_incremental $fd $w \ + $w_load $w_cmit $w_line $w_file] + } +} + +proc read_blame_incremental {fd w w_load w_cmit w_line w_file} { + global blame_status blame_data + + if {![winfo exists $w_file]} { + catch {close $fd} + return + } + + while {[gets $fd line] >= 0} { + if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \ + cmit original_line final_line line_count]} { + set blame_data($w,commit) $cmit + set blame_data($w,original_line) $original_line + set blame_data($w,final_line) $final_line + set blame_data($w,line_count) $line_count + + if {[catch {set g $blame_data($w,$cmit,order)}]} { + $w_line tag conf g$cmit + $w_file tag conf g$cmit + $w_line tag raise in_sel + $w_file tag raise in_sel + $w_file tag raise sel + set blame_data($w,$cmit,order) $blame_data($w,commit_count) + incr blame_data($w,commit_count) + lappend blame_data($w,commit_list) $cmit + } + } elseif {[string match {filename *} $line]} { + set file [string range $line 9 end] + set n $blame_data($w,line_count) + set lno $blame_data($w,final_line) + set cmit $blame_data($w,commit) + + while {$n > 0} { + if {[catch {set g g$blame_data($w,line$lno,commit)}]} { + $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c" + } else { + $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c" + $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c" + } + + set blame_data($w,line$lno,commit) $cmit + set blame_data($w,line$lno,file) $file + $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c" + $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c" + + if {$blame_data($w,highlight_line) == -1} { + if {[lindex [$w_file yview] 0] == 0} { + $w_file see $lno.0 + blame_showcommit $w $w_cmit $w_line $w_file $lno + } + } elseif {$blame_data($w,highlight_line) == $lno} { + blame_showcommit $w $w_cmit $w_line $w_file $lno + } + + incr n -1 + incr lno + incr blame_data($w,blame_lines) + } + + set hc $blame_data($w,highlight_commit) + if {$hc ne {} + && [expr {$blame_data($w,$hc,order) + 1}] + == $blame_data($w,$cmit,order)} { + blame_showcommit $w $w_cmit $w_line $w_file \ + $blame_data($w,highlight_line) + } + } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} { + set blame_data($w,$blame_data($w,commit),$header) $data + } + } + + if {[eof $fd]} { + close $fd + set blame_status($w) {Annotation complete.} + } else { + blame_incremental_status $w + } +} + +proc blame_incremental_status {w} { + global blame_status blame_data + + set have $blame_data($w,blame_lines) + set total $blame_data($w,total_lines) + set pdone 0 + if {$total} {set pdone [expr {100 * $have / $total}]} + + set blame_status($w) [format \ + "Loading annotations... %i of %i lines annotated (%2i%%)" \ + $have $total $pdone] +} + +proc blame_click {w w_cmit w_line w_file cur_w pos} { + set lno [lindex [split [$cur_w index $pos] .] 0] + if {$lno eq {}} return + + $w_line tag remove in_sel 0.0 end + $w_file tag remove in_sel 0.0 end + $w_line tag add in_sel $lno.0 "$lno.0 + 1 line" + $w_file tag add in_sel $lno.0 "$lno.0 + 1 line" + + blame_showcommit $w $w_cmit $w_line $w_file $lno +} + +set blame_colors { + #ff4040 + #ff40ff + #4040ff +} + +proc blame_showcommit {w w_cmit w_line w_file lno} { + global blame_colors blame_data repo_config + + set cmit $blame_data($w,highlight_commit) + if {$cmit ne {}} { + set idx $blame_data($w,$cmit,order) + set i 0 + foreach c $blame_colors { + set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] + $w_line tag conf g$h -background white + $w_file tag conf g$h -background white + incr i + } + } + + $w_cmit conf -state normal + $w_cmit delete 0.0 end + if {[catch {set cmit $blame_data($w,line$lno,commit)}]} { + set cmit {} + $w_cmit insert end "Loading annotation..." + } else { + set idx $blame_data($w,$cmit,order) + set i 0 + foreach c $blame_colors { + set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] + $w_line tag conf g$h -background $c + $w_file tag conf g$h -background $c + incr i + } + + set author_name {} + set author_email {} + set author_time {} + catch {set author_name $blame_data($w,$cmit,author)} + catch {set author_email $blame_data($w,$cmit,author-mail)} + catch {set author_time [clock format $blame_data($w,$cmit,author-time)]} + + set committer_name {} + set committer_email {} + set committer_time {} + catch {set committer_name $blame_data($w,$cmit,committer)} + catch {set committer_email $blame_data($w,$cmit,committer-mail)} + catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]} + + if {[catch {set msg $blame_data($w,$cmit,message)}]} { + set msg {} + catch { + set fd [open "| git cat-file commit $cmit" r] + fconfigure $fd -encoding binary -translation lf + if {[catch {set enc $repo_config(i18n.commitencoding)}]} { + set enc utf-8 + } + while {[gets $fd line] > 0} { + if {[string match {encoding *} $line]} { + set enc [string tolower [string range $line 9 end]] + } + } + set msg [encoding convertfrom $enc [read $fd]] + set msg [string trim $msg] + close $fd + + set author_name [encoding convertfrom $enc $author_name] + set committer_name [encoding convertfrom $enc $committer_name] + + set blame_data($w,$cmit,author) $author_name + set blame_data($w,$cmit,committer) $committer_name + } + set blame_data($w,$cmit,message) $msg + } + + $w_cmit insert end "commit $cmit\n" + $w_cmit insert end "Author: $author_name $author_email $author_time\n" + $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n" + $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n" + $w_cmit insert end "\n" + $w_cmit insert end $msg + } + $w_cmit conf -state disabled + + set blame_data($w,highlight_line) $lno + set blame_data($w,highlight_commit) $cmit +} + +proc blame_copycommit {w i pos} { + global blame_data + set lno [lindex [split [$i index $pos] .] 0] + if {![catch {set commit $blame_data($w,line$lno,commit)}]} { + clipboard clear + clipboard append \ + -format STRING \ + -type STRING \ + -- $commit + } +} diff --git a/lib/branch.tcl b/lib/branch.tcl new file mode 100644 index 0000000000..caaee5cf17 --- /dev/null +++ b/lib/branch.tcl @@ -0,0 +1,572 @@ +# git-gui branch (create/delete) support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc load_all_heads {} { + global all_heads + + set all_heads [list] + set fd [open "| git for-each-ref --format=%(refname) refs/heads" r] + while {[gets $fd line] > 0} { + if {[is_tracking_branch $line]} continue + if {![regsub ^refs/heads/ $line {} name]} continue + lappend all_heads $name + } + close $fd + + set all_heads [lsort $all_heads] +} + +proc load_all_tags {} { + set all_tags [list] + set fd [open "| git for-each-ref --format=%(refname) refs/tags" r] + while {[gets $fd line] > 0} { + if {![regsub ^refs/tags/ $line {} name]} continue + lappend all_tags $name + } + close $fd + + return [lsort $all_tags] +} + +proc populate_branch_menu {} { + global all_heads disable_on_lock + + set m .mbar.branch + set last [$m index last] + for {set i 0} {$i <= $last} {incr i} { + if {[$m type $i] eq {separator}} { + $m delete $i last + set new_dol [list] + foreach a $disable_on_lock { + if {[lindex $a 0] ne $m || [lindex $a 2] < $i} { + lappend new_dol $a + } + } + set disable_on_lock $new_dol + break + } + } + + if {$all_heads ne {}} { + $m add separator + } + foreach b $all_heads { + $m add radiobutton \ + -label $b \ + -command [list switch_branch $b] \ + -variable current_branch \ + -value $b + lappend disable_on_lock \ + [list $m entryconf [$m index last] -state] + } +} + +proc do_create_branch_action {w} { + global all_heads null_sha1 repo_config + global create_branch_checkout create_branch_revtype + global create_branch_head create_branch_trackinghead + global create_branch_name create_branch_revexp + global create_branch_tag + + set newbranch $create_branch_name + if {$newbranch eq {} + || $newbranch eq $repo_config(gui.newbranchtemplate)} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Please supply a branch name." + focus $w.desc.name_t + return + } + if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Branch '$newbranch' already exists." + focus $w.desc.name_t + return + } + if {[catch {git check-ref-format "heads/$newbranch"}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "We do not like '$newbranch' as a branch name." + focus $w.desc.name_t + return + } + + set rev {} + switch -- $create_branch_revtype { + head {set rev $create_branch_head} + tracking {set rev $create_branch_trackinghead} + tag {set rev $create_branch_tag} + expression {set rev $create_branch_revexp} + } + if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Invalid starting revision: $rev" + return + } + if {[catch { + git update-ref \ + -m "branch: Created from $rev" \ + "refs/heads/$newbranch" \ + $cmt \ + $null_sha1 + } err]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Failed to create '$newbranch'.\n\n$err" + return + } + + lappend all_heads $newbranch + set all_heads [lsort $all_heads] + populate_branch_menu + destroy $w + if {$create_branch_checkout} { + switch_branch $newbranch + } +} + +proc radio_selector {varname value args} { + upvar #0 $varname var + set var $value +} + +trace add variable create_branch_head write \ + [list radio_selector create_branch_revtype head] +trace add variable create_branch_trackinghead write \ + [list radio_selector create_branch_revtype tracking] +trace add variable create_branch_tag write \ + [list radio_selector create_branch_revtype tag] + +trace add variable delete_branch_head write \ + [list radio_selector delete_branch_checktype head] +trace add variable delete_branch_trackinghead write \ + [list radio_selector delete_branch_checktype tracking] + +proc do_create_branch {} { + global all_heads current_branch repo_config + global create_branch_checkout create_branch_revtype + global create_branch_head create_branch_trackinghead + global create_branch_name create_branch_revexp + global create_branch_tag + + set w .branch_editor + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text {Create New Branch} \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.create -text Create \ + -default active \ + -command [list do_create_branch_action $w] + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.desc -text {Branch Description} + label $w.desc.name_l -text {Name:} + entry $w.desc.name_t \ + -borderwidth 1 \ + -relief sunken \ + -width 40 \ + -textvariable create_branch_name \ + -validate key \ + -validatecommand { + if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0} + return 1 + } + grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5} + grid columnconfigure $w.desc 1 -weight 1 + pack $w.desc -anchor nw -fill x -pady 5 -padx 5 + + labelframe $w.from -text {Starting Revision} + radiobutton $w.from.head_r \ + -text {Local Branch:} \ + -value head \ + -variable create_branch_revtype + eval tk_optionMenu $w.from.head_m create_branch_head $all_heads + grid $w.from.head_r $w.from.head_m -sticky w + set all_trackings [all_tracking_branches] + if {$all_trackings ne {}} { + set create_branch_trackinghead [lindex $all_trackings 0] + radiobutton $w.from.tracking_r \ + -text {Tracking Branch:} \ + -value tracking \ + -variable create_branch_revtype + eval tk_optionMenu $w.from.tracking_m \ + create_branch_trackinghead \ + $all_trackings + grid $w.from.tracking_r $w.from.tracking_m -sticky w + } + set all_tags [load_all_tags] + if {$all_tags ne {}} { + set create_branch_tag [lindex $all_tags 0] + radiobutton $w.from.tag_r \ + -text {Tag:} \ + -value tag \ + -variable create_branch_revtype + eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags + grid $w.from.tag_r $w.from.tag_m -sticky w + } + radiobutton $w.from.exp_r \ + -text {Revision Expression:} \ + -value expression \ + -variable create_branch_revtype + entry $w.from.exp_t \ + -borderwidth 1 \ + -relief sunken \ + -width 50 \ + -textvariable create_branch_revexp \ + -validate key \ + -validatecommand { + if {%d == 1 && [regexp {\s} %S]} {return 0} + if {%d == 1 && [string length %S] > 0} { + set create_branch_revtype expression + } + return 1 + } + grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5} + grid columnconfigure $w.from 1 -weight 1 + pack $w.from -anchor nw -fill x -pady 5 -padx 5 + + labelframe $w.postActions -text {Post Creation Actions} + checkbutton $w.postActions.checkout \ + -text {Checkout after creation} \ + -variable create_branch_checkout + pack $w.postActions.checkout -anchor nw + pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 + + set create_branch_checkout 1 + set create_branch_head $current_branch + set create_branch_revtype head + set create_branch_name $repo_config(gui.newbranchtemplate) + set create_branch_revexp {} + + bind $w " + grab $w + $w.desc.name_t icursor end + focus $w.desc.name_t + " + bind $w "destroy $w" + bind $w "do_create_branch_action $w;break" + wm title $w "[appname] ([reponame]): Create Branch" + tkwait window $w +} + +proc do_delete_branch_action {w} { + global all_heads + global delete_branch_checktype delete_branch_head delete_branch_trackinghead + + set check_rev {} + switch -- $delete_branch_checktype { + head {set check_rev $delete_branch_head} + tracking {set check_rev $delete_branch_trackinghead} + always {set check_rev {:none}} + } + if {$check_rev eq {:none}} { + set check_cmt {} + } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Invalid check revision: $check_rev" + return + } + + set to_delete [list] + set not_merged [list] + foreach i [$w.list.l curselection] { + set b [$w.list.l get $i] + if {[catch {set o [git rev-parse --verify $b]}]} continue + if {$check_cmt ne {}} { + if {$b eq $check_rev} continue + if {[catch {set m [git merge-base $o $check_cmt]}]} continue + if {$o ne $m} { + lappend not_merged $b + continue + } + } + lappend to_delete [list $b $o] + } + if {$not_merged ne {}} { + set msg "The following branches are not completely merged into $check_rev: + + - [join $not_merged "\n - "]" + tk_messageBox \ + -icon info \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message $msg + } + if {$to_delete eq {}} return + if {$delete_branch_checktype eq {always}} { + set msg {Recovering deleted branches is difficult. + +Delete the selected branches?} + if {[tk_messageBox \ + -icon warning \ + -type yesno \ + -title [wm title $w] \ + -parent $w \ + -message $msg] ne yes} { + return + } + } + + set failed {} + foreach i $to_delete { + set b [lindex $i 0] + set o [lindex $i 1] + if {[catch {git update-ref -d "refs/heads/$b" $o} err]} { + append failed " - $b: $err\n" + } else { + set x [lsearch -sorted -exact $all_heads $b] + if {$x >= 0} { + set all_heads [lreplace $all_heads $x $x] + } + } + } + + if {$failed ne {}} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Failed to delete branches:\n$failed" + } + + set all_heads [lsort $all_heads] + populate_branch_menu + destroy $w +} + +proc do_delete_branch {} { + global all_heads tracking_branches current_branch + global delete_branch_checktype delete_branch_head delete_branch_trackinghead + + set w .branch_editor + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text {Delete Local Branch} \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.create -text Delete \ + -command [list do_delete_branch_action $w] + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.list -text {Local Branches} + listbox $w.list.l \ + -height 10 \ + -width 70 \ + -selectmode extended \ + -yscrollcommand [list $w.list.sby set] + foreach h $all_heads { + if {$h ne $current_branch} { + $w.list.l insert end $h + } + } + scrollbar $w.list.sby -command [list $w.list.l yview] + pack $w.list.sby -side right -fill y + pack $w.list.l -side left -fill both -expand 1 + pack $w.list -fill both -expand 1 -pady 5 -padx 5 + + labelframe $w.validate -text {Delete Only If} + radiobutton $w.validate.head_r \ + -text {Merged Into Local Branch:} \ + -value head \ + -variable delete_branch_checktype + eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads + grid $w.validate.head_r $w.validate.head_m -sticky w + set all_trackings [all_tracking_branches] + if {$all_trackings ne {}} { + set delete_branch_trackinghead [lindex $all_trackings 0] + radiobutton $w.validate.tracking_r \ + -text {Merged Into Tracking Branch:} \ + -value tracking \ + -variable delete_branch_checktype + eval tk_optionMenu $w.validate.tracking_m \ + delete_branch_trackinghead \ + $all_trackings + grid $w.validate.tracking_r $w.validate.tracking_m -sticky w + } + radiobutton $w.validate.always_r \ + -text {Always (Do not perform merge checks)} \ + -value always \ + -variable delete_branch_checktype + grid $w.validate.always_r -columnspan 2 -sticky w + grid columnconfigure $w.validate 1 -weight 1 + pack $w.validate -anchor nw -fill x -pady 5 -padx 5 + + set delete_branch_head $current_branch + set delete_branch_checktype head + + bind $w "grab $w; focus $w" + bind $w "destroy $w" + wm title $w "[appname] ([reponame]): Delete Branch" + tkwait window $w +} + +proc switch_branch {new_branch} { + global HEAD commit_type current_branch repo_config + + if {![lock_index switch]} return + + # -- Our in memory state should match the repository. + # + repository_state curType curHEAD curMERGE_HEAD + if {[string match amend* $commit_type] + && $curType eq {normal} + && $curHEAD eq $HEAD} { + } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { + info_popup {Last scanned state does not match repository state. + +Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed. + +The rescan will be automatically started now. +} + unlock_index + rescan {set ui_status_value {Ready.}} + return + } + + # -- Don't do a pointless switch. + # + if {$current_branch eq $new_branch} { + unlock_index + return + } + + if {$repo_config(gui.trustmtime) eq {true}} { + switch_branch_stage2 {} $new_branch + } else { + set ui_status_value {Refreshing file status...} + set cmd [list git update-index] + lappend cmd -q + lappend cmd --unmerged + lappend cmd --ignore-missing + lappend cmd --refresh + set fd_rf [open "| $cmd" r] + fconfigure $fd_rf -blocking 0 -translation binary + fileevent $fd_rf readable \ + [list switch_branch_stage2 $fd_rf $new_branch] + } +} + +proc switch_branch_stage2 {fd_rf new_branch} { + global ui_status_value HEAD + + if {$fd_rf ne {}} { + read $fd_rf + if {![eof $fd_rf]} return + close $fd_rf + } + + set ui_status_value "Updating working directory to '$new_branch'..." + set cmd [list git read-tree] + lappend cmd -m + lappend cmd -u + lappend cmd --exclude-per-directory=.gitignore + lappend cmd $HEAD + lappend cmd $new_branch + set fd_rt [open "| $cmd" r] + fconfigure $fd_rt -blocking 0 -translation binary + fileevent $fd_rt readable \ + [list switch_branch_readtree_wait $fd_rt $new_branch] +} + +proc switch_branch_readtree_wait {fd_rt new_branch} { + global selected_commit_type commit_type HEAD MERGE_HEAD PARENT + global current_branch + global ui_comm ui_status_value + + # -- We never get interesting output on stdout; only stderr. + # + read $fd_rt + fconfigure $fd_rt -blocking 1 + if {![eof $fd_rt]} { + fconfigure $fd_rt -blocking 0 + return + } + + # -- The working directory wasn't in sync with the index and + # we'd have to overwrite something to make the switch. A + # merge is required. + # + if {[catch {close $fd_rt} err]} { + regsub {^fatal: } $err {} err + warn_popup "File level merge required. + +$err + +Staying on branch '$current_branch'." + set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)." + unlock_index + return + } + + # -- Update the symbolic ref. Core git doesn't even check for failure + # here, it Just Works(tm). If it doesn't we are in some really ugly + # state that is difficult to recover from within git-gui. + # + if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} { + error_popup "Failed to set current branch. + +This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file. + +This should not have occurred. [appname] will now close and give up. + +$err" + do_quit + return + } + + # -- Update our repository state. If we were previously in amend mode + # we need to toss the current buffer and do a full rescan to update + # our file lists. If we weren't in amend mode our file lists are + # accurate and we can avoid the rescan. + # + unlock_index + set selected_commit_type new + if {[string match amend* $commit_type]} { + $ui_comm delete 0.0 end + $ui_comm edit reset + $ui_comm edit modified false + rescan {set ui_status_value "Checked out branch '$current_branch'."} + } else { + repository_state commit_type HEAD MERGE_HEAD + set PARENT $HEAD + set ui_status_value "Checked out branch '$current_branch'." + } +} diff --git a/lib/browser.tcl b/lib/browser.tcl new file mode 100644 index 0000000000..631859ae75 --- /dev/null +++ b/lib/browser.tcl @@ -0,0 +1,263 @@ +# git-gui tree browser +# Copyright (C) 2006, 2007 Shawn Pearce + +set next_browser_id 0 + +proc new_browser {commit} { + global next_browser_id cursor_ptr M1B + global browser_commit browser_status browser_stack browser_path browser_busy + + if {[winfo ismapped .]} { + set w .browser[incr next_browser_id] + set tl $w + toplevel $w + } else { + set w {} + set tl . + } + set w_list $w.list.l + set browser_commit($w_list) $commit + set browser_status($w_list) {Starting...} + set browser_stack($w_list) {} + set browser_path($w_list) $browser_commit($w_list): + set browser_busy($w_list) 1 + + label $w.path -textvariable browser_path($w_list) \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken \ + -font font_uibold + pack $w.path -anchor w -side top -fill x + + frame $w.list + text $w_list -background white -borderwidth 0 \ + -cursor $cursor_ptr \ + -state disabled \ + -wrap none \ + -height 20 \ + -width 70 \ + -xscrollcommand [list $w.list.sbx set] \ + -yscrollcommand [list $w.list.sby set] + $w_list tag conf in_sel \ + -background [$w_list cget -foreground] \ + -foreground [$w_list cget -background] + scrollbar $w.list.sbx -orient h -command [list $w_list xview] + scrollbar $w.list.sby -orient v -command [list $w_list yview] + pack $w.list.sbx -side bottom -fill x + pack $w.list.sby -side right -fill y + pack $w_list -side left -fill both -expand 1 + pack $w.list -side top -fill both -expand 1 + + label $w.status -textvariable browser_status($w_list) \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken + pack $w.status -anchor w -side bottom -fill x + + bind $w_list "browser_click 0 $w_list @%x,%y;break" + bind $w_list "browser_click 1 $w_list @%x,%y;break" + bind $w_list <$M1B-Up> "browser_parent $w_list;break" + bind $w_list <$M1B-Left> "browser_parent $w_list;break" + bind $w_list "browser_move -1 $w_list;break" + bind $w_list "browser_move 1 $w_list;break" + bind $w_list <$M1B-Right> "browser_enter $w_list;break" + bind $w_list "browser_enter $w_list;break" + bind $w_list "browser_page -1 $w_list;break" + bind $w_list "browser_page 1 $w_list;break" + bind $w_list break + bind $w_list break + + bind $tl "focus $w" + bind $tl " + array unset browser_buffer $w_list + array unset browser_files $w_list + array unset browser_status $w_list + array unset browser_stack $w_list + array unset browser_path $w_list + array unset browser_commit $w_list + array unset browser_busy $w_list + " + wm title $tl "[appname] ([reponame]): File Browser" + ls_tree $w_list $browser_commit($w_list) {} +} + +proc browser_move {dir w} { + global browser_files browser_busy + + if {$browser_busy($w)} return + set lno [lindex [split [$w index in_sel.first] .] 0] + incr lno $dir + if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { + $w tag remove in_sel 0.0 end + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + $w see $lno.0 + } +} + +proc browser_page {dir w} { + global browser_files browser_busy + + if {$browser_busy($w)} return + $w yview scroll $dir pages + set lno [expr {int( + [lindex [$w yview] 0] + * [llength $browser_files($w)] + + 1)}] + if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { + $w tag remove in_sel 0.0 end + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + $w see $lno.0 + } +} + +proc browser_parent {w} { + global browser_files browser_status browser_path + global browser_stack browser_busy + + if {$browser_busy($w)} return + set info [lindex $browser_files($w) 0] + if {[lindex $info 0] eq {parent}} { + set parent [lindex $browser_stack($w) end-1] + set browser_stack($w) [lrange $browser_stack($w) 0 end-2] + if {$browser_stack($w) eq {}} { + regsub {:.*$} $browser_path($w) {:} browser_path($w) + } else { + regsub {/[^/]+$} $browser_path($w) {} browser_path($w) + } + set browser_status($w) "Loading $browser_path($w)..." + ls_tree $w [lindex $parent 0] [lindex $parent 1] + } +} + +proc browser_enter {w} { + global browser_files browser_status browser_path + global browser_commit browser_stack browser_busy + + if {$browser_busy($w)} return + set lno [lindex [split [$w index in_sel.first] .] 0] + set info [lindex $browser_files($w) [expr {$lno - 1}]] + if {$info ne {}} { + switch -- [lindex $info 0] { + parent { + browser_parent $w + } + tree { + set name [lindex $info 2] + set escn [escape_path $name] + set browser_status($w) "Loading $escn..." + append browser_path($w) $escn + ls_tree $w [lindex $info 1] $name + } + blob { + set name [lindex $info 2] + set p {} + foreach n $browser_stack($w) { + append p [lindex $n 1] + } + append p $name + show_blame $browser_commit($w) $p + } + } + } +} + +proc browser_click {was_double_click w pos} { + global browser_files browser_busy + + if {$browser_busy($w)} return + set lno [lindex [split [$w index $pos] .] 0] + focus $w + + if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { + $w tag remove in_sel 0.0 end + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + if {$was_double_click} { + browser_enter $w + } + } +} + +proc ls_tree {w tree_id name} { + global browser_buffer browser_files browser_stack browser_busy + + set browser_buffer($w) {} + set browser_files($w) {} + set browser_busy($w) 1 + + $w conf -state normal + $w tag remove in_sel 0.0 end + $w delete 0.0 end + if {$browser_stack($w) ne {}} { + $w image create end \ + -align center -padx 5 -pady 1 \ + -name icon0 \ + -image file_uplevel + $w insert end {[Up To Parent]} + lappend browser_files($w) parent + } + lappend browser_stack($w) [list $tree_id $name] + $w conf -state disabled + + set cmd [list git ls-tree -z $tree_id] + set fd [open "| $cmd" r] + fconfigure $fd -blocking 0 -translation binary -encoding binary + fileevent $fd readable [list read_ls_tree $fd $w] +} + +proc read_ls_tree {fd w} { + global browser_buffer browser_files browser_status browser_busy + + if {![winfo exists $w]} { + catch {close $fd} + return + } + + append browser_buffer($w) [read $fd] + set pck [split $browser_buffer($w) "\0"] + set browser_buffer($w) [lindex $pck end] + + set n [llength $browser_files($w)] + $w conf -state normal + foreach p [lrange $pck 0 end-1] { + set info [split $p "\t"] + set path [lindex $info 1] + set info [split [lindex $info 0] { }] + set type [lindex $info 1] + set object [lindex $info 2] + + switch -- $type { + blob { + set image file_mod + } + tree { + set image file_dir + append path / + } + default { + set image file_question + } + } + + if {$n > 0} {$w insert end "\n"} + $w image create end \ + -align center -padx 5 -pady 1 \ + -name icon[incr n] \ + -image $image + $w insert end [escape_path $path] + lappend browser_files($w) [list $type $object $path] + } + $w conf -state disabled + + if {[eof $fd]} { + close $fd + set browser_status($w) Ready. + set browser_busy($w) 0 + array unset browser_buffer $w + if {$n > 0} { + $w tag add in_sel 1.0 2.0 + focus -force $w + } + } +} diff --git a/lib/commit.tcl b/lib/commit.tcl new file mode 100644 index 0000000000..f9791f64db --- /dev/null +++ b/lib/commit.tcl @@ -0,0 +1,410 @@ +# git-gui misc. commit reading/writing support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc load_last_commit {} { + global HEAD PARENT MERGE_HEAD commit_type ui_comm + global repo_config + + if {[llength $PARENT] == 0} { + error_popup {There is nothing to amend. + +You are about to create the initial commit. There is no commit before this to amend. +} + return + } + + repository_state curType curHEAD curMERGE_HEAD + if {$curType eq {merge}} { + error_popup {Cannot amend while merging. + +You are currently in the middle of a merge that has not been fully completed. You cannot amend the prior commit unless you first abort the current merge activity. +} + return + } + + set msg {} + set parents [list] + if {[catch { + set fd [open "| git cat-file commit $curHEAD" r] + fconfigure $fd -encoding binary -translation lf + if {[catch {set enc $repo_config(i18n.commitencoding)}]} { + set enc utf-8 + } + while {[gets $fd line] > 0} { + if {[string match {parent *} $line]} { + lappend parents [string range $line 7 end] + } elseif {[string match {encoding *} $line]} { + set enc [string tolower [string range $line 9 end]] + } + } + set msg [encoding convertfrom $enc [read $fd]] + set msg [string trim $msg] + close $fd + } err]} { + error_popup "Error loading commit data for amend:\n\n$err" + return + } + + set HEAD $curHEAD + set PARENT $parents + set MERGE_HEAD [list] + switch -- [llength $parents] { + 0 {set commit_type amend-initial} + 1 {set commit_type amend} + default {set commit_type amend-merge} + } + + $ui_comm delete 0.0 end + $ui_comm insert end $msg + $ui_comm edit reset + $ui_comm edit modified false + rescan {set ui_status_value {Ready.}} +} + +set GIT_COMMITTER_IDENT {} + +proc committer_ident {} { + global GIT_COMMITTER_IDENT + + if {$GIT_COMMITTER_IDENT eq {}} { + if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} { + error_popup "Unable to obtain your identity:\n\n$err" + return {} + } + if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \ + $me me GIT_COMMITTER_IDENT]} { + error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me" + return {} + } + } + + return $GIT_COMMITTER_IDENT +} + +proc do_signoff {} { + global ui_comm + + set me [committer_ident] + if {$me eq {}} return + + set sob "Signed-off-by: $me" + set last [$ui_comm get {end -1c linestart} {end -1c}] + if {$last ne $sob} { + $ui_comm edit separator + if {$last ne {} + && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} { + $ui_comm insert end "\n" + } + $ui_comm insert end "\n$sob" + $ui_comm edit separator + $ui_comm see end + } +} + +proc create_new_commit {} { + global commit_type ui_comm + + set commit_type normal + $ui_comm delete 0.0 end + $ui_comm edit reset + $ui_comm edit modified false + rescan {set ui_status_value {Ready.}} +} + +proc commit_tree {} { + global HEAD commit_type file_states ui_comm repo_config + global ui_status_value pch_error + + if {[committer_ident] eq {}} return + if {![lock_index update]} return + + # -- Our in memory state should match the repository. + # + repository_state curType curHEAD curMERGE_HEAD + if {[string match amend* $commit_type] + && $curType eq {normal} + && $curHEAD eq $HEAD} { + } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { + info_popup {Last scanned state does not match repository state. + +Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created. + +The rescan will be automatically started now. +} + unlock_index + rescan {set ui_status_value {Ready.}} + return + } + + # -- At least one file should differ in the index. + # + set files_ready 0 + foreach path [array names file_states] { + switch -glob -- [lindex $file_states($path) 0] { + _? {continue} + A? - + D? - + M? {set files_ready 1} + U? { + error_popup "Unmerged files cannot be committed. + +File [short_path $path] has merge conflicts. You must resolve them and add the file before committing. +" + unlock_index + return + } + default { + error_popup "Unknown file state [lindex $s 0] detected. + +File [short_path $path] cannot be committed by this program. +" + } + } + } + if {!$files_ready && ![string match *merge $curType]} { + info_popup {No changes to commit. + +You must add at least 1 file before you can commit. +} + unlock_index + return + } + + # -- A message is required. + # + set msg [string trim [$ui_comm get 1.0 end]] + regsub -all -line {[ \t\r]+$} $msg {} msg + if {$msg eq {}} { + error_popup {Please supply a commit message. + +A good commit message has the following format: + +- First line: Describe in one sentance what you did. +- Second line: Blank +- Remaining lines: Describe why this change is good. +} + unlock_index + return + } + + # -- Run the pre-commit hook. + # + set pchook [gitdir hooks pre-commit] + + # On Cygwin [file executable] might lie so we need to ask + # the shell if the hook is executable. Yes that's annoying. + # + if {[is_Cygwin] && [file isfile $pchook]} { + set pchook [list sh -c [concat \ + "if test -x \"$pchook\";" \ + "then exec \"$pchook\" 2>&1;" \ + "fi"]] + } elseif {[file executable $pchook]} { + set pchook [list $pchook |& cat] + } else { + commit_writetree $curHEAD $msg + return + } + + set ui_status_value {Calling pre-commit hook...} + set pch_error {} + set fd_ph [open "| $pchook" r] + fconfigure $fd_ph -blocking 0 -translation binary + fileevent $fd_ph readable \ + [list commit_prehook_wait $fd_ph $curHEAD $msg] +} + +proc commit_prehook_wait {fd_ph curHEAD msg} { + global pch_error ui_status_value + + append pch_error [read $fd_ph] + fconfigure $fd_ph -blocking 1 + if {[eof $fd_ph]} { + if {[catch {close $fd_ph}]} { + set ui_status_value {Commit declined by pre-commit hook.} + hook_failed_popup pre-commit $pch_error + unlock_index + } else { + commit_writetree $curHEAD $msg + } + set pch_error {} + return + } + fconfigure $fd_ph -blocking 0 +} + +proc commit_writetree {curHEAD msg} { + global ui_status_value + + set ui_status_value {Committing changes...} + set fd_wt [open "| git write-tree" r] + fileevent $fd_wt readable \ + [list commit_committree $fd_wt $curHEAD $msg] +} + +proc commit_committree {fd_wt curHEAD msg} { + global HEAD PARENT MERGE_HEAD commit_type + global all_heads current_branch + global ui_status_value ui_comm selected_commit_type + global file_states selected_paths rescan_active + global repo_config + + gets $fd_wt tree_id + if {$tree_id eq {} || [catch {close $fd_wt} err]} { + error_popup "write-tree failed:\n\n$err" + set ui_status_value {Commit failed.} + unlock_index + return + } + + # -- Verify this wasn't an empty change. + # + if {$commit_type eq {normal}} { + set old_tree [git rev-parse "$PARENT^{tree}"] + if {$tree_id eq $old_tree} { + info_popup {No changes to commit. + +No files were modified by this commit and it was not a merge commit. + +A rescan will be automatically started now. +} + unlock_index + rescan {set ui_status_value {No changes to commit.}} + return + } + } + + # -- Build the message. + # + set msg_p [gitdir COMMIT_EDITMSG] + set msg_wt [open $msg_p w] + if {[catch {set enc $repo_config(i18n.commitencoding)}]} { + set enc utf-8 + } + fconfigure $msg_wt -encoding binary -translation binary + puts -nonewline $msg_wt [encoding convertto $enc $msg] + close $msg_wt + + # -- Create the commit. + # + set cmd [list commit-tree $tree_id] + foreach p [concat $PARENT $MERGE_HEAD] { + lappend cmd -p $p + } + lappend cmd <$msg_p + if {[catch {set cmt_id [eval git $cmd]} err]} { + error_popup "commit-tree failed:\n\n$err" + set ui_status_value {Commit failed.} + unlock_index + return + } + + # -- Update the HEAD ref. + # + set reflogm commit + if {$commit_type ne {normal}} { + append reflogm " ($commit_type)" + } + set i [string first "\n" $msg] + if {$i >= 0} { + set subject [string range $msg 0 [expr {$i - 1}]] + } else { + set subject $msg + } + append reflogm {: } $subject + if {[catch { + git update-ref -m $reflogm HEAD $cmt_id $curHEAD + } err]} { + error_popup "update-ref failed:\n\n$err" + set ui_status_value {Commit failed.} + unlock_index + return + } + + # -- Cleanup after ourselves. + # + catch {file delete $msg_p} + catch {file delete [gitdir MERGE_HEAD]} + catch {file delete [gitdir MERGE_MSG]} + catch {file delete [gitdir SQUASH_MSG]} + catch {file delete [gitdir GITGUI_MSG]} + + # -- Let rerere do its thing. + # + if {[file isdirectory [gitdir rr-cache]]} { + catch {git rerere} + } + + # -- Run the post-commit hook. + # + set pchook [gitdir hooks post-commit] + if {[is_Cygwin] && [file isfile $pchook]} { + set pchook [list sh -c [concat \ + "if test -x \"$pchook\";" \ + "then exec \"$pchook\";" \ + "fi"]] + } elseif {![file executable $pchook]} { + set pchook {} + } + if {$pchook ne {}} { + catch {exec $pchook &} + } + + $ui_comm delete 0.0 end + $ui_comm edit reset + $ui_comm edit modified false + + if {[is_enabled singlecommit]} do_quit + + # -- Make sure our current branch exists. + # + if {$commit_type eq {initial}} { + lappend all_heads $current_branch + set all_heads [lsort -unique $all_heads] + populate_branch_menu + } + + # -- Update in memory status + # + set selected_commit_type new + set commit_type normal + set HEAD $cmt_id + set PARENT $cmt_id + set MERGE_HEAD [list] + + foreach path [array names file_states] { + set s $file_states($path) + set m [lindex $s 0] + switch -glob -- $m { + _O - + _M - + _D {continue} + __ - + A_ - + M_ - + D_ { + unset file_states($path) + catch {unset selected_paths($path)} + } + DO { + set file_states($path) [list _O [lindex $s 1] {} {}] + } + AM - + AD - + MM - + MD { + set file_states($path) [list \ + _[string index $m 1] \ + [lindex $s 1] \ + [lindex $s 3] \ + {}] + } + } + } + + display_all_files + unlock_index + reshow_diff + set ui_status_value \ + "Created commit [string range $cmt_id 0 7]: $subject" +} diff --git a/lib/console.tcl b/lib/console.tcl new file mode 100644 index 0000000000..e40ec9639b --- /dev/null +++ b/lib/console.tcl @@ -0,0 +1,185 @@ +# git-gui console support +# Copyright (C) 2006, 2007 Shawn Pearce + +set next_console_id 0 + +proc new_console {short_title long_title} { + global next_console_id console_data + set w .console[incr next_console_id] + set console_data($w) [list $short_title $long_title] + return [console_init $w] +} + +proc console_init {w} { + global console_cr console_data M1B + + set console_cr($w) 1.0 + toplevel $w + frame $w.m + label $w.m.l1 -text "[lindex $console_data($w) 1]:" \ + -anchor w \ + -justify left \ + -font font_uibold + text $w.m.t \ + -background white -borderwidth 1 \ + -relief sunken \ + -width 80 -height 10 \ + -font font_diff \ + -state disabled \ + -yscrollcommand [list $w.m.sby set] + label $w.m.s -text {Working... please wait...} \ + -anchor w \ + -justify left \ + -font font_uibold + scrollbar $w.m.sby -command [list $w.m.t yview] + pack $w.m.l1 -side top -fill x + pack $w.m.s -side bottom -fill x + pack $w.m.sby -side right -fill y + pack $w.m.t -side left -fill both -expand 1 + pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 + + menu $w.ctxm -tearoff 0 + $w.ctxm add command -label "Copy" \ + -command "tk_textCopy $w.m.t" + $w.ctxm add command -label "Select All" \ + -command "focus $w.m.t;$w.m.t tag add sel 0.0 end" + $w.ctxm add command -label "Copy All" \ + -command " + $w.m.t tag add sel 0.0 end + tk_textCopy $w.m.t + $w.m.t tag remove sel 0.0 end + " + + button $w.ok -text {Close} \ + -state disabled \ + -command "destroy $w" + pack $w.ok -side bottom -anchor e -pady 10 -padx 10 + + bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y" + bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break" + bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break" + bind $w "focus $w" + wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]" + return $w +} + +proc console_exec {w cmd after} { + # -- Cygwin's Tcl tosses the enviroment when we exec our child. + # But most users need that so we have to relogin. :-( + # + if {[is_Cygwin]} { + set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"] + } + + # -- Tcl won't let us redirect both stdout and stderr to + # the same pipe. So pass it through cat... + # + set cmd [concat | $cmd |& cat] + + set fd_f [open $cmd r] + fconfigure $fd_f -blocking 0 -translation binary + fileevent $fd_f readable [list console_read $w $fd_f $after] +} + +proc console_read {w fd after} { + global console_cr + + set buf [read $fd] + if {$buf ne {}} { + if {![winfo exists $w]} {console_init $w} + $w.m.t conf -state normal + set c 0 + set n [string length $buf] + while {$c < $n} { + set cr [string first "\r" $buf $c] + set lf [string first "\n" $buf $c] + if {$cr < 0} {set cr [expr {$n + 1}]} + if {$lf < 0} {set lf [expr {$n + 1}]} + + if {$lf < $cr} { + $w.m.t insert end [string range $buf $c $lf] + set console_cr($w) [$w.m.t index {end -1c}] + set c $lf + incr c + } else { + $w.m.t delete $console_cr($w) end + $w.m.t insert end "\n" + $w.m.t insert end [string range $buf $c $cr] + set c $cr + incr c + } + } + $w.m.t conf -state disabled + $w.m.t see end + } + + fconfigure $fd -blocking 1 + if {[eof $fd]} { + if {[catch {close $fd}]} { + set ok 0 + } else { + set ok 1 + } + uplevel #0 $after $w $ok + return + } + fconfigure $fd -blocking 0 +} + +proc console_chain {cmdlist w {ok 1}} { + if {$ok} { + if {[llength $cmdlist] == 0} { + console_done $w $ok + return + } + + set cmd [lindex $cmdlist 0] + set cmdlist [lrange $cmdlist 1 end] + + if {[lindex $cmd 0] eq {console_exec}} { + console_exec $w \ + [lindex $cmd 1] \ + [list console_chain $cmdlist] + } else { + uplevel #0 $cmd $cmdlist $w $ok + } + } else { + console_done $w $ok + } +} + +proc console_done {args} { + global console_cr console_data + + switch -- [llength $args] { + 2 { + set w [lindex $args 0] + set ok [lindex $args 1] + } + 3 { + set w [lindex $args 1] + set ok [lindex $args 2] + } + default { + error "wrong number of args: console_done ?ignored? w ok" + } + } + + if {$ok} { + if {[winfo exists $w]} { + $w.m.s conf -background green -text {Success} + $w.ok conf -state normal + focus $w.ok + } + } else { + if {![winfo exists $w]} { + console_init $w + } + $w.m.s conf -background red -text {Error: Command Failed} + $w.ok conf -state normal + focus $w.ok + } + + array unset console_cr $w + array unset console_data $w +} diff --git a/lib/database.tcl b/lib/database.tcl new file mode 100644 index 0000000000..e31466fb50 --- /dev/null +++ b/lib/database.tcl @@ -0,0 +1,89 @@ +# git-gui object database management support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc do_stats {} { + set fd [open "| git count-objects -v" r] + while {[gets $fd line] > 0} { + if {[regexp {^([^:]+): (\d+)$} $line _ name value]} { + set stats($name) $value + } + } + close $fd + + set packed_sz 0 + foreach p [glob -directory [gitdir objects pack] \ + -type f \ + -nocomplain -- *] { + incr packed_sz [file size $p] + } + if {$packed_sz > 0} { + set stats(size-pack) [expr {$packed_sz / 1024}] + } + + set w .stats_view + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text {Database Statistics} + pack $w.header -side top -fill x + + frame $w.buttons -border 1 + button $w.buttons.close -text Close \ + -default active \ + -command [list destroy $w] + button $w.buttons.gc -text {Compress Database} \ + -default normal \ + -command "destroy $w;do_gc" + pack $w.buttons.close -side right + pack $w.buttons.gc -side left + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + frame $w.stat -borderwidth 1 -relief solid + foreach s { + {count {Number of loose objects}} + {size {Disk space used by loose objects} { KiB}} + {in-pack {Number of packed objects}} + {packs {Number of packs}} + {size-pack {Disk space used by packed objects} { KiB}} + {prune-packable {Packed objects waiting for pruning}} + {garbage {Garbage files}} + } { + set name [lindex $s 0] + set label [lindex $s 1] + if {[catch {set value $stats($name)}]} continue + if {[llength $s] > 2} { + set value "$value[lindex $s 2]" + } + + label $w.stat.l_$name -text "$label:" -anchor w + label $w.stat.v_$name -text $value -anchor w + grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5} + } + pack $w.stat -pady 10 -padx 10 + + bind $w "grab $w; focus $w.buttons.close" + bind $w [list destroy $w] + bind $w [list destroy $w] + wm title $w "[appname] ([reponame]): Database Statistics" + tkwait window $w +} + +proc do_gc {} { + set w [new_console {gc} {Compressing the object database}] + console_chain { + {console_exec {git pack-refs --prune}} + {console_exec {git reflog expire --all}} + {console_exec {git repack -a -d -l}} + {console_exec {git rerere gc}} + } $w +} + +proc do_fsck_objects {} { + set w [new_console {fsck-objects} \ + {Verifying the object database with fsck-objects}] + set cmd [list git fsck-objects] + lappend cmd --full + lappend cmd --cache + lappend cmd --strict + console_exec $w $cmd console_done +} diff --git a/lib/diff.tcl b/lib/diff.tcl new file mode 100644 index 0000000000..7e715a6865 --- /dev/null +++ b/lib/diff.tcl @@ -0,0 +1,336 @@ +# git-gui diff viewer +# Copyright (C) 2006, 2007 Shawn Pearce + +proc clear_diff {} { + global ui_diff current_diff_path current_diff_header + global ui_index ui_workdir + + $ui_diff conf -state normal + $ui_diff delete 0.0 end + $ui_diff conf -state disabled + + set current_diff_path {} + set current_diff_header {} + + $ui_index tag remove in_diff 0.0 end + $ui_workdir tag remove in_diff 0.0 end +} + +proc reshow_diff {} { + global ui_status_value file_states file_lists + global current_diff_path current_diff_side + + set p $current_diff_path + if {$p eq {}} { + # No diff is being shown. + } elseif {$current_diff_side eq {} + || [catch {set s $file_states($p)}] + || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} { + clear_diff + } else { + show_diff $p $current_diff_side + } +} + +proc handle_empty_diff {} { + global current_diff_path file_states file_lists + + set path $current_diff_path + set s $file_states($path) + if {[lindex $s 0] ne {_M}} return + + info_popup "No differences detected. + +[short_path $path] has no changes. + +The modification date of this file was updated by another application, but the content within the file was not changed. + +A rescan will be automatically started to find other files which may have the same state." + + clear_diff + display_file $path __ + rescan {set ui_status_value {Ready.}} 0 +} + +proc show_diff {path w {lno {}}} { + global file_states file_lists + global is_3way_diff diff_active repo_config + global ui_diff ui_status_value ui_index ui_workdir + global current_diff_path current_diff_side current_diff_header + + if {$diff_active || ![lock_index read]} return + + clear_diff + if {$lno == {}} { + set lno [lsearch -sorted -exact $file_lists($w) $path] + if {$lno >= 0} { + incr lno + } + } + if {$lno >= 1} { + $w tag add in_diff $lno.0 [expr {$lno + 1}].0 + } + + set s $file_states($path) + set m [lindex $s 0] + set is_3way_diff 0 + set diff_active 1 + set current_diff_path $path + set current_diff_side $w + set current_diff_header {} + set ui_status_value "Loading diff of [escape_path $path]..." + + # - Git won't give us the diff, there's nothing to compare to! + # + if {$m eq {_O}} { + set max_sz [expr {128 * 1024}] + if {[catch { + set fd [open $path r] + set content [read $fd $max_sz] + close $fd + set sz [file size $path] + } err ]} { + set diff_active 0 + unlock_index + set ui_status_value "Unable to display [escape_path $path]" + error_popup "Error loading file:\n\n$err" + return + } + $ui_diff conf -state normal + if {![catch {set type [exec file $path]}]} { + set n [string length $path] + if {[string equal -length $n $path $type]} { + set type [string range $type $n end] + regsub {^:?\s*} $type {} type + } + $ui_diff insert end "* $type\n" d_@ + } + if {[string first "\0" $content] != -1} { + $ui_diff insert end \ + "* Binary file (not showing content)." \ + d_@ + } else { + if {$sz > $max_sz} { + $ui_diff insert end \ +"* Untracked file is $sz bytes. +* Showing only first $max_sz bytes. +" d_@ + } + $ui_diff insert end $content + if {$sz > $max_sz} { + $ui_diff insert end " +* Untracked file clipped here by [appname]. +* To see the entire file, use an external editor. +" d_@ + } + } + $ui_diff conf -state disabled + set diff_active 0 + unlock_index + set ui_status_value {Ready.} + return + } + + set cmd [list | git] + if {$w eq $ui_index} { + lappend cmd diff-index + lappend cmd --cached + } elseif {$w eq $ui_workdir} { + if {[string index $m 0] eq {U}} { + lappend cmd diff + } else { + lappend cmd diff-files + } + } + + lappend cmd -p + lappend cmd --no-color + if {$repo_config(gui.diffcontext) > 0} { + lappend cmd "-U$repo_config(gui.diffcontext)" + } + if {$w eq $ui_index} { + lappend cmd [PARENT] + } + lappend cmd -- + lappend cmd $path + + if {[catch {set fd [open $cmd r]} err]} { + set diff_active 0 + unlock_index + set ui_status_value "Unable to display [escape_path $path]" + error_popup "Error loading diff:\n\n$err" + return + } + + fconfigure $fd \ + -blocking 0 \ + -encoding binary \ + -translation binary + fileevent $fd readable [list read_diff $fd] +} + +proc read_diff {fd} { + global ui_diff ui_status_value diff_active + global is_3way_diff current_diff_header + + $ui_diff conf -state normal + while {[gets $fd line] >= 0} { + # -- Cleanup uninteresting diff header lines. + # + if { [string match {diff --git *} $line] + || [string match {diff --cc *} $line] + || [string match {diff --combined *} $line] + || [string match {--- *} $line] + || [string match {+++ *} $line]} { + append current_diff_header $line "\n" + continue + } + if {[string match {index *} $line]} continue + if {$line eq {deleted file mode 120000}} { + set line "deleted symlink" + } + + # -- Automatically detect if this is a 3 way diff. + # + if {[string match {@@@ *} $line]} {set is_3way_diff 1} + + if {[string match {mode *} $line] + || [string match {new file *} $line] + || [string match {deleted file *} $line] + || [string match {Binary files * and * differ} $line] + || $line eq {\ No newline at end of file} + || [regexp {^\* Unmerged path } $line]} { + set tags {} + } elseif {$is_3way_diff} { + set op [string range $line 0 1] + switch -- $op { + { } {set tags {}} + {@@} {set tags d_@} + { +} {set tags d_s+} + { -} {set tags d_s-} + {+ } {set tags d_+s} + {- } {set tags d_-s} + {--} {set tags d_--} + {++} { + if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} { + set line [string replace $line 0 1 { }] + set tags d$op + } else { + set tags d_++ + } + } + default { + puts "error: Unhandled 3 way diff marker: {$op}" + set tags {} + } + } + } else { + set op [string index $line 0] + switch -- $op { + { } {set tags {}} + {@} {set tags d_@} + {-} {set tags d_-} + {+} { + if {[regexp {^\+([<>]{7} |={7})} $line _g op]} { + set line [string replace $line 0 0 { }] + set tags d$op + } else { + set tags d_+ + } + } + default { + puts "error: Unhandled 2 way diff marker: {$op}" + set tags {} + } + } + } + $ui_diff insert end $line $tags + if {[string index $line end] eq "\r"} { + $ui_diff tag add d_cr {end - 2c} + } + $ui_diff insert end "\n" $tags + } + $ui_diff conf -state disabled + + if {[eof $fd]} { + close $fd + set diff_active 0 + unlock_index + set ui_status_value {Ready.} + + if {[$ui_diff index end] eq {2.0}} { + handle_empty_diff + } + } +} + +proc apply_hunk {x y} { + global current_diff_path current_diff_header current_diff_side + global ui_diff ui_index file_states + + if {$current_diff_path eq {} || $current_diff_header eq {}} return + if {![lock_index apply_hunk]} return + + set apply_cmd {git apply --cached --whitespace=nowarn} + set mi [lindex $file_states($current_diff_path) 0] + if {$current_diff_side eq $ui_index} { + set mode unstage + lappend apply_cmd --reverse + if {[string index $mi 0] ne {M}} { + unlock_index + return + } + } else { + set mode stage + if {[string index $mi 1] ne {M}} { + unlock_index + return + } + } + + set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0] + set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0] + if {$s_lno eq {}} { + unlock_index + return + } + + set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end] + if {$e_lno eq {}} { + set e_lno end + } + + if {[catch { + set p [open "| $apply_cmd" w] + fconfigure $p -translation binary -encoding binary + puts -nonewline $p $current_diff_header + puts -nonewline $p [$ui_diff get $s_lno $e_lno] + close $p} err]} { + error_popup "Failed to $mode selected hunk.\n\n$err" + unlock_index + return + } + + $ui_diff conf -state normal + $ui_diff delete $s_lno $e_lno + $ui_diff conf -state disabled + + if {[$ui_diff get 1.0 end] eq "\n"} { + set o _ + } else { + set o ? + } + + if {$current_diff_side eq $ui_index} { + set mi ${o}M + } elseif {[string index $mi 0] eq {_}} { + set mi M$o + } else { + set mi ?$o + } + unlock_index + display_file $current_diff_path $mi + if {$o eq {_}} { + clear_diff + } +} diff --git a/lib/error.tcl b/lib/error.tcl new file mode 100644 index 0000000000..d0253ae2ff --- /dev/null +++ b/lib/error.tcl @@ -0,0 +1,101 @@ +# git-gui branch (create/delete) support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc error_popup {msg} { + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" + } + set cmd [list tk_messageBox \ + -icon error \ + -type ok \ + -title "$title: error" \ + -message $msg] + if {[winfo ismapped .]} { + lappend cmd -parent . + } + eval $cmd +} + +proc warn_popup {msg} { + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" + } + set cmd [list tk_messageBox \ + -icon warning \ + -type ok \ + -title "$title: warning" \ + -message $msg] + if {[winfo ismapped .]} { + lappend cmd -parent . + } + eval $cmd +} + +proc info_popup {msg {parent .}} { + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" + } + tk_messageBox \ + -parent $parent \ + -icon info \ + -type ok \ + -title $title \ + -message $msg +} + +proc ask_popup {msg} { + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" + } + return [tk_messageBox \ + -parent . \ + -icon question \ + -type yesno \ + -title $title \ + -message $msg] +} + +proc hook_failed_popup {hook msg} { + set w .hookfail + toplevel $w + + frame $w.m + label $w.m.l1 -text "$hook hook failed:" \ + -anchor w \ + -justify left \ + -font font_uibold + text $w.m.t \ + -background white -borderwidth 1 \ + -relief sunken \ + -width 80 -height 10 \ + -font font_diff \ + -yscrollcommand [list $w.m.sby set] + label $w.m.l2 \ + -text {You must correct the above errors before committing.} \ + -anchor w \ + -justify left \ + -font font_uibold + scrollbar $w.m.sby -command [list $w.m.t yview] + pack $w.m.l1 -side top -fill x + pack $w.m.l2 -side bottom -fill x + pack $w.m.sby -side right -fill y + pack $w.m.t -side left -fill both -expand 1 + pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 + + $w.m.t insert 1.0 $msg + $w.m.t conf -state disabled + + button $w.ok -text OK \ + -width 15 \ + -command "destroy $w" + pack $w.ok -side bottom -anchor e -pady 10 -padx 10 + + bind $w "grab $w; focus $w" + bind $w "destroy $w" + wm title $w "[appname] ([reponame]): error" + tkwait window $w +} diff --git a/lib/index.tcl b/lib/index.tcl new file mode 100644 index 0000000000..42742850ee --- /dev/null +++ b/lib/index.tcl @@ -0,0 +1,409 @@ +# git-gui index (add/remove) support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc update_indexinfo {msg pathList after} { + global update_index_cp ui_status_value + + if {![lock_index update]} return + + set update_index_cp 0 + set pathList [lsort $pathList] + set totalCnt [llength $pathList] + set batch [expr {int($totalCnt * .01) + 1}] + if {$batch > 25} {set batch 25} + + set ui_status_value [format \ + "$msg... %i/%i files (%.2f%%)" \ + $update_index_cp \ + $totalCnt \ + 0.0] + set fd [open "| git update-index -z --index-info" w] + fconfigure $fd \ + -blocking 0 \ + -buffering full \ + -buffersize 512 \ + -encoding binary \ + -translation binary + fileevent $fd writable [list \ + write_update_indexinfo \ + $fd \ + $pathList \ + $totalCnt \ + $batch \ + $msg \ + $after \ + ] +} + +proc write_update_indexinfo {fd pathList totalCnt batch msg after} { + global update_index_cp ui_status_value + global file_states current_diff_path + + if {$update_index_cp >= $totalCnt} { + close $fd + unlock_index + uplevel #0 $after + return + } + + for {set i $batch} \ + {$update_index_cp < $totalCnt && $i > 0} \ + {incr i -1} { + set path [lindex $pathList $update_index_cp] + incr update_index_cp + + set s $file_states($path) + switch -glob -- [lindex $s 0] { + A? {set new _O} + M? {set new _M} + D_ {set new _D} + D? {set new _?} + ?? {continue} + } + set info [lindex $s 2] + if {$info eq {}} continue + + puts -nonewline $fd "$info\t[encoding convertto $path]\0" + display_file $path $new + } + + set ui_status_value [format \ + "$msg... %i/%i files (%.2f%%)" \ + $update_index_cp \ + $totalCnt \ + [expr {100.0 * $update_index_cp / $totalCnt}]] +} + +proc update_index {msg pathList after} { + global update_index_cp ui_status_value + + if {![lock_index update]} return + + set update_index_cp 0 + set pathList [lsort $pathList] + set totalCnt [llength $pathList] + set batch [expr {int($totalCnt * .01) + 1}] + if {$batch > 25} {set batch 25} + + set ui_status_value [format \ + "$msg... %i/%i files (%.2f%%)" \ + $update_index_cp \ + $totalCnt \ + 0.0] + set fd [open "| git update-index --add --remove -z --stdin" w] + fconfigure $fd \ + -blocking 0 \ + -buffering full \ + -buffersize 512 \ + -encoding binary \ + -translation binary + fileevent $fd writable [list \ + write_update_index \ + $fd \ + $pathList \ + $totalCnt \ + $batch \ + $msg \ + $after \ + ] +} + +proc write_update_index {fd pathList totalCnt batch msg after} { + global update_index_cp ui_status_value + global file_states current_diff_path + + if {$update_index_cp >= $totalCnt} { + close $fd + unlock_index + uplevel #0 $after + return + } + + for {set i $batch} \ + {$update_index_cp < $totalCnt && $i > 0} \ + {incr i -1} { + set path [lindex $pathList $update_index_cp] + incr update_index_cp + + switch -glob -- [lindex $file_states($path) 0] { + AD {set new __} + ?D {set new D_} + _O - + AM {set new A_} + U? { + if {[file exists $path]} { + set new M_ + } else { + set new D_ + } + } + ?M {set new M_} + ?? {continue} + } + puts -nonewline $fd "[encoding convertto $path]\0" + display_file $path $new + } + + set ui_status_value [format \ + "$msg... %i/%i files (%.2f%%)" \ + $update_index_cp \ + $totalCnt \ + [expr {100.0 * $update_index_cp / $totalCnt}]] +} + +proc checkout_index {msg pathList after} { + global update_index_cp ui_status_value + + if {![lock_index update]} return + + set update_index_cp 0 + set pathList [lsort $pathList] + set totalCnt [llength $pathList] + set batch [expr {int($totalCnt * .01) + 1}] + if {$batch > 25} {set batch 25} + + set ui_status_value [format \ + "$msg... %i/%i files (%.2f%%)" \ + $update_index_cp \ + $totalCnt \ + 0.0] + set cmd [list git checkout-index] + lappend cmd --index + lappend cmd --quiet + lappend cmd --force + lappend cmd -z + lappend cmd --stdin + set fd [open "| $cmd " w] + fconfigure $fd \ + -blocking 0 \ + -buffering full \ + -buffersize 512 \ + -encoding binary \ + -translation binary + fileevent $fd writable [list \ + write_checkout_index \ + $fd \ + $pathList \ + $totalCnt \ + $batch \ + $msg \ + $after \ + ] +} + +proc write_checkout_index {fd pathList totalCnt batch msg after} { + global update_index_cp ui_status_value + global file_states current_diff_path + + if {$update_index_cp >= $totalCnt} { + close $fd + unlock_index + uplevel #0 $after + return + } + + for {set i $batch} \ + {$update_index_cp < $totalCnt && $i > 0} \ + {incr i -1} { + set path [lindex $pathList $update_index_cp] + incr update_index_cp + switch -glob -- [lindex $file_states($path) 0] { + U? {continue} + ?M - + ?D { + puts -nonewline $fd "[encoding convertto $path]\0" + display_file $path ?_ + } + } + } + + set ui_status_value [format \ + "$msg... %i/%i files (%.2f%%)" \ + $update_index_cp \ + $totalCnt \ + [expr {100.0 * $update_index_cp / $totalCnt}]] +} + +proc unstage_helper {txt paths} { + global file_states current_diff_path + + if {![lock_index begin-update]} return + + set pathList [list] + set after {} + foreach path $paths { + switch -glob -- [lindex $file_states($path) 0] { + A? - + M? - + D? { + lappend pathList $path + if {$path eq $current_diff_path} { + set after {reshow_diff;} + } + } + } + } + if {$pathList eq {}} { + unlock_index + } else { + update_indexinfo \ + $txt \ + $pathList \ + [concat $after {set ui_status_value {Ready.}}] + } +} + +proc do_unstage_selection {} { + global current_diff_path selected_paths + + if {[array size selected_paths] > 0} { + unstage_helper \ + {Unstaging selected files from commit} \ + [array names selected_paths] + } elseif {$current_diff_path ne {}} { + unstage_helper \ + "Unstaging [short_path $current_diff_path] from commit" \ + [list $current_diff_path] + } +} + +proc add_helper {txt paths} { + global file_states current_diff_path + + if {![lock_index begin-update]} return + + set pathList [list] + set after {} + foreach path $paths { + switch -glob -- [lindex $file_states($path) 0] { + _O - + ?M - + ?D - + U? { + lappend pathList $path + if {$path eq $current_diff_path} { + set after {reshow_diff;} + } + } + } + } + if {$pathList eq {}} { + unlock_index + } else { + update_index \ + $txt \ + $pathList \ + [concat $after {set ui_status_value {Ready to commit.}}] + } +} + +proc do_add_selection {} { + global current_diff_path selected_paths + + if {[array size selected_paths] > 0} { + add_helper \ + {Adding selected files} \ + [array names selected_paths] + } elseif {$current_diff_path ne {}} { + add_helper \ + "Adding [short_path $current_diff_path]" \ + [list $current_diff_path] + } +} + +proc do_add_all {} { + global file_states + + set paths [list] + foreach path [array names file_states] { + switch -glob -- [lindex $file_states($path) 0] { + U? {continue} + ?M - + ?D {lappend paths $path} + } + } + add_helper {Adding all changed files} $paths +} + +proc revert_helper {txt paths} { + global file_states current_diff_path + + if {![lock_index begin-update]} return + + set pathList [list] + set after {} + foreach path $paths { + switch -glob -- [lindex $file_states($path) 0] { + U? {continue} + ?M - + ?D { + lappend pathList $path + if {$path eq $current_diff_path} { + set after {reshow_diff;} + } + } + } + } + + set n [llength $pathList] + if {$n == 0} { + unlock_index + return + } elseif {$n == 1} { + set s "[short_path [lindex $pathList]]" + } else { + set s "these $n files" + } + + set reply [tk_dialog \ + .confirm_revert \ + "[appname] ([reponame])" \ + "Revert changes in $s? + +Any unadded changes will be permanently lost by the revert." \ + question \ + 1 \ + {Do Nothing} \ + {Revert Changes} \ + ] + if {$reply == 1} { + checkout_index \ + $txt \ + $pathList \ + [concat $after {set ui_status_value {Ready.}}] + } else { + unlock_index + } +} + +proc do_revert_selection {} { + global current_diff_path selected_paths + + if {[array size selected_paths] > 0} { + revert_helper \ + {Reverting selected files} \ + [array names selected_paths] + } elseif {$current_diff_path ne {}} { + revert_helper \ + "Reverting [short_path $current_diff_path]" \ + [list $current_diff_path] + } +} + +proc do_select_commit_type {} { + global commit_type selected_commit_type + + if {$selected_commit_type eq {new} + && [string match amend* $commit_type]} { + create_new_commit + } elseif {$selected_commit_type eq {amend} + && ![string match amend* $commit_type]} { + load_last_commit + + # The amend request was rejected... + # + if {![string match amend* $commit_type]} { + set selected_commit_type new + } + } +} diff --git a/lib/merge.tcl b/lib/merge.tcl new file mode 100644 index 0000000000..75724a930f --- /dev/null +++ b/lib/merge.tcl @@ -0,0 +1,281 @@ +# git-gui branch merge support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc can_merge {} { + global HEAD commit_type file_states + + if {[string match amend* $commit_type]} { + info_popup {Cannot merge while amending. + +You must finish amending this commit before starting any type of merge. +} + return 0 + } + + if {[committer_ident] eq {}} {return 0} + if {![lock_index merge]} {return 0} + + # -- Our in memory state should match the repository. + # + repository_state curType curHEAD curMERGE_HEAD + if {$commit_type ne $curType || $HEAD ne $curHEAD} { + info_popup {Last scanned state does not match repository state. + +Another Git program has modified this repository since the last scan. A rescan must be performed before a merge can be performed. + +The rescan will be automatically started now. +} + unlock_index + rescan {set ui_status_value {Ready.}} + return 0 + } + + foreach path [array names file_states] { + switch -glob -- [lindex $file_states($path) 0] { + _O { + continue; # and pray it works! + } + U? { + error_popup "You are in the middle of a conflicted merge. + +File [short_path $path] has merge conflicts. + +You must resolve them, add the file, and commit to complete the current merge. Only then can you begin another merge. +" + unlock_index + return 0 + } + ?? { + error_popup "You are in the middle of a change. + +File [short_path $path] is modified. + +You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise. +" + unlock_index + return 0 + } + } + } + + return 1 +} + +proc visualize_local_merge {w} { + set revs {} + foreach i [$w.source.l curselection] { + lappend revs [$w.source.l get $i] + } + if {$revs eq {}} return + lappend revs --not HEAD + do_gitk $revs +} + +proc start_local_merge_action {w} { + global HEAD ui_status_value current_branch + + set cmd [list git merge] + set names {} + set revcnt 0 + foreach i [$w.source.l curselection] { + set b [$w.source.l get $i] + lappend cmd $b + lappend names $b + incr revcnt + } + + if {$revcnt == 0} { + return + } elseif {$revcnt == 1} { + set unit branch + } elseif {$revcnt <= 15} { + set unit branches + + if {[tk_dialog \ + $w.confirm_octopus \ + [wm title $w] \ + "Use octopus merge strategy? + +You are merging $revcnt branches at once. This requires using the octopus merge driver, which may not succeed if there are file-level conflicts. +" \ + question \ + 0 \ + {Cancel} \ + {Use octopus} \ + ] != 1} return + } else { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Too many branches selected. + +You have requested to merge $revcnt branches in an octopus merge. This exceeds Git's internal limit of 15 branches per merge. + +Please select fewer branches. To merge more than 15 branches, merge the branches in batches. +" + return + } + + set msg "Merging $current_branch, [join $names {, }]" + set ui_status_value "$msg..." + set cons [new_console "Merge" $msg] + console_exec $cons $cmd [list finish_merge $revcnt] + bind $w {} + destroy $w +} + +proc finish_merge {revcnt w ok} { + console_done $w $ok + if {$ok} { + set msg {Merge completed successfully.} + } else { + if {$revcnt != 1} { + info_popup "Octopus merge failed. + +Your merge of $revcnt branches has failed. + +There are file-level conflicts between the branches which must be resolved manually. + +The working directory will now be reset. + +You can attempt this merge again by merging only one branch at a time." $w + + set fd [open "| git read-tree --reset -u HEAD" r] + fconfigure $fd -blocking 0 -translation binary + fileevent $fd readable [list reset_hard_wait $fd] + set ui_status_value {Aborting... please wait...} + return + } + + set msg {Merge failed. Conflict resolution is required.} + } + unlock_index + rescan [list set ui_status_value $msg] +} + +proc do_local_merge {} { + global current_branch + + if {![can_merge]} return + + set w .merge_setup + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header \ + -text "Merge Into $current_branch" \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.visualize -text Visualize \ + -command [list visualize_local_merge $w] + pack $w.buttons.visualize -side left + button $w.buttons.create -text Merge \ + -command [list start_local_merge_action $w] + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.source -text {Source Branches} + listbox $w.source.l \ + -height 10 \ + -width 70 \ + -selectmode extended \ + -yscrollcommand [list $w.source.sby set] + scrollbar $w.source.sby -command [list $w.source.l yview] + pack $w.source.sby -side right -fill y + pack $w.source.l -side left -fill both -expand 1 + pack $w.source -fill both -expand 1 -pady 5 -padx 5 + + set cmd [list git for-each-ref] + lappend cmd {--format=%(objectname) %(*objectname) %(refname)} + lappend cmd refs/heads + lappend cmd refs/remotes + lappend cmd refs/tags + set fr_fd [open "| $cmd" r] + fconfigure $fr_fd -translation binary + while {[gets $fr_fd line] > 0} { + set line [split $line { }] + set sha1([lindex $line 0]) [lindex $line 2] + set sha1([lindex $line 1]) [lindex $line 2] + } + close $fr_fd + + set to_show {} + set fr_fd [open "| git rev-list --all --not HEAD"] + while {[gets $fr_fd line] > 0} { + if {[catch {set ref $sha1($line)}]} continue + regsub ^refs/(heads|remotes|tags)/ $ref {} ref + lappend to_show $ref + } + close $fr_fd + + foreach ref [lsort -unique $to_show] { + $w.source.l insert end $ref + } + + bind $w "grab $w" + bind $w "unlock_index;destroy $w" + bind $w unlock_index + wm title $w "[appname] ([reponame]): Merge" + tkwait window $w +} + +proc do_reset_hard {} { + global HEAD commit_type file_states + + if {[string match amend* $commit_type]} { + info_popup {Cannot abort while amending. + +You must finish amending this commit. +} + return + } + + if {![lock_index abort]} return + + if {[string match *merge* $commit_type]} { + set op merge + } else { + set op commit + } + + if {[ask_popup "Abort $op? + +Aborting the current $op will cause *ALL* uncommitted changes to be lost. + +Continue with aborting the current $op?"] eq {yes}} { + set fd [open "| git read-tree --reset -u HEAD" r] + fconfigure $fd -blocking 0 -translation binary + fileevent $fd readable [list reset_hard_wait $fd] + set ui_status_value {Aborting... please wait...} + } else { + unlock_index + } +} + +proc reset_hard_wait {fd} { + global ui_comm + + read $fd + if {[eof $fd]} { + close $fd + unlock_index + + $ui_comm delete 0.0 end + $ui_comm edit modified false + + catch {file delete [gitdir MERGE_HEAD]} + catch {file delete [gitdir rr-cache MERGE_RR]} + catch {file delete [gitdir SQUASH_MSG]} + catch {file delete [gitdir MERGE_MSG]} + catch {file delete [gitdir GITGUI_MSG]} + + rescan {set ui_status_value {Abort completed. Ready.}} + } +} diff --git a/lib/option.tcl b/lib/option.tcl new file mode 100644 index 0000000000..17fcc65f78 --- /dev/null +++ b/lib/option.tcl @@ -0,0 +1,290 @@ +# git-gui options editor +# Copyright (C) 2006, 2007 Shawn Pearce + +proc save_config {} { + global default_config font_descs + global repo_config global_config + global repo_config_new global_config_new + + foreach option $font_descs { + set name [lindex $option 0] + set font [lindex $option 1] + font configure $font \ + -family $global_config_new(gui.$font^^family) \ + -size $global_config_new(gui.$font^^size) + font configure ${font}bold \ + -family $global_config_new(gui.$font^^family) \ + -size $global_config_new(gui.$font^^size) + set global_config_new(gui.$name) [font configure $font] + unset global_config_new(gui.$font^^family) + unset global_config_new(gui.$font^^size) + } + + foreach name [array names default_config] { + set value $global_config_new($name) + if {$value ne $global_config($name)} { + if {$value eq $default_config($name)} { + catch {git config --global --unset $name} + } else { + regsub -all "\[{}\]" $value {"} value + git config --global $name $value + } + set global_config($name) $value + if {$value eq $repo_config($name)} { + catch {git config --unset $name} + set repo_config($name) $value + } + } + } + + foreach name [array names default_config] { + set value $repo_config_new($name) + if {$value ne $repo_config($name)} { + if {$value eq $global_config($name)} { + catch {git config --unset $name} + } else { + regsub -all "\[{}\]" $value {"} value + git config $name $value + } + set repo_config($name) $value + } + } +} + +proc do_about {} { + global appvers copyright + global tcl_patchLevel tk_patchLevel + + set w .about_dialog + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text "About [appname]" \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.close -text {Close} \ + -default active \ + -command [list destroy $w] + pack $w.buttons.close -side right + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + label $w.desc \ + -text "git-gui - a graphical user interface for Git. +$copyright" \ + -padx 5 -pady 5 \ + -justify left \ + -anchor w \ + -borderwidth 1 \ + -relief solid + pack $w.desc -side top -fill x -padx 5 -pady 5 + + set v {} + append v "git-gui version $appvers\n" + append v "[git version]\n" + append v "\n" + if {$tcl_patchLevel eq $tk_patchLevel} { + append v "Tcl/Tk version $tcl_patchLevel" + } else { + append v "Tcl version $tcl_patchLevel" + append v ", Tk version $tk_patchLevel" + } + + label $w.vers \ + -text $v \ + -padx 5 -pady 5 \ + -justify left \ + -anchor w \ + -borderwidth 1 \ + -relief solid + pack $w.vers -side top -fill x -padx 5 -pady 5 + + menu $w.ctxm -tearoff 0 + $w.ctxm add command \ + -label {Copy} \ + -command " + clipboard clear + clipboard append -format STRING -type STRING -- \[$w.vers cget -text\] + " + + bind $w "grab $w; focus $w.buttons.close" + bind $w "destroy $w" + bind $w "destroy $w" + bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w" + wm title $w "About [appname]" + tkwait window $w +} + +proc do_options {} { + global repo_config global_config font_descs + global repo_config_new global_config_new + + array unset repo_config_new + array unset global_config_new + foreach name [array names repo_config] { + set repo_config_new($name) $repo_config($name) + } + load_config 1 + foreach name [array names repo_config] { + switch -- $name { + gui.diffcontext {continue} + } + set repo_config_new($name) $repo_config($name) + } + foreach name [array names global_config] { + set global_config_new($name) $global_config($name) + } + + set w .options_editor + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text "Options" \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.restore -text {Restore Defaults} \ + -default normal \ + -command do_restore_defaults + pack $w.buttons.restore -side left + button $w.buttons.save -text Save \ + -default active \ + -command [list do_save_config $w] + pack $w.buttons.save -side right + button $w.buttons.cancel -text {Cancel} \ + -default normal \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.repo -text "[reponame] Repository" + labelframe $w.global -text {Global (All Repositories)} + pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5 + pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 + + set optid 0 + foreach option { + {t user.name {User Name}} + {t user.email {Email Address}} + + {b merge.summary {Summarize Merge Commits}} + {i-1..5 merge.verbosity {Merge Verbosity}} + + {b gui.trustmtime {Trust File Modification Timestamps}} + {i-1..99 gui.diffcontext {Number of Diff Context Lines}} + {t gui.newbranchtemplate {New Branch Name Template}} + } { + set type [lindex $option 0] + set name [lindex $option 1] + set text [lindex $option 2] + incr optid + foreach f {repo global} { + switch -glob -- $type { + b { + checkbutton $w.$f.$optid -text $text \ + -variable ${f}_config_new($name) \ + -onvalue true \ + -offvalue false + pack $w.$f.$optid -side top -anchor w + } + i-* { + regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max + frame $w.$f.$optid + label $w.$f.$optid.l -text "$text:" + pack $w.$f.$optid.l -side left -anchor w -fill x + spinbox $w.$f.$optid.v \ + -textvariable ${f}_config_new($name) \ + -from $min \ + -to $max \ + -increment 1 \ + -width [expr {1 + [string length $max]}] + bind $w.$f.$optid.v {%W selection range 0 end} + pack $w.$f.$optid.v -side right -anchor e -padx 5 + pack $w.$f.$optid -side top -anchor w -fill x + } + t { + frame $w.$f.$optid + label $w.$f.$optid.l -text "$text:" + entry $w.$f.$optid.v \ + -borderwidth 1 \ + -relief sunken \ + -width 20 \ + -textvariable ${f}_config_new($name) + pack $w.$f.$optid.l -side left -anchor w + pack $w.$f.$optid.v -side left -anchor w \ + -fill x -expand 1 \ + -padx 5 + pack $w.$f.$optid -side top -anchor w -fill x + } + } + } + } + + set all_fonts [lsort [font families]] + foreach option $font_descs { + set name [lindex $option 0] + set font [lindex $option 1] + set text [lindex $option 2] + + set global_config_new(gui.$font^^family) \ + [font configure $font -family] + set global_config_new(gui.$font^^size) \ + [font configure $font -size] + + frame $w.global.$name + label $w.global.$name.l -text "$text:" + pack $w.global.$name.l -side left -anchor w -fill x + eval tk_optionMenu $w.global.$name.family \ + global_config_new(gui.$font^^family) \ + $all_fonts + spinbox $w.global.$name.size \ + -textvariable global_config_new(gui.$font^^size) \ + -from 2 -to 80 -increment 1 \ + -width 3 + bind $w.global.$name.size {%W selection range 0 end} + pack $w.global.$name.size -side right -anchor e + pack $w.global.$name.family -side right -anchor e + pack $w.global.$name -side top -anchor w -fill x + } + + bind $w "grab $w; focus $w.buttons.save" + bind $w "destroy $w" + bind $w [list do_save_config $w] + wm title $w "[appname] ([reponame]): Options" + tkwait window $w +} + +proc do_restore_defaults {} { + global font_descs default_config repo_config + global repo_config_new global_config_new + + foreach name [array names default_config] { + set repo_config_new($name) $default_config($name) + set global_config_new($name) $default_config($name) + } + + foreach option $font_descs { + set name [lindex $option 0] + set repo_config(gui.$name) $default_config(gui.$name) + } + apply_config + + foreach option $font_descs { + set name [lindex $option 0] + set font [lindex $option 1] + set global_config_new(gui.$font^^family) \ + [font configure $font -family] + set global_config_new(gui.$font^^size) \ + [font configure $font -size] + } +} + +proc do_save_config {w} { + if {[catch {save_config} err]} { + error_popup "Failed to completely save options:\n\n$err" + } + reshow_diff + destroy $w +} diff --git a/lib/remote.tcl b/lib/remote.tcl new file mode 100644 index 0000000000..99f353ed7d --- /dev/null +++ b/lib/remote.tcl @@ -0,0 +1,159 @@ +# git-gui remote management +# Copyright (C) 2006, 2007 Shawn Pearce + +proc is_tracking_branch {name} { + global tracking_branches + + if {![catch {set info $tracking_branches($name)}]} { + return 1 + } + foreach t [array names tracking_branches] { + if {[string match {*/\*} $t] && [string match $t $name]} { + return 1 + } + } + return 0 +} + +proc all_tracking_branches {} { + global tracking_branches + + set all_trackings {} + set cmd {} + foreach name [array names tracking_branches] { + if {[regsub {/\*$} $name {} name]} { + lappend cmd $name + } else { + regsub ^refs/(heads|remotes)/ $name {} name + lappend all_trackings $name + } + } + + if {$cmd ne {}} { + set fd [open "| git for-each-ref --format=%(refname) $cmd" r] + while {[gets $fd name] > 0} { + regsub ^refs/(heads|remotes)/ $name {} name + lappend all_trackings $name + } + close $fd + } + + return [lsort -unique $all_trackings] +} + +proc load_all_remotes {} { + global repo_config + global all_remotes tracking_branches + + set all_remotes [list] + array unset tracking_branches + + set rm_dir [gitdir remotes] + if {[file isdirectory $rm_dir]} { + set all_remotes [glob \ + -types f \ + -tails \ + -nocomplain \ + -directory $rm_dir *] + + foreach name $all_remotes { + catch { + set fd [open [file join $rm_dir $name] r] + while {[gets $fd line] >= 0} { + if {![regexp {^Pull:[ ]*([^:]+):(.+)$} \ + $line line src dst]} continue + if {![regexp ^refs/ $dst]} { + set dst "refs/heads/$dst" + } + set tracking_branches($dst) [list $name $src] + } + close $fd + } + } + } + + foreach line [array names repo_config remote.*.url] { + if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue + lappend all_remotes $name + + if {[catch {set fl $repo_config(remote.$name.fetch)}]} { + set fl {} + } + foreach line $fl { + if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue + if {![regexp ^refs/ $dst]} { + set dst "refs/heads/$dst" + } + set tracking_branches($dst) [list $name $src] + } + } + + set all_remotes [lsort -unique $all_remotes] +} + +proc populate_fetch_menu {} { + global all_remotes repo_config + + set m .mbar.fetch + foreach r $all_remotes { + set enable 0 + if {![catch {set a $repo_config(remote.$r.url)}]} { + if {![catch {set a $repo_config(remote.$r.fetch)}]} { + set enable 1 + } + } else { + catch { + set fd [open [gitdir remotes $r] r] + while {[gets $fd n] >= 0} { + if {[regexp {^Pull:[ \t]*([^:]+):} $n]} { + set enable 1 + break + } + } + close $fd + } + } + + if {$enable} { + $m add command \ + -label "Fetch from $r..." \ + -command [list fetch_from $r] + } + } +} + +proc populate_push_menu {} { + global all_remotes repo_config + + set m .mbar.push + set fast_count 0 + foreach r $all_remotes { + set enable 0 + if {![catch {set a $repo_config(remote.$r.url)}]} { + if {![catch {set a $repo_config(remote.$r.push)}]} { + set enable 1 + } + } else { + catch { + set fd [open [gitdir remotes $r] r] + while {[gets $fd n] >= 0} { + if {[regexp {^Push:[ \t]*([^:]+):} $n]} { + set enable 1 + break + } + } + close $fd + } + } + + if {$enable} { + if {!$fast_count} { + $m add separator + } + $m add command \ + -label "Push to $r..." \ + -command [list push_to $r] + incr fast_count + } + } +} diff --git a/lib/shortcut.tcl b/lib/shortcut.tcl new file mode 100644 index 0000000000..ebf72e4452 --- /dev/null +++ b/lib/shortcut.tcl @@ -0,0 +1,141 @@ +# git-gui desktop icon creators +# Copyright (C) 2006, 2007 Shawn Pearce + +proc do_windows_shortcut {} { + global argv0 + + set fn [tk_getSaveFile \ + -parent . \ + -title "[appname] ([reponame]): Create Desktop Icon" \ + -initialfile "Git [reponame].bat"] + if {$fn != {}} { + if {[catch { + set fd [open $fn w] + puts $fd "@ECHO Entering [reponame]" + puts $fd "@ECHO Starting git-gui... please wait..." + puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%" + puts $fd "@SET GIT_DIR=[file normalize [gitdir]]" + puts -nonewline $fd "@\"[info nameofexecutable]\"" + puts $fd " \"[file normalize $argv0]\"" + close $fd + } err]} { + error_popup "Cannot write script:\n\n$err" + } + } +} + +proc do_cygwin_shortcut {} { + global argv0 + + if {[catch { + set desktop [exec cygpath \ + --windows \ + --absolute \ + --long-name \ + --desktop] + }]} { + set desktop . + } + set fn [tk_getSaveFile \ + -parent . \ + -title "[appname] ([reponame]): Create Desktop Icon" \ + -initialdir $desktop \ + -initialfile "Git [reponame].bat"] + if {$fn != {}} { + if {[catch { + set fd [open $fn w] + set sh [exec cygpath \ + --windows \ + --absolute \ + /bin/sh] + set me [exec cygpath \ + --unix \ + --absolute \ + $argv0] + set gd [exec cygpath \ + --unix \ + --absolute \ + [gitdir]] + set gw [exec cygpath \ + --windows \ + --absolute \ + [file dirname [gitdir]]] + regsub -all ' $me "'\\''" me + regsub -all ' $gd "'\\''" gd + puts $fd "@ECHO Entering $gw" + puts $fd "@ECHO Starting git-gui... please wait..." + puts -nonewline $fd "@\"$sh\" --login -c \"" + puts -nonewline $fd "GIT_DIR='$gd'" + puts -nonewline $fd " '$me'" + puts $fd "&\"" + close $fd + } err]} { + error_popup "Cannot write script:\n\n$err" + } + } +} + +proc do_macosx_app {} { + global argv0 env + + set fn [tk_getSaveFile \ + -parent . \ + -title "[appname] ([reponame]): Create Desktop Icon" \ + -initialdir [file join $env(HOME) Desktop] \ + -initialfile "Git [reponame].app"] + if {$fn != {}} { + if {[catch { + set Contents [file join $fn Contents] + set MacOS [file join $Contents MacOS] + set exe [file join $MacOS git-gui] + + file mkdir $MacOS + + set fd [open [file join $Contents Info.plist] w] + puts $fd { + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + git-gui + CFBundleIdentifier + org.spearce.git-gui + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + NSPrincipalClass + NSApplication + +} + close $fd + + set fd [open $exe w] + set gd [file normalize [gitdir]] + set ep [file normalize [gitexec]] + regsub -all ' $gd "'\\''" gd + regsub -all ' $ep "'\\''" ep + puts $fd "#!/bin/sh" + foreach name [array names env] { + if {[string match GIT_* $name]} { + regsub -all ' $env($name) "'\\''" v + puts $fd "export $name='$v'" + } + } + puts $fd "export PATH='$ep':\$PATH" + puts $fd "export GIT_DIR='$gd'" + puts $fd "exec [file normalize $argv0]" + close $fd + + file attributes $exe -permissions u+x,g+x,o+x + } err]} { + error_popup "Cannot write icon:\n\n$err" + } + } +} diff --git a/lib/transport.tcl b/lib/transport.tcl new file mode 100644 index 0000000000..ce6fc45eba --- /dev/null +++ b/lib/transport.tcl @@ -0,0 +1,162 @@ +# git-gui transport (fetch/push) support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc fetch_from {remote} { + set w [new_console \ + "fetch $remote" \ + "Fetching new changes from $remote"] + set cmd [list git fetch] + lappend cmd $remote + console_exec $w $cmd console_done +} + +proc push_to {remote} { + set w [new_console \ + "push $remote" \ + "Pushing changes to $remote"] + set cmd [list git push] + lappend cmd -v + lappend cmd $remote + console_exec $w $cmd console_done +} + +proc start_push_anywhere_action {w} { + global push_urltype push_remote push_url push_thin push_tags + + set r_url {} + switch -- $push_urltype { + remote {set r_url $push_remote} + url {set r_url $push_url} + } + if {$r_url eq {}} return + + set cmd [list git push] + lappend cmd -v + if {$push_thin} { + lappend cmd --thin + } + if {$push_tags} { + lappend cmd --tags + } + lappend cmd $r_url + set cnt 0 + foreach i [$w.source.l curselection] { + set b [$w.source.l get $i] + lappend cmd "refs/heads/$b:refs/heads/$b" + incr cnt + } + if {$cnt == 0} { + return + } elseif {$cnt == 1} { + set unit branch + } else { + set unit branches + } + + set cons [new_console "push $r_url" "Pushing $cnt $unit to $r_url"] + console_exec $cons $cmd console_done + destroy $w +} + +trace add variable push_remote write \ + [list radio_selector push_urltype remote] + +proc do_push_anywhere {} { + global all_heads all_remotes current_branch + global push_urltype push_remote push_url push_thin push_tags + + set w .push_setup + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text {Push Branches} -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.create -text Push \ + -default active \ + -command [list start_push_anywhere_action $w] + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} \ + -default normal \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.source -text {Source Branches} + listbox $w.source.l \ + -height 10 \ + -width 70 \ + -selectmode extended \ + -yscrollcommand [list $w.source.sby set] + foreach h $all_heads { + $w.source.l insert end $h + if {$h eq $current_branch} { + $w.source.l select set end + } + } + scrollbar $w.source.sby -command [list $w.source.l yview] + pack $w.source.sby -side right -fill y + pack $w.source.l -side left -fill both -expand 1 + pack $w.source -fill both -expand 1 -pady 5 -padx 5 + + labelframe $w.dest -text {Destination Repository} + if {$all_remotes ne {}} { + radiobutton $w.dest.remote_r \ + -text {Remote:} \ + -value remote \ + -variable push_urltype + eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes + grid $w.dest.remote_r $w.dest.remote_m -sticky w + if {[lsearch -sorted -exact $all_remotes origin] != -1} { + set push_remote origin + } else { + set push_remote [lindex $all_remotes 0] + } + set push_urltype remote + } else { + set push_urltype url + } + radiobutton $w.dest.url_r \ + -text {Arbitrary URL:} \ + -value url \ + -variable push_urltype + entry $w.dest.url_t \ + -borderwidth 1 \ + -relief sunken \ + -width 50 \ + -textvariable push_url \ + -validate key \ + -validatecommand { + if {%d == 1 && [regexp {\s} %S]} {return 0} + if {%d == 1 && [string length %S] > 0} { + set push_urltype url + } + return 1 + } + grid $w.dest.url_r $w.dest.url_t -sticky we -padx {0 5} + grid columnconfigure $w.dest 1 -weight 1 + pack $w.dest -anchor nw -fill x -pady 5 -padx 5 + + labelframe $w.options -text {Transfer Options} + checkbutton $w.options.thin \ + -text {Use thin pack (for slow network connections)} \ + -variable push_thin + grid $w.options.thin -columnspan 2 -sticky w + checkbutton $w.options.tags \ + -text {Include tags} \ + -variable push_tags + grid $w.options.tags -columnspan 2 -sticky w + grid columnconfigure $w.options 1 -weight 1 + pack $w.options -anchor nw -fill x -pady 5 -padx 5 + + set push_url {} + set push_thin 0 + set push_tags 0 + + bind $w "grab $w; focus $w.buttons.create" + bind $w "destroy $w" + bind $w [list start_push_anywhere_action $w] + wm title $w "[appname] ([reponame]): Push" + tkwait window $w +} From a35d65d9c8a2a6a10d369c00e22aded6fbdff2a9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 1 May 2007 02:59:53 -0400 Subject: [PATCH 36/77] git-gui: Move console procs into their own namespace To help modularize git-gui better I'm isolating the code and variables required to handle our little console windows into their own namespace. This way we can say console::new rather than new_console, and the hidden internal procs to create the window and read data from our filehandle are off in their own private little land, where most users don't see them. Signed-off-by: Shawn O. Pearce --- lib/console.tcl | 60 ++++++++++++++++++++++++++++++----------------- lib/database.tcl | 16 ++++++------- lib/merge.tcl | 6 ++--- lib/transport.tcl | 14 ++++++----- 4 files changed, 57 insertions(+), 39 deletions(-) diff --git a/lib/console.tcl b/lib/console.tcl index e40ec9639b..75f3e0463b 100644 --- a/lib/console.tcl +++ b/lib/console.tcl @@ -1,17 +1,25 @@ # git-gui console support # Copyright (C) 2006, 2007 Shawn Pearce -set next_console_id 0 +namespace eval console { + +variable next_console_id 0 +variable console_data +variable console_cr + +proc new {short_title long_title} { + variable next_console_id + variable console_data -proc new_console {short_title long_title} { - global next_console_id console_data set w .console[incr next_console_id] set console_data($w) [list $short_title $long_title] - return [console_init $w] + return [_init $w] } -proc console_init {w} { - global console_cr console_data M1B +proc _init {w} { + global M1B + variable console_cr + variable console_data set console_cr($w) 1.0 toplevel $w @@ -63,7 +71,7 @@ proc console_init {w} { return $w } -proc console_exec {w cmd after} { +proc exec {w cmd {after {}}} { # -- Cygwin's Tcl tosses the enviroment when we exec our child. # But most users need that so we have to relogin. :-( # @@ -78,15 +86,16 @@ proc console_exec {w cmd after} { set fd_f [open $cmd r] fconfigure $fd_f -blocking 0 -translation binary - fileevent $fd_f readable [list console_read $w $fd_f $after] + fileevent $fd_f readable \ + [namespace code [list _read $w $fd_f $after]] } -proc console_read {w fd after} { - global console_cr +proc _read {w fd after} { + variable console_cr set buf [read $fd] if {$buf ne {}} { - if {![winfo exists $w]} {console_init $w} + if {![winfo exists $w]} {_init $w} $w.m.t conf -state normal set c 0 set n [string length $buf] @@ -120,36 +129,41 @@ proc console_read {w fd after} { } else { set ok 1 } - uplevel #0 $after $w $ok + if {$after ne {}} { + uplevel #0 $after $w $ok + } else { + done $w $ok + } return } fconfigure $fd -blocking 0 } -proc console_chain {cmdlist w {ok 1}} { +proc chain {cmdlist w {ok 1}} { if {$ok} { if {[llength $cmdlist] == 0} { - console_done $w $ok + done $w $ok return } set cmd [lindex $cmdlist 0] set cmdlist [lrange $cmdlist 1 end] - if {[lindex $cmd 0] eq {console_exec}} { - console_exec $w \ + if {[lindex $cmd 0] eq {exec}} { + exec $w \ [lindex $cmd 1] \ - [list console_chain $cmdlist] + [namespace code [list chain $cmdlist]] } else { uplevel #0 $cmd $cmdlist $w $ok } } else { - console_done $w $ok + done $w $ok } } -proc console_done {args} { - global console_cr console_data +proc done {args} { + variable console_cr + variable console_data switch -- [llength $args] { 2 { @@ -161,7 +175,7 @@ proc console_done {args} { set ok [lindex $args 2] } default { - error "wrong number of args: console_done ?ignored? w ok" + error "wrong number of args: done ?ignored? w ok" } } @@ -173,7 +187,7 @@ proc console_done {args} { } } else { if {![winfo exists $w]} { - console_init $w + _init $w } $w.m.s conf -background red -text {Error: Command Failed} $w.ok conf -state normal @@ -183,3 +197,5 @@ proc console_done {args} { array unset console_cr $w array unset console_data $w } + +} diff --git a/lib/database.tcl b/lib/database.tcl index e31466fb50..73058a8269 100644 --- a/lib/database.tcl +++ b/lib/database.tcl @@ -69,21 +69,21 @@ proc do_stats {} { } proc do_gc {} { - set w [new_console {gc} {Compressing the object database}] - console_chain { - {console_exec {git pack-refs --prune}} - {console_exec {git reflog expire --all}} - {console_exec {git repack -a -d -l}} - {console_exec {git rerere gc}} + set w [console::new {gc} {Compressing the object database}] + console::chain { + {exec {git pack-refs --prune}} + {exec {git reflog expire --all}} + {exec {git repack -a -d -l}} + {exec {git rerere gc}} } $w } proc do_fsck_objects {} { - set w [new_console {fsck-objects} \ + set w [console::new {fsck-objects} \ {Verifying the object database with fsck-objects}] set cmd [list git fsck-objects] lappend cmd --full lappend cmd --cache lappend cmd --strict - console_exec $w $cmd console_done + console::exec $w $cmd } diff --git a/lib/merge.tcl b/lib/merge.tcl index 75724a930f..e0e84aeabe 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -120,14 +120,14 @@ Please select fewer branches. To merge more than 15 branches, merge the branche set msg "Merging $current_branch, [join $names {, }]" set ui_status_value "$msg..." - set cons [new_console "Merge" $msg] - console_exec $cons $cmd [list finish_merge $revcnt] + set cons [console::new "Merge" $msg] + console::exec $cons $cmd [list finish_merge $revcnt] bind $w {} destroy $w } proc finish_merge {revcnt w ok} { - console_done $w $ok + console::done $w $ok if {$ok} { set msg {Merge completed successfully.} } else { diff --git a/lib/transport.tcl b/lib/transport.tcl index ce6fc45eba..c0e7d20fce 100644 --- a/lib/transport.tcl +++ b/lib/transport.tcl @@ -2,22 +2,22 @@ # Copyright (C) 2006, 2007 Shawn Pearce proc fetch_from {remote} { - set w [new_console \ + set w [console::new \ "fetch $remote" \ "Fetching new changes from $remote"] set cmd [list git fetch] lappend cmd $remote - console_exec $w $cmd console_done + console::exec $w $cmd } proc push_to {remote} { - set w [new_console \ + set w [console::new \ "push $remote" \ "Pushing changes to $remote"] set cmd [list git push] lappend cmd -v lappend cmd $remote - console_exec $w $cmd console_done + console::exec $w $cmd } proc start_push_anywhere_action {w} { @@ -53,8 +53,10 @@ proc start_push_anywhere_action {w} { set unit branches } - set cons [new_console "push $r_url" "Pushing $cnt $unit to $r_url"] - console_exec $cons $cmd console_done + set cons [console::new \ + "push $r_url" \ + "Pushing $cnt $unit to $r_url"] + console::exec $cons $cmd destroy $w } From 60aa065f6958a888d14178ca94f17a60100d3709 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 1 May 2007 15:51:09 -0400 Subject: [PATCH 37/77] git-gui: Allow vi keys to scroll the diff/blame regions Users who are used to vi and recent versions of gitk may want to scroll the diff region using vi style keybindings. Since these aren't bound to anything else and that widget does not accept focus for data input, we can easily support that too. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 6 ++++++ lib/blame.tcl | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index 72673c627a..dc6664c68e 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2038,6 +2038,12 @@ bind $ui_diff {catch {%W yview scroll -1 units};break} bind $ui_diff {catch {%W yview scroll 1 units};break} bind $ui_diff {catch {%W xview scroll -1 units};break} bind $ui_diff {catch {%W xview scroll 1 units};break} +bind $ui_diff {catch {%W yview scroll -1 units};break} +bind $ui_diff {catch {%W yview scroll 1 units};break} +bind $ui_diff {catch {%W xview scroll -1 units};break} +bind $ui_diff {catch {%W xview scroll 1 units};break} +bind $ui_diff {catch {%W yview scroll -1 pages};break} +bind $ui_diff {catch {%W yview scroll 1 pages};break} bind $ui_diff {focus %W} if {[is_enabled branch]} { diff --git a/lib/blame.tcl b/lib/blame.tcl index c276fa9852..6d894e52d4 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -125,6 +125,23 @@ proc show_blame {commit path} { " } + foreach i [list \ + $w.out.loaded_t \ + $w.out.linenumber_t \ + $w.out.file_t \ + $w.cm.t] { + bind $i {catch {%W yview scroll -1 units};break} + bind $i {catch {%W yview scroll 1 units};break} + bind $i {catch {%W xview scroll -1 units};break} + bind $i {catch {%W xview scroll 1 units};break} + bind $i {catch {%W yview scroll -1 units};break} + bind $i {catch {%W yview scroll 1 units};break} + bind $i {catch {%W xview scroll -1 units};break} + bind $i {catch {%W xview scroll 1 units};break} + bind $i {catch {%W yview scroll -1 pages};break} + bind $i {catch {%W yview scroll 1 pages};break} + } + bind $w.cm.t "focus $w.cm.t" bind $tl "focus $tl" bind $tl " From a6c9b081b6860816615e84b75bbc7916aab184e9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 2 May 2007 13:56:27 -0400 Subject: [PATCH 38/77] git-gui: Move merge support into a namespace Like the console procs I have moved the code related to merge support into their own namespace, so that they are isolated from the rest of the world. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 6 +++--- lib/merge.tcl | 31 ++++++++++++++++++------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index dc6664c68e..46358258bb 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1376,7 +1376,7 @@ if {[is_enabled branch]} { [.mbar.branch index last] -state] .mbar.branch add command -label {Reset...} \ - -command do_reset_hard + -command merge::reset_hard lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] } @@ -1449,11 +1449,11 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { if {[is_enabled branch]} { menu .mbar.merge .mbar.merge add command -label {Local Merge...} \ - -command do_local_merge + -command merge::dialog lappend disable_on_lock \ [list .mbar.merge entryconf [.mbar.merge index last] -state] .mbar.merge add command -label {Abort Merge...} \ - -command do_reset_hard + -command merge::reset_hard lappend disable_on_lock \ [list .mbar.merge entryconf [.mbar.merge index last] -state] diff --git a/lib/merge.tcl b/lib/merge.tcl index e0e84aeabe..21cd83d9c8 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -1,7 +1,9 @@ # git-gui branch merge support # Copyright (C) 2006, 2007 Shawn Pearce -proc can_merge {} { +namespace eval merge { + +proc _can_merge {} { global HEAD commit_type file_states if {[string match amend* $commit_type]} { @@ -61,7 +63,7 @@ You should complete the current commit before starting a merge. Doing so will h return 1 } -proc visualize_local_merge {w} { +proc _visualize {w} { set revs {} foreach i [$w.source.l curselection] { lappend revs [$w.source.l get $i] @@ -71,7 +73,7 @@ proc visualize_local_merge {w} { do_gitk $revs } -proc start_local_merge_action {w} { +proc _start {w} { global HEAD ui_status_value current_branch set cmd [list git merge] @@ -121,12 +123,12 @@ Please select fewer branches. To merge more than 15 branches, merge the branche set msg "Merging $current_branch, [join $names {, }]" set ui_status_value "$msg..." set cons [console::new "Merge" $msg] - console::exec $cons $cmd [list finish_merge $revcnt] + console::exec $cons $cmd [namespace code [list _finish $revcnt]] bind $w {} destroy $w } -proc finish_merge {revcnt w ok} { +proc _finish {revcnt w ok} { console::done $w $ok if {$ok} { set msg {Merge completed successfully.} @@ -144,7 +146,8 @@ You can attempt this merge again by merging only one branch at a time." $w set fd [open "| git read-tree --reset -u HEAD" r] fconfigure $fd -blocking 0 -translation binary - fileevent $fd readable [list reset_hard_wait $fd] + fileevent $fd readable \ + [namespace code [list _reset_wait $fd]] set ui_status_value {Aborting... please wait...} return } @@ -155,10 +158,10 @@ You can attempt this merge again by merging only one branch at a time." $w rescan [list set ui_status_value $msg] } -proc do_local_merge {} { +proc dialog {} { global current_branch - if {![can_merge]} return + if {![_can_merge]} return set w .merge_setup toplevel $w @@ -171,10 +174,10 @@ proc do_local_merge {} { frame $w.buttons button $w.buttons.visualize -text Visualize \ - -command [list visualize_local_merge $w] + -command [namespace code [list _visualize $w]] pack $w.buttons.visualize -side left button $w.buttons.create -text Merge \ - -command [list start_local_merge_action $w] + -command [namespace code [list _start $w]] pack $w.buttons.create -side right button $w.buttons.cancel -text {Cancel} \ -command [list destroy $w] @@ -226,7 +229,7 @@ proc do_local_merge {} { tkwait window $w } -proc do_reset_hard {} { +proc reset_hard {} { global HEAD commit_type file_states if {[string match amend* $commit_type]} { @@ -252,14 +255,14 @@ Aborting the current $op will cause *ALL* uncommitted changes to be lost. Continue with aborting the current $op?"] eq {yes}} { set fd [open "| git read-tree --reset -u HEAD" r] fconfigure $fd -blocking 0 -translation binary - fileevent $fd readable [list reset_hard_wait $fd] + fileevent $fd readable [namespace code [list _reset_wait $fd]] set ui_status_value {Aborting... please wait...} } else { unlock_index } } -proc reset_hard_wait {fd} { +proc _reset_wait {fd} { global ui_comm read $fd @@ -279,3 +282,5 @@ proc reset_hard_wait {fd} { rescan {set ui_status_value {Abort completed. Ready.}} } } + +} From 349f92e3a25216b6f79c58044d74b61c89de20b8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 2 May 2007 14:25:22 -0400 Subject: [PATCH 39/77] git-gui: Show all possible branches for merge Johannes Sixt pointed out that git-gui was randomly selecting which branch (or tag!) it will show in the merge dialog when more than one ref points at the same commit. This can be a problem for the user if they want to merge a branch, but the ref that git-gui selected to display was actually a tag that points at the commit at the tip of that branch. Since the user is looking for the branch, and not the tag, its confusing to not find it, and worse, merging the tag causes git-merge to generate a different message than if the branch was selected. While I am in here and am messing around I have changed the for-each-ref usage to take advantage of its --tcl formatting, and to fetch the subject line of the commit (or tag) we are looking at. This way we could present the subject line in the UI to the user, given them an even better chance to select the correct branch. Signed-off-by: Shawn O. Pearce --- lib/merge.tcl | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/merge.tcl b/lib/merge.tcl index 21cd83d9c8..8a07660e15 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -195,17 +195,22 @@ proc dialog {} { pack $w.source.l -side left -fill both -expand 1 pack $w.source -fill both -expand 1 -pady 5 -padx 5 - set cmd [list git for-each-ref] - lappend cmd {--format=%(objectname) %(*objectname) %(refname)} + set fmt {list %(objectname) %(*objectname) %(refname) %(subject)} + set cmd [list git for-each-ref --tcl --format=$fmt] lappend cmd refs/heads lappend cmd refs/remotes lappend cmd refs/tags set fr_fd [open "| $cmd" r] fconfigure $fr_fd -translation binary while {[gets $fr_fd line] > 0} { - set line [split $line { }] - set sha1([lindex $line 0]) [lindex $line 2] - set sha1([lindex $line 1]) [lindex $line 2] + set line [eval $line] + set ref [lindex $line 2] + regsub ^refs/(heads|remotes|tags)/ $ref {} ref + set subj($ref) [lindex $line 3] + lappend sha1([lindex $line 0]) $ref + if {[lindex $line 1] ne {}} { + lappend sha1([lindex $line 1]) $ref + } } close $fr_fd @@ -213,8 +218,9 @@ proc dialog {} { set fr_fd [open "| git rev-list --all --not HEAD"] while {[gets $fr_fd line] > 0} { if {[catch {set ref $sha1($line)}]} continue - regsub ^refs/(heads|remotes|tags)/ $ref {} ref - lappend to_show $ref + foreach n $ref { + lappend to_show $n + } } close $fr_fd From 1fc4ba86f80c1365943abac54679dd1924a6737b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 3 May 2007 18:21:39 -0400 Subject: [PATCH 40/77] git-gui: Include commit id/subject in merge choices When merging branches using our local merge feature it can be handy to know the first few digits of the commit the ref points at as well as the short description of the branch name. Unfortunately I'm unable to use three listboxes in a row, as Tcl freaks out and refuses to let me have a selection in more than one of them at any given point in time. So instead we use a fixed width font in the existing listbox and organize the data into three columns. Not nearly as nice looking, but users can continue to use the listbox's features. Signed-off-by: Shawn O. Pearce --- lib/merge.tcl | 105 +++++++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/lib/merge.tcl b/lib/merge.tcl index 8a07660e15..304490a276 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -63,28 +63,28 @@ You should complete the current commit before starting a merge. Doing so will h return 1 } -proc _visualize {w} { - set revs {} +proc _refs {w list} { + set r {} foreach i [$w.source.l curselection] { - lappend revs [$w.source.l get $i] + lappend r [lindex [lindex $list $i] 0] } + return $r +} + +proc _visualize {w list} { + set revs [_refs $w $list] if {$revs eq {}} return lappend revs --not HEAD do_gitk $revs } -proc _start {w} { +proc _start {w list} { global HEAD ui_status_value current_branch set cmd [list git merge] - set names {} - set revcnt 0 - foreach i [$w.source.l curselection] { - set b [$w.source.l get $i] - lappend cmd $b - lappend names $b - incr revcnt - } + set names [_refs $w $list] + set revcnt [llength $names] + append cmd { } $names if {$revcnt == 0} { return @@ -163,38 +163,6 @@ proc dialog {} { if {![_can_merge]} return - set w .merge_setup - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header \ - -text "Merge Into $current_branch" \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.visualize -text Visualize \ - -command [namespace code [list _visualize $w]] - pack $w.buttons.visualize -side left - button $w.buttons.create -text Merge \ - -command [namespace code [list _start $w]] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.source -text {Source Branches} - listbox $w.source.l \ - -height 10 \ - -width 70 \ - -selectmode extended \ - -yscrollcommand [list $w.source.sby set] - scrollbar $w.source.sby -command [list $w.source.l yview] - pack $w.source.sby -side right -fill y - pack $w.source.l -side left -fill both -expand 1 - pack $w.source -fill both -expand 1 -pady 5 -padx 5 - set fmt {list %(objectname) %(*objectname) %(refname) %(subject)} set cmd [list git for-each-ref --tcl --format=$fmt] lappend cmd refs/heads @@ -219,16 +187,57 @@ proc dialog {} { while {[gets $fr_fd line] > 0} { if {[catch {set ref $sha1($line)}]} continue foreach n $ref { - lappend to_show $n + lappend to_show [list $n $line] } } close $fr_fd + set to_show [lsort -unique $to_show] - foreach ref [lsort -unique $to_show] { - $w.source.l insert end $ref + set w .merge_setup + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header \ + -text "Merge Into $current_branch" \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.visualize -text Visualize \ + -command [namespace code [list _visualize $w $to_show]] + pack $w.buttons.visualize -side left + button $w.buttons.create -text Merge \ + -command [namespace code [list _start $w $to_show]] + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.source -text {Source Branches} + listbox $w.source.l \ + -height 10 \ + -width 70 \ + -font font_diff \ + -selectmode extended \ + -yscrollcommand [list $w.source.sby set] + scrollbar $w.source.sby -command [list $w.source.l yview] + pack $w.source.sby -side right -fill y + pack $w.source.l -side left -fill both -expand 1 + pack $w.source -fill both -expand 1 -pady 5 -padx 5 + + foreach ref $to_show { + set n [lindex $ref 0] + if {[string length $n] > 20} { + set n "[string range $n 0 16]..." + } + $w.source.l insert end [format {%s %-20s %s} \ + [string range [lindex $ref 1] 0 5] \ + $n \ + $subj([lindex $ref 0])] } - bind $w "grab $w" + bind $w "grab $w; focus $w.source.l" bind $w "unlock_index;destroy $w" bind $w unlock_index wm title $w "[appname] ([reponame]): Merge" From ebcaadabcb90eaf35a3bd69a50cccce4be4dcc2c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 5 May 2007 02:28:41 -0400 Subject: [PATCH 41/77] git-gui: Use vi-like keys in merge dialog Since we support vi-like keys for scrolling in other UI contexts we can easily do so here too. Tk's handy little `event generate' makes this a lot easier than I thought it would be. We may want to go back and fix some of the other vi-like bindings to redirect to the arrow and pageup/pagedown keys, rather than running the view changes directly. I've bound 'v' to visualize, as this is a somewhat common thing to want to do in the merge dialog. Control (or Command) Return is also bound to start the merge, much as it is bound in the main window to activate the commit. Signed-off-by: Shawn O. Pearce --- lib/merge.tcl | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/merge.tcl b/lib/merge.tcl index 304490a276..3dce856e5e 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -160,6 +160,7 @@ You can attempt this merge again by merging only one branch at a time." $w proc dialog {} { global current_branch + global M1B if {![_can_merge]} return @@ -197,20 +198,20 @@ proc dialog {} { toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + set _visualize [namespace code [list _visualize $w $to_show]] + set _start [namespace code [list _start $w $to_show]] + label $w.header \ -text "Merge Into $current_branch" \ -font font_uibold pack $w.header -side top -fill x frame $w.buttons - button $w.buttons.visualize -text Visualize \ - -command [namespace code [list _visualize $w $to_show]] + button $w.buttons.visualize -text Visualize -command $_visualize pack $w.buttons.visualize -side left - button $w.buttons.create -text Merge \ - -command [namespace code [list _start $w $to_show]] + button $w.buttons.create -text Merge -command $_start pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -command [list destroy $w] + button $w.buttons.cancel -text {Cancel} -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 @@ -237,6 +238,13 @@ proc dialog {} { $subj([lindex $ref 0])] } + bind $w.source.l [list event generate %W ] + bind $w.source.l [list event generate %W ] + bind $w.source.l [list event generate %W ] + bind $w.source.l [list event generate %W ] + bind $w.source.l $_visualize + + bind $w <$M1B-Key-Return> $_start bind $w "grab $w; focus $w.source.l" bind $w "unlock_index;destroy $w" bind $w unlock_index From a42cbacc1133f57de23734e5d52324208606806e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 7 May 2007 23:53:06 -0400 Subject: [PATCH 42/77] Remove duplicate exports from Makefile We already export these variables earlier in the Makefile, right after they were 'declared'. There is no point in doing so again. Signed-off-by: Shawn O. Pearce --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e89ccd4965..7cf146ba7b 100644 --- a/Makefile +++ b/Makefile @@ -709,7 +709,7 @@ LIB_OBJS += $(COMPAT_OBJS) ALL_CFLAGS += $(BASIC_CFLAGS) ALL_LDFLAGS += $(BASIC_LDFLAGS) -export prefix gitexecdir TAR INSTALL DESTDIR SHELL_PATH template_dir +export TAR INSTALL DESTDIR SHELL_PATH ### Build rules From 52c80037e423c7e7806cf2f198ed978dc36fa654 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Mon, 7 May 2007 23:34:26 -0400 Subject: [PATCH 43/77] user-manual: fix clone and fetch typos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More typo fixes from Santi Béjar, plus a couple other mistakes I noticed along the way. Cc: Santi Béjar Signed-off-by: "J. Bruce Fields" Signed-off-by: Junio C Hamano --- Documentation/user-manual.txt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 67f5b9b6ab..13db9699c1 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -1836,12 +1836,12 @@ Now, assume your personal repository is in the directory ~/proj. We first create a new clone of the repository: ------------------------------------------------- -$ git clone --bare proj.git +$ git clone --bare ~/proj proj.git ------------------------------------------------- -The resulting directory proj.git will contains a "bare" git -repository--it is just the contents of the ".git" directory, without -a checked-out copy of a working directory. +The resulting directory proj.git contains a "bare" git repository--it is +just the contents of the ".git" directory, without a checked-out copy of +a working directory. Next, copy proj.git to the server where you plan to host the public repository. You can use scp, rsync, or whatever is most @@ -2372,9 +2372,8 @@ $ git config remote.example.fetch master:refs/remotes/example/master then the following commands will all do the same thing: ------------------------------------------------- -$ git fetch git://example.com/proj.git master:ref/remotes/example/master -$ git fetch example master:ref/remotes/example/master -$ git fetch example example/master +$ git fetch git://example.com/proj.git master:refs/remotes/example/master +$ git fetch example master:refs/remotes/example/master $ git fetch example ------------------------------------------------- From b51be13c9c83621551824876ee037151a92f0323 Mon Sep 17 00:00:00 2001 From: Amos Waterland Date: Tue, 8 May 2007 00:46:08 -0400 Subject: [PATCH 44/77] wcwidth redeclaration Build fails for git 1.5.1.3 on AIX, with the message: utf8.c:66: error: conflicting types for 'wcwidth' /.../lib/gcc/powerpc-ibm-aix5.3.0.0/4.0.3/include/string.h:266: error: previous declaration of 'wcwidth' was here Fix this by renaming our static variant to our own name. Signed-off-by: Amos Waterland Signed-off-by: Junio C Hamano --- utf8.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utf8.c b/utf8.c index a2965c9c11..4efef6faf7 100644 --- a/utf8.c +++ b/utf8.c @@ -62,7 +62,7 @@ static int bisearch(ucs_char_t ucs, const struct interval *table, int max) { * in ISO 10646. */ -static int wcwidth(ucs_char_t ch) +static int git_wcwidth(ucs_char_t ch) { /* * Sorted list of non-overlapping intervals of non-spacing characters, @@ -207,7 +207,7 @@ invalid: return 0; } - return wcwidth(ch); + return git_wcwidth(ch); } int is_utf8(const char *text) From a1a4975824e8f9f88a8c96ae908c488a2c6047c5 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Tue, 8 May 2007 13:33:06 +0200 Subject: [PATCH 45/77] git-gui: Call changes "Staged" and "Unstaged" in file list titles. All menu entries talk about "staging" and "unstaging" changes, but the titles of the file lists use different wording, which may confuse newcomers. Signed-off-by: Johannes Sixt Signed-off-by: Shawn O. Pearce --- git-gui.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index ae881336da..4cf340dfe7 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -5496,7 +5496,7 @@ pack .vpane -anchor n -side top -fill both -expand 1 # -- Index File List # frame .vpane.files.index -height 100 -width 200 -label .vpane.files.index.title -text {Changes To Be Committed} \ +label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \ -background green \ -font font_ui text $ui_index -background white -borderwidth 0 \ @@ -5518,7 +5518,7 @@ pack $ui_index -side left -fill both -expand 1 # -- Working Directory File List # frame .vpane.files.workdir -height 100 -width 200 -label .vpane.files.workdir.title -text {Changed But Not Updated} \ +label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \ -background red \ -font font_ui text $ui_workdir -background white -borderwidth 0 \ From cc1f83fbdff1ae248c91ab231ab4100351e1ba0a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 8 May 2007 21:38:46 -0400 Subject: [PATCH 46/77] git-gui: Allow shift-{k,j} to select a range of branches to merge I found it useful to be able to use j/k (vi-like keys) to move up and down the list of branches to merge and shift-j/k to do the selection, much as shift-up/down (arrow keys) would alter the selection. Signed-off-by: Shawn O. Pearce --- lib/merge.tcl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/merge.tcl b/lib/merge.tcl index 3dce856e5e..642d6eefbd 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -238,6 +238,8 @@ proc dialog {} { $subj([lindex $ref 0])] } + bind $w.source.l [list event generate %W ] + bind $w.source.l [list event generate %W ] bind $w.source.l [list event generate %W ] bind $w.source.l [list event generate %W ] bind $w.source.l [list event generate %W ] From 1f07c4e5cefec88d825045ade24eee71f6a2df47 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 8 May 2007 19:54:05 -0400 Subject: [PATCH 47/77] git-gui: Define a simple class/method system As most of the git-gui interface is based upon "meta-widgets" that need to carry around a good deal of state (e.g. console windows, browser windows, blame viewer) we have a good deal of messy code that tries to store this meta-widget state in global arrays, where keys into the array are formed from a union of a unique "object instance id" and the field name. This is a simple class system for Tcl that allows us to hide much of that mess by making Tcl do what it does best; process strings to manipulate its own code during startup. Each object instance is placed into its own namespace. The namespace is created when the object instance is created and the namespace is destroyed when the object instance is removed from the system. Within that namespace we place variables for each field within the class; these variables can themselves be scalar values or full-blown Tcl arrays. A simple class might be defined as: class map { field data field size 0 constructor {} { return $this } method set {name value} { set data($name) $value incr size } method size {} { return $size } ifdeleted { return 0 } } All fields must be declared before any constructors or methods. This allows our class to generate a list of the fields so it can properly alter the definition of the constructor and method bodies prior to passing them off to Tcl for definition with proc. A field may optionally be given a default/initial value. This can only be done for non-array type fields. Constructors are given full access to all fields of the class, so they can initialize the data values. The default values of fields (if any) are set before the constructor runs, and the implicit local variable $this is initialized to the instance identifier. Methods are given access to fields they actually use in their body. Every method has an implicit "this" argument inserted as its first parameter; callers of methods must be sure they supply this value. Some basic optimization tricks are performed (but not much). We try to only upvar (locally bind) fields that are accessed within a method, but we err on the side of caution and may upvar more than we need to. If a variable is accessed only once within a method and that access is by $foo (read) we avoid the upvar and instead use [set foo] to obtain the value. This is slightly faster as Tcl does not need to lookup the variable twice. We also offer some small syntatic sugar for interacting with Tk and the fileevent callback system in Tcl. If a field (say "foo") is used as "@foo" we insert instead the true global variable name of that variable into the body of the constructor or method. This allows easy binding to Tk textvariable options, e.g.: label $w.title -textvariable @title Proper namespace callbacks can also be setup with the special cb proc that is defined in each namespace. [cb _foo a] will invoke the method _foo in the current namespace, passing it $this as the first (implied) parameter and a as the second parameter. This makes it very simple to connect an object instance to a -command option for a Tk widget or to a fileevent readable or writable for a file channel. Signed-off-by: Shawn O. Pearce --- Makefile | 5 +- lib/class.tcl | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 lib/class.tcl diff --git a/Makefile b/Makefile index ba1e33ba84..e73b6453d9 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,10 @@ $(GITGUI_BUILT_INS): git-gui $(QUIET_BUILT_IN)rm -f $@ && ln git-gui $@ lib/tclIndex: $(ALL_LIBFILES) - $(QUIET_INDEX)echo auto_mkindex lib '*.tcl' | $(TCL_PATH) + $(QUIET_INDEX)echo \ + source lib/class.tcl \; \ + auto_mkindex lib '*.tcl' \ + | $(TCL_PATH) # These can record GITGUI_VERSION $(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE GIT-GUI-VARS diff --git a/lib/class.tcl b/lib/class.tcl new file mode 100644 index 0000000000..c1291989aa --- /dev/null +++ b/lib/class.tcl @@ -0,0 +1,153 @@ +# git-gui simple class/object fake-alike +# Copyright (C) 2007 Shawn Pearce + +proc class {class body} { + if {[namespace exists $class]} { + error "class $class already declared" + } + namespace eval $class { + variable __nextid 0 + variable __sealed 0 + variable __field_list {} + variable __field_array + + proc cb {name args} { + upvar this this + set args [linsert $args 0 $name $this] + return [uplevel [list namespace code $args]] + } + } + namespace eval $class $body +} + +proc field {name args} { + set class [uplevel {namespace current}] + variable ${class}::__sealed + variable ${class}::__field_array + + switch [llength $args] { + 0 { set new [list $name] } + 1 { set new [list $name [lindex $args 0]] } + default { error "wrong # args: field name value?" } + } + + if {$__sealed} { + error "class $class is sealed (cannot add new fields)" + } + + if {[catch {set old $__field_array($name)}]} { + variable ${class}::__field_list + lappend __field_list $new + set __field_array($name) 1 + } else { + error "field $name already declared" + } +} + +proc constructor {name params body} { + set class [uplevel {namespace current}] + set ${class}::__sealed 1 + variable ${class}::__field_list + set mbodyc {} + + append mbodyc {set this } $class + append mbodyc {::__o[incr } $class {::__nextid]} \; + append mbodyc {namespace eval $this {}} \; + + if {$__field_list ne {}} { + append mbodyc {upvar #0} + foreach n $__field_list { + set n [lindex $n 0] + append mbodyc { ${this}::} $n { } $n + regsub -all @$n\\M $body "\${this}::$n" body + } + append mbodyc \; + foreach n $__field_list { + if {[llength $n] == 2} { + append mbodyc \ + {set } [lindex $n 0] { } [list [lindex $n 1]] \; + } + } + } + append mbodyc $body + namespace eval $class [list proc $name $params $mbodyc] +} + +proc method {name params body {deleted {}} {del_body {}}} { + set class [uplevel {namespace current}] + set ${class}::__sealed 1 + variable ${class}::__field_list + set params [linsert $params 0 this] + set mbodyc {} + + switch $deleted { + {} {} + ifdeleted { + append mbodyc {if {![namespace exists $this]} } + append mbodyc \{ $del_body \; return \} \; + } + default { + error "wrong # args: method name args body (ifdeleted body)?" + } + } + + set decl {} + foreach n $__field_list { + set n [lindex $n 0] + if {[regexp -- $n\\M $body]} { + if { [regexp -all -- $n\\M $body] == 1 + && [regexp -all -- \\\$$n\\M $body] == 1} { + regsub -all \\\$$n\\M $body "\[set \${this}::$n\]" body + } else { + append decl { ${this}::} $n { } $n + regsub -all @$n\\M $body "\${this}::$n" body + } + } + } + if {$decl ne {}} { + append mbodyc {upvar #0} $decl \; + } + append mbodyc $body + namespace eval $class [list proc $name $params $mbodyc] +} + +proc delete_this {{t {}}} { + if {$t eq {}} { + upvar this this + set t $this + } + if {[namespace exists $t]} {namespace delete $t} +} + +proc make_toplevel {t w} { + upvar $t top $w pfx + if {[winfo ismapped .]} { + upvar this this + regsub -all {::} $this {__} w + set top .$w + set pfx $top + toplevel $top + } else { + set top . + set pfx {} + } +} + + +## auto_mkindex support for class/constructor/method +## +auto_mkindex_parser::command class {name body} { + variable parser + variable contextStack + set contextStack [linsert $contextStack 0 $name] + $parser eval [list _%@namespace eval $name] $body + set contextStack [lrange $contextStack 1 end] +} +auto_mkindex_parser::command constructor {name args} { + variable index + variable scriptFile + append index [list set auto_index([fullname $name])] \ + [format { [list source [file join $dir %s]]} \ + [file split $scriptFile]] "\n" +} + From c74b6c66f00ce51b2c01d20fc5ef0e6429811124 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 8 May 2007 20:33:47 -0400 Subject: [PATCH 48/77] git-gui: Convert browser, console to "class" format Now that we have a slightly easier method of working with per-widget data we should make use of that technique in our browser and console meta-widgets, as both have a decent amount of information that they store on a per-widget basis and our current approach of handling it is difficult to follow. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 4 +- lib/blame.tcl | 2 + lib/browser.tcl | 196 +++++++++++++++++++++-------------------------- lib/console.tcl | 97 +++++++++-------------- lib/database.tcl | 12 +-- lib/merge.tcl | 3 +- 6 files changed, 134 insertions(+), 180 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 84a3b62011..67f993aa4b 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1288,7 +1288,7 @@ menu .mbar.repository .mbar.repository add command \ -label {Browse Current Branch} \ - -command {new_browser $current_branch} + -command {browser::new $current_branch} trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#" .mbar.repository add separator @@ -1581,7 +1581,7 @@ browser { exit 1 } set current_branch [lindex $argv 0] - new_browser $current_branch + browser::new $current_branch return } blame { diff --git a/lib/blame.tcl b/lib/blame.tcl index 6d894e52d4..dc7b22f7b5 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -1,6 +1,8 @@ # git-gui blame viewer # Copyright (C) 2006, 2007 Shawn Pearce +set next_browser_id 0 + proc show_blame {commit path} { global next_browser_id blame_status blame_data diff --git a/lib/browser.tcl b/lib/browser.tcl index 631859ae75..f9a4e1d52c 100644 --- a/lib/browser.tcl +++ b/lib/browser.tcl @@ -1,28 +1,26 @@ # git-gui tree browser # Copyright (C) 2006, 2007 Shawn Pearce -set next_browser_id 0 +class browser { -proc new_browser {commit} { - global next_browser_id cursor_ptr M1B - global browser_commit browser_status browser_stack browser_path browser_busy +field w +field browser_commit +field browser_path +field browser_files {} +field browser_status {Starting...} +field browser_stack {} +field browser_busy 1 - if {[winfo ismapped .]} { - set w .browser[incr next_browser_id] - set tl $w - toplevel $w - } else { - set w {} - set tl . - } - set w_list $w.list.l - set browser_commit($w_list) $commit - set browser_status($w_list) {Starting...} - set browser_stack($w_list) {} - set browser_path($w_list) $browser_commit($w_list): - set browser_busy($w_list) 1 +constructor new {commit} { + global cursor_ptr M1B + make_toplevel top w + wm title $top "[appname] ([reponame]): File Browser" - label $w.path -textvariable browser_path($w_list) \ + set browser_commit $commit + set browser_path $browser_commit: + + label $w.path \ + -textvariable @browser_path \ -anchor w \ -justify left \ -borderwidth 1 \ @@ -31,6 +29,7 @@ proc new_browser {commit} { pack $w.path -anchor w -side top -fill x frame $w.list + set w_list $w.list.l text $w_list -background white -borderwidth 0 \ -cursor $cursor_ptr \ -state disabled \ @@ -49,176 +48,149 @@ proc new_browser {commit} { pack $w_list -side left -fill both -expand 1 pack $w.list -side top -fill both -expand 1 - label $w.status -textvariable browser_status($w_list) \ + label $w.status \ + -textvariable @browser_status \ -anchor w \ -justify left \ -borderwidth 1 \ -relief sunken pack $w.status -anchor w -side bottom -fill x - bind $w_list "browser_click 0 $w_list @%x,%y;break" - bind $w_list "browser_click 1 $w_list @%x,%y;break" - bind $w_list <$M1B-Up> "browser_parent $w_list;break" - bind $w_list <$M1B-Left> "browser_parent $w_list;break" - bind $w_list "browser_move -1 $w_list;break" - bind $w_list "browser_move 1 $w_list;break" - bind $w_list <$M1B-Right> "browser_enter $w_list;break" - bind $w_list "browser_enter $w_list;break" - bind $w_list "browser_page -1 $w_list;break" - bind $w_list "browser_page 1 $w_list;break" + bind $w_list "[cb _click 0 @%x,%y];break" + bind $w_list "[cb _click 1 @%x,%y];break" + bind $w_list <$M1B-Up> "[cb _parent] ;break" + bind $w_list <$M1B-Left> "[cb _parent] ;break" + bind $w_list "[cb _move -1] ;break" + bind $w_list "[cb _move 1] ;break" + bind $w_list <$M1B-Right> "[cb _enter] ;break" + bind $w_list "[cb _enter] ;break" + bind $w_list "[cb _page -1] ;break" + bind $w_list "[cb _page 1] ;break" bind $w_list break bind $w_list break - bind $tl "focus $w" - bind $tl " - array unset browser_buffer $w_list - array unset browser_files $w_list - array unset browser_status $w_list - array unset browser_stack $w_list - array unset browser_path $w_list - array unset browser_commit $w_list - array unset browser_busy $w_list - " - wm title $tl "[appname] ([reponame]): File Browser" - ls_tree $w_list $browser_commit($w_list) {} + bind $w_list [list focus $w_list] + bind $w_list [list delete_this $this] + set w $w_list + _ls $this $browser_commit + return $this } -proc browser_move {dir w} { - global browser_files browser_busy - - if {$browser_busy($w)} return +method _move {dir} { + if {$browser_busy} return set lno [lindex [split [$w index in_sel.first] .] 0] incr lno $dir - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { + if {[lindex $browser_files [expr {$lno - 1}]] ne {}} { $w tag remove in_sel 0.0 end $w tag add in_sel $lno.0 [expr {$lno + 1}].0 $w see $lno.0 } } -proc browser_page {dir w} { - global browser_files browser_busy - - if {$browser_busy($w)} return +method _page {dir} { + if {$browser_busy} return $w yview scroll $dir pages set lno [expr {int( [lindex [$w yview] 0] - * [llength $browser_files($w)] + * [llength $browser_files] + 1)}] - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { + if {[lindex $browser_files [expr {$lno - 1}]] ne {}} { $w tag remove in_sel 0.0 end $w tag add in_sel $lno.0 [expr {$lno + 1}].0 $w see $lno.0 } } -proc browser_parent {w} { - global browser_files browser_status browser_path - global browser_stack browser_busy - - if {$browser_busy($w)} return - set info [lindex $browser_files($w) 0] +method _parent {} { + if {$browser_busy} return + set info [lindex $browser_files 0] if {[lindex $info 0] eq {parent}} { - set parent [lindex $browser_stack($w) end-1] - set browser_stack($w) [lrange $browser_stack($w) 0 end-2] - if {$browser_stack($w) eq {}} { - regsub {:.*$} $browser_path($w) {:} browser_path($w) + set parent [lindex $browser_stack end-1] + set browser_stack [lrange $browser_stack 0 end-2] + if {$browser_stack eq {}} { + regsub {:.*$} $browser_path {:} browser_path } else { - regsub {/[^/]+$} $browser_path($w) {} browser_path($w) + regsub {/[^/]+$} $browser_path {} browser_path } - set browser_status($w) "Loading $browser_path($w)..." - ls_tree $w [lindex $parent 0] [lindex $parent 1] + set browser_status "Loading $browser_path..." + _ls $this [lindex $parent 0] [lindex $parent 1] } } -proc browser_enter {w} { - global browser_files browser_status browser_path - global browser_commit browser_stack browser_busy - - if {$browser_busy($w)} return +method _enter {} { + if {$browser_busy} return set lno [lindex [split [$w index in_sel.first] .] 0] - set info [lindex $browser_files($w) [expr {$lno - 1}]] + set info [lindex $browser_files [expr {$lno - 1}]] if {$info ne {}} { switch -- [lindex $info 0] { parent { - browser_parent $w + _parent $this } tree { set name [lindex $info 2] set escn [escape_path $name] - set browser_status($w) "Loading $escn..." - append browser_path($w) $escn - ls_tree $w [lindex $info 1] $name + set browser_status "Loading $escn..." + append browser_path $escn + _ls $this [lindex $info 1] $name } blob { set name [lindex $info 2] set p {} - foreach n $browser_stack($w) { + foreach n $browser_stack { append p [lindex $n 1] } append p $name - show_blame $browser_commit($w) $p + show_blame $browser_commit $p } } } } -proc browser_click {was_double_click w pos} { - global browser_files browser_busy - - if {$browser_busy($w)} return +method _click {was_double_click pos} { + if {$browser_busy} return set lno [lindex [split [$w index $pos] .] 0] focus $w - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { + if {[lindex $browser_files [expr {$lno - 1}]] ne {}} { $w tag remove in_sel 0.0 end $w tag add in_sel $lno.0 [expr {$lno + 1}].0 if {$was_double_click} { - browser_enter $w + _enter $this } } } -proc ls_tree {w tree_id name} { - global browser_buffer browser_files browser_stack browser_busy - - set browser_buffer($w) {} - set browser_files($w) {} - set browser_busy($w) 1 +method _ls {tree_id {name {}}} { + set browser_buffer {} + set browser_files {} + set browser_busy 1 $w conf -state normal $w tag remove in_sel 0.0 end $w delete 0.0 end - if {$browser_stack($w) ne {}} { + if {$browser_stack ne {}} { $w image create end \ -align center -padx 5 -pady 1 \ -name icon0 \ -image file_uplevel $w insert end {[Up To Parent]} - lappend browser_files($w) parent + lappend browser_files parent } - lappend browser_stack($w) [list $tree_id $name] + lappend browser_stack [list $tree_id $name] $w conf -state disabled set cmd [list git ls-tree -z $tree_id] set fd [open "| $cmd" r] fconfigure $fd -blocking 0 -translation binary -encoding binary - fileevent $fd readable [list read_ls_tree $fd $w] + fileevent $fd readable [cb _read $fd] } -proc read_ls_tree {fd w} { - global browser_buffer browser_files browser_status browser_busy +method _read {fd} { + append browser_buffer [read $fd] + set pck [split $browser_buffer "\0"] + set browser_buffer [lindex $pck end] - if {![winfo exists $w]} { - catch {close $fd} - return - } - - append browser_buffer($w) [read $fd] - set pck [split $browser_buffer($w) "\0"] - set browser_buffer($w) [lindex $pck end] - - set n [llength $browser_files($w)] + set n [llength $browser_files] $w conf -state normal foreach p [lrange $pck 0 end-1] { set info [split $p "\t"] @@ -246,18 +218,22 @@ proc read_ls_tree {fd w} { -name icon[incr n] \ -image $image $w insert end [escape_path $path] - lappend browser_files($w) [list $type $object $path] + lappend browser_files [list $type $object $path] } $w conf -state disabled if {[eof $fd]} { close $fd - set browser_status($w) Ready. - set browser_busy($w) 0 - array unset browser_buffer $w + set browser_status Ready. + set browser_busy 0 + unset browser_buffer if {$n > 0} { $w tag add in_sel 1.0 2.0 focus -force $w } } +} ifdeleted { + catch {close $fd} +} + } diff --git a/lib/console.tcl b/lib/console.tcl index 75f3e0463b..8c112f3a89 100644 --- a/lib/console.tcl +++ b/lib/console.tcl @@ -1,30 +1,29 @@ # git-gui console support # Copyright (C) 2006, 2007 Shawn Pearce -namespace eval console { +class console { -variable next_console_id 0 -variable console_data -variable console_cr +field t_short +field t_long +field w +field console_cr -proc new {short_title long_title} { - variable next_console_id - variable console_data - - set w .console[incr next_console_id] - set console_data($w) [list $short_title $long_title] - return [_init $w] +constructor new {short_title long_title} { + set t_short $short_title + set t_long $long_title + _init $this + return $this } -proc _init {w} { +method _init {} { global M1B - variable console_cr - variable console_data + make_toplevel top w + wm title $top "[appname] ([reponame]): $t_short" + set console_cr 1.0 - set console_cr($w) 1.0 - toplevel $w frame $w.m - label $w.m.l1 -text "[lindex $console_data($w) 1]:" \ + label $w.m.l1 \ + -textvariable @t_long \ -anchor w \ -justify left \ -font font_uibold @@ -67,11 +66,9 @@ proc _init {w} { bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break" bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break" bind $w "focus $w" - wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]" - return $w } -proc exec {w cmd {after {}}} { +method exec {cmd {after {}}} { # -- Cygwin's Tcl tosses the enviroment when we exec our child. # But most users need that so we have to relogin. :-( # @@ -86,16 +83,13 @@ proc exec {w cmd {after {}}} { set fd_f [open $cmd r] fconfigure $fd_f -blocking 0 -translation binary - fileevent $fd_f readable \ - [namespace code [list _read $w $fd_f $after]] + fileevent $fd_f readable [cb _read $fd_f $after] } -proc _read {w fd after} { - variable console_cr - +method _read {fd after} { set buf [read $fd] if {$buf ne {}} { - if {![winfo exists $w]} {_init $w} + if {![winfo exists $w.m.t]} {_init $this} $w.m.t conf -state normal set c 0 set n [string length $buf] @@ -107,11 +101,11 @@ proc _read {w fd after} { if {$lf < $cr} { $w.m.t insert end [string range $buf $c $lf] - set console_cr($w) [$w.m.t index {end -1c}] + set console_cr [$w.m.t index {end -1c}] set c $lf incr c } else { - $w.m.t delete $console_cr($w) end + $w.m.t delete $console_cr end $w.m.t insert end "\n" $w.m.t insert end [string range $buf $c $cr] set c $cr @@ -130,19 +124,19 @@ proc _read {w fd after} { set ok 1 } if {$after ne {}} { - uplevel #0 $after $w $ok + uplevel #0 $after $ok } else { - done $w $ok + done $this $ok } return } fconfigure $fd -blocking 0 } -proc chain {cmdlist w {ok 1}} { +method chain {cmdlist {ok 1}} { if {$ok} { if {[llength $cmdlist] == 0} { - done $w $ok + done $this $ok return } @@ -150,52 +144,33 @@ proc chain {cmdlist w {ok 1}} { set cmdlist [lrange $cmdlist 1 end] if {[lindex $cmd 0] eq {exec}} { - exec $w \ - [lindex $cmd 1] \ - [namespace code [list chain $cmdlist]] + exec $this \ + [lrange $cmd 1 end] \ + [cb chain $cmdlist] } else { - uplevel #0 $cmd $cmdlist $w $ok + uplevel #0 $cmd [cb chain $cmdlist] } } else { - done $w $ok + done $this $ok } } -proc done {args} { - variable console_cr - variable console_data - - switch -- [llength $args] { - 2 { - set w [lindex $args 0] - set ok [lindex $args 1] - } - 3 { - set w [lindex $args 1] - set ok [lindex $args 2] - } - default { - error "wrong number of args: done ?ignored? w ok" - } - } - +method done {ok} { if {$ok} { - if {[winfo exists $w]} { + if {[winfo exists $w.m.s]} { $w.m.s conf -background green -text {Success} $w.ok conf -state normal focus $w.ok } } else { - if {![winfo exists $w]} { - _init $w + if {![winfo exists $w.m.s]} { + _init $this } $w.m.s conf -background red -text {Error: Command Failed} $w.ok conf -state normal focus $w.ok } - - array unset console_cr $w - array unset console_data $w + delete_this } } diff --git a/lib/database.tcl b/lib/database.tcl index 73058a8269..43e4a289bb 100644 --- a/lib/database.tcl +++ b/lib/database.tcl @@ -70,12 +70,12 @@ proc do_stats {} { proc do_gc {} { set w [console::new {gc} {Compressing the object database}] - console::chain { - {exec {git pack-refs --prune}} - {exec {git reflog expire --all}} - {exec {git repack -a -d -l}} - {exec {git rerere gc}} - } $w + console::chain $w { + {exec git pack-refs --prune} + {exec git reflog expire --all} + {exec git repack -a -d -l} + {exec git rerere gc} + } } proc do_fsck_objects {} { diff --git a/lib/merge.tcl b/lib/merge.tcl index 642d6eefbd..24ed24b3d0 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -123,7 +123,8 @@ Please select fewer branches. To merge more than 15 branches, merge the branche set msg "Merging $current_branch, [join $names {, }]" set ui_status_value "$msg..." set cons [console::new "Merge" $msg] - console::exec $cons $cmd [namespace code [list _finish $revcnt]] + console::exec $cons $cmd \ + [namespace code [list _finish $revcnt $cons]] bind $w {} destroy $w } From 28bf928cf81eec720bc8a31179a0c9cf688d5af3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 8 May 2007 21:31:31 -0400 Subject: [PATCH 49/77] git-gui: Don't attempt to inline array reads in methods If a variable reference to a field is to an array, and it is the only reference to that field in that method we cannot make it an inlined [set foo] call as the regexp was converting the Tcl code wrong. We were producing "[set foo](x)" for "$foo(x)", and that isn't valid Tcl when foo is an array. So we just punt if the only occurance has a ( after it. Signed-off-by: Shawn O. Pearce --- lib/class.tcl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/class.tcl b/lib/class.tcl index c1291989aa..88b056522a 100644 --- a/lib/class.tcl +++ b/lib/class.tcl @@ -96,7 +96,8 @@ proc method {name params body {deleted {}} {del_body {}}} { set n [lindex $n 0] if {[regexp -- $n\\M $body]} { if { [regexp -all -- $n\\M $body] == 1 - && [regexp -all -- \\\$$n\\M $body] == 1} { + && [regexp -all -- \\\$$n\\M $body] == 1 + && [regexp -all -- \\\$$n\\( $body] == 0} { regsub -all \\\$$n\\M $body "\[set \${this}::$n\]" body } else { append decl { ${this}::} $n { } $n From 685caf9af672811b45ebeaaa299b8f9fbd743d82 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 8 May 2007 21:33:14 -0400 Subject: [PATCH 50/77] git-gui: Convert blame to the "class" way of doing things Our blame viewer code has historically been a mess simply because the data for multiple viewers was all crammed into a single pair of Tcl arrays. This made the code hard to read and even harder to maintain. Now that we have a slightly better way of tracking the data for our "meta-widgets" we can make use of it here in the blame viewer to cleanup the code and make it easier to work with long term. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 2 +- lib/blame.tcl | 248 +++++++++++++++++++++++------------------------- lib/browser.tcl | 2 +- 3 files changed, 121 insertions(+), 131 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 67f993aa4b..568c814c60 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1590,7 +1590,7 @@ blame { exit 1 } set current_branch [lindex $argv 0] - show_blame $current_branch [lindex $argv 1] + blame::new $current_branch [lindex $argv 1] return } citool - diff --git a/lib/blame.tcl b/lib/blame.tcl index dc7b22f7b5..37ce3e7a86 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -1,20 +1,42 @@ # git-gui blame viewer # Copyright (C) 2006, 2007 Shawn Pearce -set next_browser_id 0 +class blame { -proc show_blame {commit path} { - global next_browser_id blame_status blame_data +field commit ; # input commit to blame +field path ; # input filename to view in $commit - if {[winfo ismapped .]} { - set w .browser[incr next_browser_id] - set tl $w - toplevel $w - } else { - set w {} - set tl . - } - set blame_status($w) {Loading current file content...} +field w +field w_line +field w_load +field w_file +field w_cmit +field status + +field highlight_line -1 ; # current line selected +field highlight_commit {} ; # sha1 of commit selected + +field total_lines 0 ; # total length of file +field blame_lines 0 ; # number of lines computed +field commit_count 0 ; # number of commits in $commit_list +field commit_list {} ; # list of commit sha1 in receipt order +field order ; # array commit -> receipt order +field header ; # array commit,key -> header field +field line_commit ; # array line -> sha1 commit +field line_file ; # array line -> file name + +field r_commit ; # commit currently being parsed +field r_orig_line ; # original line number +field r_final_line ; # final line number +field r_line_count ; # lines in this region + +constructor new {i_commit i_path} { + set commit $i_commit + set path $i_path + + make_toplevel top w + wm title $top "[appname] ([reponame]): File Viewer" + set status "Loading $commit:$path..." label $w.path -text "$commit:$path" \ -anchor w \ @@ -70,7 +92,8 @@ proc show_blame {commit path} { grid rowconfigure $w.out 0 -weight 1 pack $w.out -fill both -expand 1 - label $w.status -textvariable blame_status($w) \ + label $w.status \ + -textvariable @status \ -anchor w \ -justify left \ -borderwidth 1 \ @@ -95,8 +118,14 @@ proc show_blame {commit path} { pack $w.cm -side bottom -fill x menu $w.ctxm -tearoff 0 - $w.ctxm add command -label "Copy Commit" \ - -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY" + $w.ctxm add command \ + -label "Copy Commit" \ + -command [cb _copycommit] + + set w_line $w.out.linenumber_t + set w_load $w.out.loaded_t + set w_file $w.out.file_t + set w_cmit $w.cm.t foreach i [list \ $w.out.loaded_t \ @@ -111,14 +140,7 @@ proc show_blame {commit path} { $w.out.linenumber_t \ $w.out.file_t \ ] yview $w.out.sby] - bind $i " - blame_click {$w} \\ - $w.cm.t \\ - $w.out.linenumber_t \\ - $w.out.file_t \\ - $i @%x,%y - focus $i - " + bind $i "[cb _click $i @%x,%y]; focus $i" bind_button3 $i " set cursorX %x set cursorY %y @@ -145,157 +167,122 @@ proc show_blame {commit path} { } bind $w.cm.t "focus $w.cm.t" - bind $tl "focus $tl" - bind $tl " - array unset blame_status {$w} - array unset blame_data $w,* - " - wm title $tl "[appname] ([reponame]): File Viewer" - - set blame_data($w,commit_count) 0 - set blame_data($w,commit_list) {} - set blame_data($w,total_lines) 0 - set blame_data($w,blame_lines) 0 - set blame_data($w,highlight_commit) {} - set blame_data($w,highlight_line) -1 + bind $top "focus $top" + bind $top [list delete_this $this] set cmd [list git cat-file blob "$commit:$path"] set fd [open "| $cmd" r] fconfigure $fd -blocking 0 -translation lf -encoding binary - fileevent $fd readable [list read_blame_catfile \ - $fd $w $commit $path \ - $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t] + fileevent $fd readable [cb _read_file $fd] } -proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} { - global blame_status blame_data - - if {![winfo exists $w_file]} { - catch {close $fd} - return - } - - set n $blame_data($w,total_lines) +method _read_file {fd} { $w_load conf -state normal $w_line conf -state normal $w_file conf -state normal while {[gets $fd line] >= 0} { regsub "\r\$" $line {} line - incr n + incr total_lines $w_load insert end "\n" - $w_line insert end "$n\n" linenumber + $w_line insert end "$total_lines\n" linenumber $w_file insert end "$line\n" } $w_load conf -state disabled $w_line conf -state disabled $w_file conf -state disabled - set blame_data($w,total_lines) $n if {[eof $fd]} { close $fd - blame_incremental_status $w - set cmd [list git blame -M -C --incremental] - lappend cmd $commit -- $path + _status $this + set cmd [list git blame -M -C --incremental $commit -- $path] set fd [open "| $cmd" r] fconfigure $fd -blocking 0 -translation lf -encoding binary - fileevent $fd readable [list read_blame_incremental $fd $w \ - $w_load $w_cmit $w_line $w_file] - } -} - -proc read_blame_incremental {fd w w_load w_cmit w_line w_file} { - global blame_status blame_data - - if {![winfo exists $w_file]} { - catch {close $fd} - return + fileevent $fd readable [cb _read_blame $fd] } +} ifdeleted { catch {close $fd} } +method _read_blame {fd} { while {[gets $fd line] >= 0} { if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \ cmit original_line final_line line_count]} { - set blame_data($w,commit) $cmit - set blame_data($w,original_line) $original_line - set blame_data($w,final_line) $final_line - set blame_data($w,line_count) $line_count + set r_commit $cmit + set r_orig_line $original_line + set r_final_line $final_line + set r_line_count $line_count - if {[catch {set g $blame_data($w,$cmit,order)}]} { + if {[catch {set g $order($cmit)}]} { $w_line tag conf g$cmit $w_file tag conf g$cmit $w_line tag raise in_sel $w_file tag raise in_sel $w_file tag raise sel - set blame_data($w,$cmit,order) $blame_data($w,commit_count) - incr blame_data($w,commit_count) - lappend blame_data($w,commit_list) $cmit + set order($cmit) $commit_count + incr commit_count + lappend commit_list $cmit } } elseif {[string match {filename *} $line]} { set file [string range $line 9 end] - set n $blame_data($w,line_count) - set lno $blame_data($w,final_line) - set cmit $blame_data($w,commit) + set n $r_line_count + set lno $r_final_line + set cmit $r_commit while {$n > 0} { - if {[catch {set g g$blame_data($w,line$lno,commit)}]} { + if {[catch {set g g$line_commit($lno)}]} { $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c" } else { $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c" $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c" } - set blame_data($w,line$lno,commit) $cmit - set blame_data($w,line$lno,file) $file + set line_commit($lno) $cmit + set line_file($lno) $file $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c" $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c" - if {$blame_data($w,highlight_line) == -1} { + if {$highlight_line == -1} { if {[lindex [$w_file yview] 0] == 0} { $w_file see $lno.0 - blame_showcommit $w $w_cmit $w_line $w_file $lno + _showcommit $this $lno } - } elseif {$blame_data($w,highlight_line) == $lno} { - blame_showcommit $w $w_cmit $w_line $w_file $lno + } elseif {$highlight_line == $lno} { + _showcommit $this $lno } incr n -1 incr lno - incr blame_data($w,blame_lines) + incr blame_lines } - set hc $blame_data($w,highlight_commit) + set hc $highlight_commit if {$hc ne {} - && [expr {$blame_data($w,$hc,order) + 1}] - == $blame_data($w,$cmit,order)} { - blame_showcommit $w $w_cmit $w_line $w_file \ - $blame_data($w,highlight_line) + && [expr {$order($hc) + 1}] == $order($cmit)} { + _showcommit $this $highlight_line } - } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} { - set blame_data($w,$blame_data($w,commit),$header) $data + } elseif {[regexp {^([a-z-]+) (.*)$} $line line key data]} { + set header($r_commit,$key) $data } } if {[eof $fd]} { close $fd - set blame_status($w) {Annotation complete.} + set status {Annotation complete.} } else { - blame_incremental_status $w + _status $this } -} +} ifdeleted { catch {close $fd} } -proc blame_incremental_status {w} { - global blame_status blame_data - - set have $blame_data($w,blame_lines) - set total $blame_data($w,total_lines) +method _status {} { + set have $blame_lines + set total $total_lines set pdone 0 if {$total} {set pdone [expr {100 * $have / $total}]} - set blame_status($w) [format \ + set status [format \ "Loading annotations... %i of %i lines annotated (%2i%%)" \ $have $total $pdone] } -proc blame_click {w w_cmit w_line w_file cur_w pos} { +method _click {cur_w pos} { set lno [lindex [split [$cur_w index $pos] .] 0] if {$lno eq {}} return @@ -304,24 +291,24 @@ proc blame_click {w w_cmit w_line w_file cur_w pos} { $w_line tag add in_sel $lno.0 "$lno.0 + 1 line" $w_file tag add in_sel $lno.0 "$lno.0 + 1 line" - blame_showcommit $w $w_cmit $w_line $w_file $lno + _showcommit $this $lno } -set blame_colors { +variable blame_colors { #ff4040 #ff40ff #4040ff } -proc blame_showcommit {w w_cmit w_line w_file lno} { - global blame_colors blame_data repo_config +method _showcommit {lno} { + global repo_config + variable blame_colors - set cmit $blame_data($w,highlight_commit) - if {$cmit ne {}} { - set idx $blame_data($w,$cmit,order) + if {$highlight_commit ne {}} { + set idx $order($highlight_commit) set i 0 foreach c $blame_colors { - set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] + set h [lindex $commit_list [expr {$idx - 1 + $i}]] $w_line tag conf g$h -background white $w_file tag conf g$h -background white incr i @@ -330,14 +317,15 @@ proc blame_showcommit {w w_cmit w_line w_file lno} { $w_cmit conf -state normal $w_cmit delete 0.0 end - if {[catch {set cmit $blame_data($w,line$lno,commit)}]} { + if {[catch {set cmit $line_commit($lno)} myerr]} { + puts "myerr = $myerr" set cmit {} $w_cmit insert end "Loading annotation..." } else { - set idx $blame_data($w,$cmit,order) + set idx $order($cmit) set i 0 foreach c $blame_colors { - set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] + set h [lindex $commit_list [expr {$idx - 1 + $i}]] $w_line tag conf g$h -background $c $w_file tag conf g$h -background $c incr i @@ -346,18 +334,18 @@ proc blame_showcommit {w w_cmit w_line w_file lno} { set author_name {} set author_email {} set author_time {} - catch {set author_name $blame_data($w,$cmit,author)} - catch {set author_email $blame_data($w,$cmit,author-mail)} - catch {set author_time [clock format $blame_data($w,$cmit,author-time)]} + catch {set author_name $header($cmit,author)} + catch {set author_email $header($cmit,author-mail)} + catch {set author_time [clock format $header($cmit,author-time)]} set committer_name {} set committer_email {} set committer_time {} - catch {set committer_name $blame_data($w,$cmit,committer)} - catch {set committer_email $blame_data($w,$cmit,committer-mail)} - catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]} + catch {set committer_name $header($cmit,committer)} + catch {set committer_email $header($cmit,committer-mail)} + catch {set committer_time [clock format $header($cmit,committer-time)]} - if {[catch {set msg $blame_data($w,$cmit,message)}]} { + if {[catch {set msg $header($cmit,message)}]} { set msg {} catch { set fd [open "| git cat-file commit $cmit" r] @@ -377,29 +365,29 @@ proc blame_showcommit {w w_cmit w_line w_file lno} { set author_name [encoding convertfrom $enc $author_name] set committer_name [encoding convertfrom $enc $committer_name] - set blame_data($w,$cmit,author) $author_name - set blame_data($w,$cmit,committer) $committer_name + set header($cmit,author) $author_name + set header($cmit,committer) $committer_name } - set blame_data($w,$cmit,message) $msg + set header($cmit,message) $msg } $w_cmit insert end "commit $cmit\n" $w_cmit insert end "Author: $author_name $author_email $author_time\n" $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n" - $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n" + $w_cmit insert end "Original File: [escape_path $line_file($lno)]\n" $w_cmit insert end "\n" $w_cmit insert end $msg } $w_cmit conf -state disabled - set blame_data($w,highlight_line) $lno - set blame_data($w,highlight_commit) $cmit + set highlight_line $lno + set highlight_commit $cmit } -proc blame_copycommit {w i pos} { - global blame_data - set lno [lindex [split [$i index $pos] .] 0] - if {![catch {set commit $blame_data($w,line$lno,commit)}]} { +method _copycommit {} { + set pos @$::cursorX,$::cursorY + set lno [lindex [split [$::cursorW index $pos] .] 0] + if {![catch {set commit $line_commit($lno)}]} { clipboard clear clipboard append \ -format STRING \ @@ -407,3 +395,5 @@ proc blame_copycommit {w i pos} { -- $commit } } + +} diff --git a/lib/browser.tcl b/lib/browser.tcl index f9a4e1d52c..fd86b11217 100644 --- a/lib/browser.tcl +++ b/lib/browser.tcl @@ -140,7 +140,7 @@ method _enter {} { append p [lindex $n 1] } append p $name - show_blame $browser_commit $p + blame::new $browser_commit $p } } } From c6127856eb318157121b0aeab150c074e9eeb3de Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 8 May 2007 21:58:25 -0400 Subject: [PATCH 51/77] git-gui: Use prefix if blame is run in a subdirectory I think it was Andy Parkins who pointed out that git gui blame HEAD f does not work if f is in a subdirectory and we are currently running git-gui within that subdirectory. This is happening because we did not take the user's prefix into account when we computed the file path in the repository. We now assume the prefix as returned by rev-parse --show-prefix is valid and we use that during the command line blame subcommand when we apply the parameters. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 568c814c60..8f33f81c58 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -261,8 +261,14 @@ unset -nocomplain v _junk act_maj act_min req_maj req_min ## ## repository setup -if { [catch {set _gitdir $env(GIT_DIR)}] - && [catch {set _gitdir [git rev-parse --git-dir]} err]} { +if {[catch { + set _gitdir $env(GIT_DIR) + set _prefix {} + }] + && [catch { + set _gitdir [git rev-parse --git-dir] + set _prefix [git rev-parse --show-prefix] + } err]} { catch {wm withdraw .} error_popup "Cannot find the git directory:\n\n$err" exit 1 @@ -1590,7 +1596,7 @@ blame { exit 1 } set current_branch [lindex $argv 0] - blame::new $current_branch [lindex $argv 1] + blame::new $current_branch $_prefix[lindex $argv 1] return } citool - From 3e45ee1ef268c73539903f89c737af89034d391f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 8 May 2007 22:36:01 -0400 Subject: [PATCH 52/77] git-gui: Smarter command line parsing for browser, blame The browser subcommand now optionally accepts a single revision argument; if no revision argument is supplied then we use the current branch as the tree to browse. This is very common, so its a nice option. Our blame subcommand now tries to perform the same assumptions as the command line git-blame; both the revision and the file are optional. We assume the argument is a filename if the file exists in the working directory, otherwise we assume the argument is a revision name. A -- can be supplied between the two to force parsing, or before the filename to force it to be a filename. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 56 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 8f33f81c58..e90cdde96c 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1578,25 +1578,63 @@ bind all <$M1B-Key-Q> do_quit bind all <$M1B-Key-w> {destroy [winfo toplevel %W]} bind all <$M1B-Key-W> {destroy [winfo toplevel %W]} +set subcommand_args {} +proc usage {} { + puts stderr "usage: $::argv0 $::subcommand $::subcommand_args" + exit 1 +} + # -- Not a normal commit type invocation? Do that instead! # switch -- $subcommand { browser { - if {[llength $argv] != 1} { - puts stderr "usage: $argv0 browser commit" - exit 1 + set subcommand_args {rev?} + switch [llength $argv] { + 0 { + set current_branch [git symbolic-ref HEAD] + regsub ^refs/((heads|tags|remotes)/)? \ + $current_branch {} current_branch + } + 1 { + set current_branch [lindex $argv 0] + } + default usage } - set current_branch [lindex $argv 0] browser::new $current_branch return } blame { - if {[llength $argv] != 2} { - puts stderr "usage: $argv0 blame commit path" - exit 1 + set subcommand_args {rev? path?} + set path {} + set is_path 0 + foreach a $argv { + if {$is_path || [file exists $_prefix$a]} { + if {$path ne {}} usage + set path $a + break + } elseif {$a eq {--}} { + if {$path ne {}} { + if {$current_branch ne {}} usage + set current_branch $path + set path {} + } + set is_path 1 + } elseif {$current_branch eq {}} { + if {$current_branch ne {}} usage + set current_branch $a + } else { + usage + } } - set current_branch [lindex $argv 0] - blame::new $current_branch $_prefix[lindex $argv 1] + unset is_path + + if {$current_branch eq {} && $path ne {}} { + set current_branch [git symbolic-ref HEAD] + regsub ^refs/((heads|tags|remotes)/)? \ + $current_branch {} current_branch + } + if {$current_branch eq {} || $path eq {}} usage + blame::new $current_branch $path return } citool - From a0db0d61fbc447f13cb0aadf01c83eb193b22dc9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 8 May 2007 22:48:47 -0400 Subject: [PATCH 53/77] git-gui: Generate blame on uncommitted working tree file If the user doesn't give us a revision parameter to our blame subcommand then we can generate blame against the working tree file by passing the file path off to blame with the --contents argument. In this case we cannot obtain the contents of the file from the ODB; instead we must obtain the contents by reading the working directory file as-is. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 20 ++++++++++++-------- lib/blame.tcl | 16 +++++++++++++--- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index e90cdde96c..62b1a36a8a 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1605,6 +1605,7 @@ browser { } blame { set subcommand_args {rev? path?} + set head {} set path {} set is_path 0 foreach a $argv { @@ -1614,27 +1615,30 @@ blame { break } elseif {$a eq {--}} { if {$path ne {}} { - if {$current_branch ne {}} usage - set current_branch $path + if {$head ne {}} usage + set head $path set path {} } set is_path 1 - } elseif {$current_branch eq {}} { - if {$current_branch ne {}} usage - set current_branch $a + } elseif {$head eq {}} { + if {$head ne {}} usage + set head $a } else { usage } } unset is_path - if {$current_branch eq {} && $path ne {}} { + if {$head eq {}} { set current_branch [git symbolic-ref HEAD] regsub ^refs/((heads|tags|remotes)/)? \ $current_branch {} current_branch + } else { + set current_branch $head } - if {$current_branch eq {} || $path eq {}} usage - blame::new $current_branch $path + + if {$path eq {}} usage + blame::new $head $path return } citool - diff --git a/lib/blame.tcl b/lib/blame.tcl index 37ce3e7a86..8ac0104187 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -170,8 +170,12 @@ constructor new {i_commit i_path} { bind $top "focus $top" bind $top [list delete_this $this] - set cmd [list git cat-file blob "$commit:$path"] - set fd [open "| $cmd" r] + if {$commit eq {}} { + set fd [open $path r] + } else { + set cmd [list git cat-file blob "$commit:$path"] + set fd [open "| $cmd" r] + } fconfigure $fd -blocking 0 -translation lf -encoding binary fileevent $fd readable [cb _read_file $fd] } @@ -194,7 +198,13 @@ method _read_file {fd} { if {[eof $fd]} { close $fd _status $this - set cmd [list git blame -M -C --incremental $commit -- $path] + set cmd [list git blame -M -C --incremental] + if {$commit eq {}} { + lappend cmd --contents $path + } else { + lappend cmd $commit + } + lappend cmd -- $path set fd [open "| $cmd" r] fconfigure $fd -blocking 0 -translation lf -encoding binary fileevent $fd readable [cb _read_blame $fd] From 0511798f06d3456885682605b54bd3bc378e8fda Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 9 May 2007 00:36:25 -0400 Subject: [PATCH 54/77] git-gui: Cleanup minor nits in blame code We can use [list ...] rather than "", especially when we are talking about values as then they are properly escaped if necessary. Small nit, but probably not a huge deal as the only data being inlined here is Tk paths. Some of the lines in the parser code were longer than 80 characters wide, and they actually were all the same value on the end part of the line. Rather than keeping the mess copied-and-pasted around we can set the last argument into a local variable and reuse it many times. The commit display code was also rather difficult to read on an 80 character wide terminal, so I'm moving it all into a double quoted string that is easier to read. Signed-off-by: Shawn O. Pearce --- lib/blame.tcl | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/blame.tcl b/lib/blame.tcl index 8ac0104187..d6d57d889d 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -166,8 +166,8 @@ constructor new {i_commit i_path} { bind $i {catch {%W yview scroll 1 pages};break} } - bind $w.cm.t "focus $w.cm.t" - bind $top "focus $top" + bind $w.cm.t [list focus $w.cm.t] + bind $top [list focus $top] bind $top [list delete_this $this] if {$commit eq {}} { @@ -237,17 +237,18 @@ method _read_blame {fd} { set cmit $r_commit while {$n > 0} { + set lno_e "$lno.0 lineend + 1c" if {[catch {set g g$line_commit($lno)}]} { - $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c" + $w_load tag add annotated $lno.0 $lno_e } else { - $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c" - $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c" + $w_line tag remove g$g $lno.0 $lno_e + $w_file tag remove g$g $lno.0 $lno_e } set line_commit($lno) $cmit set line_file($lno) $file - $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c" - $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c" + $w_line tag add g$cmit $lno.0 $lno_e + $w_file tag add g$cmit $lno.0 $lno_e if {$highlight_line == -1} { if {[lindex [$w_file yview] 0] == 0} { @@ -296,10 +297,11 @@ method _click {cur_w pos} { set lno [lindex [split [$cur_w index $pos] .] 0] if {$lno eq {}} return + set lno_e "$lno.0 + 1 line" $w_line tag remove in_sel 0.0 end $w_file tag remove in_sel 0.0 end - $w_line tag add in_sel $lno.0 "$lno.0 + 1 line" - $w_file tag add in_sel $lno.0 "$lno.0 + 1 line" + $w_line tag add in_sel $lno.0 $lno_e + $w_file tag add in_sel $lno.0 $lno_e _showcommit $this $lno } @@ -327,8 +329,7 @@ method _showcommit {lno} { $w_cmit conf -state normal $w_cmit delete 0.0 end - if {[catch {set cmit $line_commit($lno)} myerr]} { - puts "myerr = $myerr" + if {[catch {set cmit $line_commit($lno)}]} { set cmit {} $w_cmit insert end "Loading annotation..." } else { @@ -381,12 +382,12 @@ method _showcommit {lno} { set header($cmit,message) $msg } - $w_cmit insert end "commit $cmit\n" - $w_cmit insert end "Author: $author_name $author_email $author_time\n" - $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n" - $w_cmit insert end "Original File: [escape_path $line_file($lno)]\n" - $w_cmit insert end "\n" - $w_cmit insert end $msg + $w_cmit insert end "commit $cmit +Author: $author_name $author_email $author_time +Committer: $committer_name $committer_email $committer_time +Original File: [escape_path $line_file($lno)] + +$msg" } $w_cmit conf -state disabled From 76486bbefbaeb606f662dadde0ca5dac59a4ec31 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 9 May 2007 00:48:27 -0400 Subject: [PATCH 55/77] git-gui: Format author/committer times in ISO format This is a simple change to match what gitk does when it shows a commit; we format using ISO dates (yyyy-mm-dd HH:MM:SS). Signed-off-by: Shawn O. Pearce --- lib/blame.tcl | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/blame.tcl b/lib/blame.tcl index d6d57d889d..8b032d9590 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -347,14 +347,20 @@ method _showcommit {lno} { set author_time {} catch {set author_name $header($cmit,author)} catch {set author_email $header($cmit,author-mail)} - catch {set author_time [clock format $header($cmit,author-time)]} + catch {set author_time [clock format \ + $header($cmit,author-time) \ + -format {%Y-%m-%d %H:%M:%S} + ]} set committer_name {} set committer_email {} set committer_time {} catch {set committer_name $header($cmit,committer)} catch {set committer_email $header($cmit,committer-mail)} - catch {set committer_time [clock format $header($cmit,committer-time)]} + catch {set committer_time [clock format \ + $header($cmit,committer-time) \ + -format {%Y-%m-%d %H:%M:%S} + ]} if {[catch {set msg $header($cmit,message)}]} { set msg {} @@ -383,8 +389,8 @@ method _showcommit {lno} { } $w_cmit insert end "commit $cmit -Author: $author_name $author_email $author_time -Committer: $committer_name $committer_email $committer_time +Author: $author_name $author_email $author_time +Committer: $committer_name $committer_email $committer_time Original File: [escape_path $line_file($lno)] $msg" From 22f09585d402f79f8248b30253c3338f69114ba3 Mon Sep 17 00:00:00 2001 From: Quy Tonthat Date: Wed, 9 May 2007 00:19:41 +1000 Subject: [PATCH 56/77] Add howto files to rpm packages. RPM packages did not include howto files which causes broken links in howto-index.html Signed-off-by: Quy Tonthat Signed-off-by: Junio C Hamano --- git.spec.in | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/git.spec.in b/git.spec.in index 87197d10e1..b0ea62884f 100644 --- a/git.spec.in +++ b/git.spec.in @@ -166,9 +166,12 @@ rm -rf $RPM_BUILD_ROOT %defattr(-,root,root) %{_datadir}/git-core/ %doc README COPYING Documentation/*.txt -%{!?_without_docs: %doc Documentation/*.html } +%{!?_without_docs: %doc Documentation/*.html Documentation/howto} %changelog +* Tue May 8 2007 Quy Tonthat +- Added howto files + * Mon Feb 13 2007 Nicolas Pitre - Update core package description (Git isn't as stupid as it used to be) From 1cc202bd4ebd0390d738f0d633d42791cb79dd05 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 8 May 2007 22:10:56 -0700 Subject: [PATCH 57/77] GIT v1.5.1.4 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.5.1.4.txt | 30 ++++++++++++++++++++++++++++++ GIT-VERSION-GEN | 2 +- RelNotes | 2 +- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 Documentation/RelNotes-1.5.1.4.txt diff --git a/Documentation/RelNotes-1.5.1.4.txt b/Documentation/RelNotes-1.5.1.4.txt new file mode 100644 index 0000000000..df2f66ccb5 --- /dev/null +++ b/Documentation/RelNotes-1.5.1.4.txt @@ -0,0 +1,30 @@ +GIT v1.5.1.4 Release Notes +========================== + +Fixes since v1.5.1.3 +-------------------- + +* Bugfixes + + - "git-http-fetch" did not work around a bug in libcurl + earlier than 7.16 (curl_multi_remove_handle() was broken). + + - "git cvsserver" handles a file that was once removed and + then added again correctly. + + - import-tars script (in contrib/) handles GNU tar archives + that contain pathnames longer than 100 bytes (long-link + extension) correctly. + + - xdelta test program did not build correctly. + + - gitweb sometimes tried incorrectly to apply function to + decode utf8 twice, resulting in corrupt output. + + - "git blame -C" mishandled text at the end of a group of + lines. + + - "git log/rev-list --boundary" did not produce output + correctly without --left-right option. + + - Many documentation updates. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index cd9e0500e0..094a0d1b0e 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.5.1.3.GIT +DEF_VER=v1.5.1.4.GIT LF=' ' diff --git a/RelNotes b/RelNotes index b630faa0c0..4cad80a03c 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.5.1.3.txt \ No newline at end of file +Documentation/RelNotes-1.5.1.4.txt \ No newline at end of file From abda5227770b39bb4abd8f0b4c1a0f69c2778300 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 7 May 2007 22:57:15 -0400 Subject: [PATCH 58/77] Use .git/MERGE_MSG in cherry-pick/revert Rather than storing the temporary commit message data in .msg (in the working tree) we now store the message data in .git/MERGE_MSG. By storing the message in the .git/ directory we are sure we will never have a collision with a user file, should a project actually have a ".msg" file in their top level tree. We also don't need to worry about leaving this stale file behind during a `reset --hard` and have it show up in the output of status. We are using .git/MERGE_MSG here to store the temporary message as it is an already established convention between git-merge, git-am and git-rebase that git-commit will default the user's edit buffer to the contents of .git/MERGE_MSG. If the user is going to need to resolve this commit or wants to edit the message on their own prepping that file with the desired message "just works". Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-revert.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/builtin-revert.c b/builtin-revert.c index 4ba0ee63ab..55d4fa1e6c 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -237,6 +237,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) int i; char *oneline, *reencoded_message = NULL; const char *message, *encoding; + const char *defmsg = xstrdup(git_path("MERGE_MSG")); git_config(git_default_config); me = action == REVERT ? "revert" : "cherry-pick"; @@ -280,7 +281,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) * reverse of it if we are revert. */ - msg_fd = hold_lock_file_for_update(&msg_file, ".msg", 1); + msg_fd = hold_lock_file_for_update(&msg_file, defmsg, 1); encoding = get_encoding(message); if (!encoding) @@ -330,7 +331,6 @@ static int revert_or_cherry_pick(int argc, const char **argv) sha1_to_hex(head), "HEAD", sha1_to_hex(next->object.sha1), oneline) || write_tree(head, 0, NULL)) { - const char *target = git_path("MERGE_MSG"); add_to_msg("\nConflicts:\n\n"); read_cache(); for (i = 0; i < active_nr;) { @@ -345,10 +345,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) } } if (close(msg_fd) || commit_lock_file(&msg_file) < 0) - die ("Error wrapping up .msg"); - unlink(target); - if (rename(".msg", target)) - die ("Could not move .msg to %s", target); + die ("Error wrapping up %s", defmsg); fprintf(stderr, "Automatic %s failed. " "After resolving the conflicts,\n" "mark the corrected paths with 'git-add '\n" @@ -362,7 +359,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) exit(1); } if (close(msg_fd) || commit_lock_file(&msg_file) < 0) - die ("Error wrapping up .msg"); + die ("Error wrapping up %s", defmsg); fprintf(stderr, "Finished one %s.\n", me); /* @@ -376,11 +373,9 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (!no_commit) { if (edit) - return execl_git_cmd("commit", "-n", "-F", ".msg", - "-e", NULL); + return execl_git_cmd("commit", "-n", NULL); else - return execl_git_cmd("commit", "-n", "-F", ".msg", - NULL); + return execl_git_cmd("commit", "-n", "-F", defmsg, NULL); } if (reencoded_message) free(reencoded_message); From 842aaf93238377976111fc801aaacbf8a491db33 Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Tue, 8 May 2007 09:28:26 -0400 Subject: [PATCH 59/77] Add pack.depth option to git-pack-objects. Signed-off-by: "Theodore Ts'o" Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ Documentation/git-pack-objects.txt | 2 +- Documentation/git-repack.txt | 2 +- builtin-pack-objects.c | 6 +++++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 24f9655fef..840dd81b81 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -546,6 +546,10 @@ pack.window:: The size of the window used by gitlink:git-pack-objects[1] when no window size is given on the command line. Defaults to 10. +pack.depth:: + The maximum delta depth used by gitlink:git-pack-objects[1] when no + maximum depth is given on the command line. Defaults to 10. + pull.octopus:: The default merge strategy to use when pulling multiple branches at once. diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index d9e11c6534..5a7204c9fb 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -83,7 +83,7 @@ base-name:: it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for both --window and --depth is 10. + The default value for --window is 10 and --depth is 10. --incremental:: This flag causes an object already in a pack ignored diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index d39abc126d..0d70c9f782 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -63,7 +63,7 @@ OPTIONS space. `--depth` limits the maximum delta depth; making it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for both --window and --depth is 10. + The default value for --window is 10 and --depth is 10. Configuration diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 7bff8eadd8..79d9228925 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -64,6 +64,7 @@ static char tmpname[PATH_MAX]; static unsigned char pack_file_sha1[20]; static int progress = 1; static int window = 10; +static int depth = 10; static int pack_to_stdout; static int num_preferred_base; static struct progress progress_state; @@ -1489,6 +1490,10 @@ static int git_pack_config(const char *k, const char *v) window = git_config_int(k, v); return 0; } + if(!strcmp(k, "pack.depth")) { + depth = git_config_int(k, v); + return 0; + } return git_default_config(k, v); } @@ -1584,7 +1589,6 @@ static int adjust_perm(const char *path, mode_t mode) int cmd_pack_objects(int argc, const char **argv, const char *prefix) { - int depth = 10; int use_internal_rev_list = 0; int thin = 0; uint32_t i; From 618e613a706638a7a1fb5c87cd418158c9b2687c Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Tue, 8 May 2007 09:28:26 -0400 Subject: [PATCH 60/77] Increase pack.depth default to 50 Signed-off-by: "Theodore Ts'o" Signed-off-by: Junio C Hamano --- Documentation/config.txt | 2 +- Documentation/git-pack-objects.txt | 2 +- Documentation/git-repack.txt | 2 +- builtin-pack-objects.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 840dd81b81..ea434af9db 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -548,7 +548,7 @@ pack.window:: pack.depth:: The maximum delta depth used by gitlink:git-pack-objects[1] when no - maximum depth is given on the command line. Defaults to 10. + maximum depth is given on the command line. Defaults to 50. pull.octopus:: The default merge strategy to use when pulling multiple branches diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 5a7204c9fb..bd3ee456e3 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -83,7 +83,7 @@ base-name:: it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 10. + The default value for --window is 10 and --depth is 50. --incremental:: This flag causes an object already in a pack ignored diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index 0d70c9f782..cc3b0b21c7 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -63,7 +63,7 @@ OPTIONS space. `--depth` limits the maximum delta depth; making it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 10. + The default value for --window is 10 and --depth is 50. Configuration diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 79d9228925..966f843e43 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -64,7 +64,7 @@ static char tmpname[PATH_MAX]; static unsigned char pack_file_sha1[20]; static int progress = 1; static int window = 10; -static int depth = 10; +static int depth = 50; static int pack_to_stdout; static int num_preferred_base; static struct progress progress_state; From 467592ea926d105e1cb78b2471b28bf37e8c62c6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 8 May 2007 23:47:35 -0700 Subject: [PATCH 61/77] Update documentation links to point at 1.5.1.4 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.5.2.txt | 20 ++++++++++++++++++-- Documentation/git.txt | 6 +++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Documentation/RelNotes-1.5.2.txt b/Documentation/RelNotes-1.5.2.txt index 02b8ea0a68..c14f3da7aa 100644 --- a/Documentation/RelNotes-1.5.2.txt +++ b/Documentation/RelNotes-1.5.2.txt @@ -32,7 +32,7 @@ Updates since v1.5.1 arbitrary filter to contents on check-in/check-out codepath but this feature is an extremely sharp-edged razor and needs to be handled with caution (do not use it unless you - understand the earlier mailing list discussion on keyward + understand the earlier mailing list discussion on keyword expansion). * The packfile format now optionally suports 64-bit index. @@ -42,6 +42,8 @@ Updates since v1.5.1 needs more than 32-bit to express offsets of objects in the pack +* Comes with an updated git-gui 0.7.0 + * New commands and options. - "git bisect start" can optionally take a single bad commit and @@ -112,6 +114,13 @@ Updates since v1.5.1 - "git blame" uses .mailmap to canonicalize the author name just like "git shortlog" does. + - "git pack-objects" pays attention to pack.depth + configuration variable. + + - "git cherry-pick" and "git revert" does not use .msg file in + the working tree to prepare commit message; instead it uses + $GIT_DIR/MERGE_MSG as other commands. + * Builds - git-p4import has never been installed; now there is an @@ -137,6 +146,11 @@ Updates since v1.5.1 renamed it. We do not do that when there is no rename, so match that behaviour. + - The default pack depth has been increased to 50, as the + recent addition of delta_base_cache makes deeper delta chains + much less expensive to access. + + Fixes since v1.5.1 ------------------ @@ -165,12 +179,14 @@ this release, unless otherwise noted. - git-fetch had trouble with a remote with insanely large number of refs. + - "git clean -d -X" now does not remove non-excluded directories. + * Documentation updates * Performance Tweaks -- exec >/var/tmp/1 -O=v1.5.2-rc1-32-g125a5f1 +O=v1.5.2-rc2-45-g618e613 echo O=`git describe refs/heads/master` git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint diff --git a/Documentation/git.txt b/Documentation/git.txt index f84728bc11..273ca173d4 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -40,7 +40,11 @@ Documentation for older releases are available here: * link:RelNotes-1.5.1.txt[release notes for 1.5.1] -* link:v1.5.1.2/git.html[documentation for release 1.5.1.2] +* link:v1.5.1.4/git.html[documentation for release 1.5.1.4] + +* link:RelNotes-1.5.1.4.txt[release notes for 1.5.1.4] + +* link:RelNotes-1.5.1.3.txt[release notes for 1.5.1.3] * link:RelNotes-1.5.1.2.txt[release notes for 1.5.1.2] From b3b53439701189597c116aec10a6e503f80820d8 Mon Sep 17 00:00:00 2001 From: Frank Lichtenheld Date: Wed, 2 May 2007 02:45:22 +0200 Subject: [PATCH 62/77] cvsserver: Add test cases for git-cvsserver Use the :fork: access method to force cvs to call "$CVS_SERVER server" even when accessing a local repository. Add a basic test for checkout and some tests for update. Signed-off-by: Frank Lichtenheld Signed-off-by: Junio C Hamano --- t/t9400-git-cvsserver-server.sh | 120 ++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100755 t/t9400-git-cvsserver-server.sh diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh new file mode 100755 index 0000000000..f17be6b97e --- /dev/null +++ b/t/t9400-git-cvsserver-server.sh @@ -0,0 +1,120 @@ +#!/bin/sh +# +# Copyright (c) 2007 Frank Lichtenheld +# + +test_description='git-cvsserver access + +tests read access to a git repository with the +cvs CLI client via git-cvsserver server' + +. ./test-lib.sh + +cvs >/dev/null 2>&1 +if test $? -ne 1 +then + test_expect_success 'skipping git-cvsserver tests, cvs not found' : + test_done + exit +fi + +unset GIT_DIR GIT_CONFIG +WORKDIR=$(pwd) +SERVERDIR=$(pwd)/gitcvs.git +CVSROOT=":fork:$SERVERDIR" +CVSWORK=$(pwd)/cvswork +CVS_SERVER=git-cvsserver +export CVSROOT CVS_SERVER + +rm -rf "$CVSWORK" "$SERVERDIR" +echo >empty && + git add empty && + git commit -q -m "First Commit" && + git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && + GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && + GIT_DIR="$SERVERDIR" git config --bool gitcvs.logfile "$SERVERDIR/gitcvs.log" || + exit 1 + +# note that cvs doesn't accept absolute pathnames +# as argument to co -d +test_expect_success 'basic checkout' \ + 'cvs -Q co -d cvswork master && + test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5))" = "empty/1.1/"' + +test_expect_success 'cvs update (create new file)' \ + 'echo testfile1 >testfile1 && + git add testfile1 && + git commit -q -m "Add testfile1" && + git push gitcvs.git >/dev/null && + cd cvswork && + cvs -Q update && + test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.1/" && + diff -q testfile1 ../testfile1' + +cd "$WORKDIR" +test_expect_success 'cvs update (update existing file)' \ + 'echo line 2 >>testfile1 && + git add testfile1 && + git commit -q -m "Append to testfile1" && + git push gitcvs.git >/dev/null && + cd cvswork && + cvs -Q update && + test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.2/" && + diff -q testfile1 ../testfile1' + +cd "$WORKDIR" +#TODO: cvsserver doesn't support update w/o -d +test_expect_failure "cvs update w/o -d doesn't create subdir (TODO)" \ + 'mkdir test && + echo >test/empty && + git add test && + git commit -q -m "Single Subdirectory" && + git push gitcvs.git >/dev/null && + cd cvswork && + cvs -Q update && + test ! -d test' + +cd "$WORKDIR" +test_expect_success 'cvs update (subdirectories)' \ + '(for dir in A A/B A/B/C A/D E; do + mkdir $dir && + echo "test file in $dir" >"$dir/file_in_$(echo $dir|sed -e "s#/# #g")" && + git add $dir; + done) && + git commit -q -m "deep sub directory structure" && + git push gitcvs.git >/dev/null && + cd cvswork && + cvs -Q update -d && + (for dir in A A/B A/B/C A/D E; do + filename="file_in_$(echo $dir|sed -e "s#/# #g")" && + if test "$(echo $(grep -v ^D $dir/CVS/Entries|cut -d/ -f2,3,5))" = "$filename/1.1/" && + diff -q "$dir/$filename" "../$dir/$filename"; then + : + else + echo >failure + fi + done) && + test ! -f failure' + +cd "$WORKDIR" +test_expect_success 'cvs update (delete file)' \ + 'git rm testfile1 && + git commit -q -m "Remove testfile1" && + git push gitcvs.git >/dev/null && + cd cvswork && + cvs -Q update && + test -z "$(grep testfile1 CVS/Entries)" && + test ! -f testfile1' + +cd "$WORKDIR" +test_expect_success 'cvs update (re-add deleted file)' \ + 'echo readded testfile >testfile1 && + git add testfile1 && + git commit -q -m "Re-Add testfile1" && + git push gitcvs.git >/dev/null && + cd cvswork && + cvs -Q update && + test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.4/" && + diff -q testfile1 ../testfile1' + +test_done From 469be5b258d72cec68c8d4e0c2fe44030bfc8787 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 9 May 2007 09:19:42 -0700 Subject: [PATCH 63/77] t9400: skip cvsserver test if Perl SQLite interface is unavailable Signed-off-by: Junio C Hamano --- t/t9400-git-cvsserver-server.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index f17be6b97e..f137b308f3 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -17,6 +17,11 @@ then test_done exit fi +perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || { + test_expect_success 'skipping git-cvsserver tests, Perl SQLite interface unavailable' : + test_done + exit +} unset GIT_DIR GIT_CONFIG WORKDIR=$(pwd) From c69f4050958b648acd668b37dc77772e05148a97 Mon Sep 17 00:00:00 2001 From: "Richard P. Curnow" Date: Wed, 9 May 2007 23:13:44 +0100 Subject: [PATCH 64/77] Fix documentation of tag in git-fast-import.txt The tag command does not take a trailing LF. Signed-off-by: Richard P. Curnow Signed-off-by: Junio C Hamano --- Documentation/git-fast-import.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index eaba6fd4c1..8d06775a6b 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -548,7 +548,6 @@ lightweight (non-annotated) tags see the `reset` command below. 'from' SP LF 'tagger' SP SP LT GT SP LF data - LF .... where `` is the name of the tag to create. From 7b6e0eb3c38696c89f7527318e010b339ba978df Mon Sep 17 00:00:00 2001 From: Quy Tonthat Date: Thu, 10 May 2007 17:03:15 +1000 Subject: [PATCH 65/77] Added new git-gui library files to rpm spec "make rpm" breaks without these files. Signed-off-by: Quy Tonthat Signed-off-by: Junio C Hamano --- git.spec.in | 1 + 1 file changed, 1 insertion(+) diff --git a/git.spec.in b/git.spec.in index 9de655c182..16148d4019 100644 --- a/git.spec.in +++ b/git.spec.in @@ -163,6 +163,7 @@ rm -rf $RPM_BUILD_ROOT %defattr(-,root,root) %{_bindir}/git-gui %{_bindir}/git-citool +%{_datadir}/git-gui/ # Not Yet... # %{!?_without_docs: %{_mandir}/man1/git-gui.1} # %{!?_without_docs: %doc Documentation/git-gui.html} From 7f9778b19b07601ae8134fc4ff23b7bf7cac28bd Mon Sep 17 00:00:00 2001 From: Gerrit Pape Date: Thu, 10 May 2007 07:32:07 +0000 Subject: [PATCH 66/77] gitweb: choose appropriate view for file type if a= parameter missing gitweb URLs use the a= parameter for the view to use on the given path, such as "blob" or "tree". Currently, if a gitweb URL omits the a= parameter, gitweb just shows the top-level repository summary, regardless of the path given. gitweb could instead choose an appropriate view based on the file type: blob for blobs (files), tree for trees (directories), and summary if no path given (the URL included no f= parameter, or an empty f= parameter). Apart from making gitweb more robust and supporting URL editing more easily, this change would aid the creation of shortcuts to git repositories using simple substitution, such as: http://example.org/git/?p=path/to/repo.git;hb=HEAD;f=%s With this patch, if given the hash through the h= parameter, or the hash base (hb=) and a filename (f=), gitweb uses cat-file -t to automatically set the a= parameter. This feature was requested by Josh Triplett through http://bugs.debian.org/410465 Signed-off-by: Gerrit Pape Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 90243fdf98..21864c62a9 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -458,10 +458,16 @@ my %actions = ( "project_index" => \&git_project_index, ); -if (defined $project) { - $action ||= 'summary'; -} else { - $action ||= 'project_list'; +if (!defined $action) { + if (defined $hash) { + $action = git_get_type($hash); + } elsif (defined $hash_base && defined $file_name) { + $action = git_get_type("$hash_base:$file_name"); + } elsif (defined $project) { + $action = 'summary'; + } else { + $action = 'project_list'; + } } if (!defined($actions{$action})) { die_error(undef, "Unknown action"); From c2f599e09fd0496413d1744b5b89b9b5c223555d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 9 May 2007 17:11:15 -0700 Subject: [PATCH 67/77] git-clone: don't get fooled by $PWD If you have /home/me/git symlink pointing at /pub/git/mine, trying to clone from /pub/git/his/ using relative path would not work as expected: $ cd /home/me $ cd git $ ls ../ his mine $ git clone -l -s -n ../his/stuff.git This is because "cd ../his/stuff.git" done inside git-clone to check if the repository is local is confused by $PWD, which is set to /home/me, and tries to go to /home/his/stuff.git which is different from /pub/git/his/stuff.git. We could probably say "set -P" (or "cd -P") instead, if we know the shell is POSIX, but the way the patch is coded is probably more portable. [jc: this is updated with Andy Whitcroft's improvements] Signed-off-by: Junio C Hamano --- git-clone.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/git-clone.sh b/git-clone.sh index cad5c0c088..70374aaaf0 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -18,7 +18,14 @@ usage() { } get_repo_base() { - (cd "$1" && (cd .git ; pwd)) 2> /dev/null + ( + cd "`/bin/pwd`" && + cd "$1" && + { + cd .git 2>/dev/null + pwd + } + ) } if [ -n "$GIT_SSL_NO_VERIFY" ]; then From e18ee576b1420e7b2cfc5c686872fa3b439ac45f Mon Sep 17 00:00:00 2001 From: Jari Aalto Date: Mon, 30 Apr 2007 21:37:57 +0300 Subject: [PATCH 68/77] SPECIFYING RANGES typo fix: it it => it is Signed-off-by: Jari Aalto Signed-off-by: Junio C Hamano --- Documentation/git-rev-parse.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index a8bf6561e1..7757abe621 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -255,7 +255,7 @@ reachable from `r1` from the set of commits reachable from A similar notation "`r1\...r2`" is called symmetric difference of `r1` and `r2` and is defined as "`r1 r2 --not $(git-merge-base --all r1 r2)`". -It it the set of commits that are reachable from either one of +It is the set of commits that are reachable from either one of `r1` or `r2` but not from both. Two other shorthands for naming a set that is formed by a commit From b722b9585518c9d75ec5d8731cb90dc0c9fb918c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 10 May 2007 13:24:21 -0700 Subject: [PATCH 69/77] .mailmap: add some aliases --- .mailmap | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.mailmap b/.mailmap index 17e89af118..4e0615e9be 100644 --- a/.mailmap +++ b/.mailmap @@ -35,7 +35,11 @@ Shawn O. Pearce Theodore Ts'o Tony Luck Uwe Kleine-König +Uwe Kleine-König +Uwe Kleine-König +Uwe Kleine-König Ville Skyttä YOSHIFUJI Hideaki anonymous anonymous +Dana L. How From 35c49eeae7e71e8554f27958a16405397c69b552 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Wed, 9 May 2007 12:49:41 +0200 Subject: [PATCH 70/77] Git.pm: config_boolean() -> config_bool() This patch renames config_boolean() to config_bool() for consistency with the commandline interface and because it is shorter but still obvious. ;-) It also changes the return value from some obscure string to real Perl boolean, allowing for clean user code. Signed-off-by: Petr Baudis --- git-remote.perl | 4 ++-- git-send-email.perl | 4 ++-- perl/Git.pm | 12 ++++++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/git-remote.perl b/git-remote.perl index 52013fe76d..5763799127 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -297,9 +297,9 @@ sub update_remote { } elsif ($name eq 'default') { undef @remotes; for (sort keys %$remote) { - my $do_fetch = $git->config_boolean("remote." . $_ . + my $do_fetch = $git->config_bool("remote." . $_ . ".skipDefaultUpdate"); - if (!defined($do_fetch) || $do_fetch ne "true") { + unless ($do_fetch) { push @remotes, $_; } } diff --git a/git-send-email.perl b/git-send-email.perl index a6e3e02619..404095f258 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -154,8 +154,8 @@ if ($@) { $term = new FakeTerm "$@: going non-interactive"; } -my $def_chain = $repo->config_boolean('sendemail.chainreplyto'); -if ($def_chain and $def_chain eq 'false') { +my $def_chain = $repo->config_bool('sendemail.chainreplyto'); +if (defined $def_chain and not $def_chain) { $chain_reply_to = 0; } diff --git a/perl/Git.pm b/perl/Git.pm index b5b1cf5edc..8fd3611753 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -516,9 +516,11 @@ sub config { } -=item config_boolean ( VARIABLE ) +=item config_bool ( VARIABLE ) -Retrieve the boolean configuration C. +Retrieve the bool configuration C. The return value +is usable as a boolean in perl (and C if it's not defined, +of course). Must be called on a repository instance. @@ -526,14 +528,16 @@ This currently wraps command('config') so it is not so fast. =cut -sub config_boolean { +sub config_bool { my ($self, $var) = @_; $self->repo_path() or throw Error::Simple("not a repository"); try { - return $self->command_oneline('config', '--bool', '--get', + my $val = $self->command_oneline('config', '--bool', '--get', $var); + return undef unless defined $val; + return $val eq 'true'; } catch Git::Error::Command with { my $E = shift; if ($E->value() == 1) { From 63fcbe00a66d7cb7d8bce9a36120a19a809a33b8 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Wed, 9 May 2007 03:41:50 +0200 Subject: [PATCH 71/77] gitweb: Do not use absolute font sizes Avoid specifying font sizes in pixels, since that is just pure evil. Pointed out by Chris Riddoch. Note that this is pretty much just a proposal; I didn't test if everything fits perfectly right, but things seem to be pretty much okay. repo.or.cz uses it now as a test drive - if you find any visual quirks, please point them out, with a patch if possible since I'm total CSS noob and debugging CSS is an extremely painful experience for me. Note that this patch actually does change visual look of gitweb in Firefox with my resolution and default settings - everything is bigger and I can't explain the joy of actually seeing gitweb text that is in _readable_ size; also, my horizontal screen real estate feels better used now. But judging from the look of most modern webpages on the 'net, most people prefer reading the web with strained eyes and/or a magnifying glass (I wonder what species of scientists should look into this mystifying phenomenon) - so, please tell us what you think. Maybe we might want to get rid of absolute sizes other than font sizes in the CSS file too in the long term. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano --- gitweb/gitweb.css | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index e795b70b2b..b57c8beccb 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -1,6 +1,5 @@ body { font-family: sans-serif; - font-size: 12px; border: solid #d9d8d1; border-width: 1px; margin: 10px; @@ -31,7 +30,7 @@ img.logo { div.page_header { height: 25px; padding: 8px; - font-size: 18px; + font-size: 150%; font-weight: bold; background-color: #d9d8d1; } @@ -113,7 +112,7 @@ span.signoff { div.log_link { padding: 0px 8px; - font-size: 10px; + font-size: 70%; font-family: sans-serif; font-style: normal; position: relative; @@ -204,13 +203,13 @@ table.blame { table.blame td { padding: 0px 5px; - font-size: 12px; + font-size: 100%; vertical-align: top; } th { padding: 2px 5px; - font-size: 12px; + font-size: 100%; text-align: left; } @@ -232,14 +231,14 @@ tr.dark:hover { td { padding: 2px 5px; - font-size: 12px; + font-size: 100%; vertical-align: top; } td.link, td.selflink { padding: 2px 5px; font-family: sans-serif; - font-size: 10px; + font-size: 70%; } td.selflink { @@ -416,7 +415,7 @@ div.index_include { } div.search { - font-size: 12px; + font-size: 100%; font-weight: normal; margin: 4px 8px; position: absolute; @@ -444,7 +443,7 @@ a.rss_logo { background-color: #ff6600; font-weight: bold; font-family: sans-serif; - font-size: 10px; + font-size: 70%; text-align: center; text-decoration: none; } @@ -455,7 +454,7 @@ a.rss_logo:hover { span.refs span { padding: 0px 4px; - font-size: 10px; + font-size: 70%; font-weight: normal; border: 1px solid; background-color: #ffaaff; From 419ca50e4c5afbf6a3f956db5531ed85f2b54bb9 Mon Sep 17 00:00:00 2001 From: "Richard P. Curnow" Date: Wed, 9 May 2007 23:13:44 +0100 Subject: [PATCH 72/77] Fix documentation of tag in git-fast-import.txt The tag command does not take a trailing LF. Signed-off-by: Richard P. Curnow Signed-off-by: Shawn O. Pearce --- Documentation/git-fast-import.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index eaba6fd4c1..8d06775a6b 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -548,7 +548,6 @@ lightweight (non-annotated) tags see the `reset` command below. 'from' SP LF 'tagger' SP SP LT GT SP LF data - LF .... where `` is the name of the tag to create. From 6b3d8b97cb48bcc6e2ac020312a56e1d9a2828b2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 9 May 2007 18:35:04 -0400 Subject: [PATCH 73/77] git-gui: Paperbag fix blame in subdirectory Signed-off-by: Shawn O. Pearce --- git-gui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index 62b1a36a8a..2fda4c2290 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1611,7 +1611,7 @@ blame { foreach a $argv { if {$is_path || [file exists $_prefix$a]} { if {$path ne {}} usage - set path $a + set path $_prefix$a break } elseif {$a eq {--}} { if {$path ne {}} { From d6da71a9d16b8cf27f9d8f90692d3625c849cbc8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 10 May 2007 17:54:45 -0400 Subject: [PATCH 74/77] git gui 0.7.0 Signed-off-by: Shawn O. Pearce --- GIT-VERSION-GEN | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 2741c1e14c..25647c8060 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=0.6.GITGUI +DEF_VER=0.7.GITGUI LF=' ' From 1dcb3b6478cac3d301349c2df4af2e5ec69dfffe Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 10 May 2007 18:10:36 -0400 Subject: [PATCH 75/77] Correct error message in revert/cherry-pick We now write to MERGE_MSG, not .msg. I missed this earlier when I changed the target we write to. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-revert.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-revert.c b/builtin-revert.c index 55d4fa1e6c..ea2f15b977 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -133,7 +133,7 @@ static void add_to_msg(const char *string) { int len = strlen(string); if (write_in_full(msg_fd, string, len) < 0) - die ("Could not write to .msg"); + die ("Could not write to MERGE_MSG"); } static void add_message_to_msg(const char *message) From 56822cc9ab9003c80b18201c097e4faa6f0ffbd3 Mon Sep 17 00:00:00 2001 From: Michael Hendricks Date: Thu, 10 May 2007 16:09:38 -0600 Subject: [PATCH 76/77] Document 'git-log --decorate' Signed-off-by: Michael Hendricks Signed-off-by: Junio C Hamano --- Documentation/git-log.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 49bb539dea..dd06527a1e 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -51,6 +51,9 @@ include::pretty-formats.txt[] a record about how the tip of a reference was changed. See also gitlink:git-reflog[1]. +--decorate:: + Print out the ref names of any commits that are shown. + ...:: Show only commits that affect the specified paths. From 843142ada000a992fa87bd2dc7796501332a52d9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 10 May 2007 14:49:36 -0700 Subject: [PATCH 77/77] GIT v1.5.2-rc3 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.5.2.txt | 9 ++++++++- GIT-VERSION-GEN | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Documentation/RelNotes-1.5.2.txt b/Documentation/RelNotes-1.5.2.txt index c14f3da7aa..42e7fa5bec 100644 --- a/Documentation/RelNotes-1.5.2.txt +++ b/Documentation/RelNotes-1.5.2.txt @@ -44,6 +44,11 @@ Updates since v1.5.1 * Comes with an updated git-gui 0.7.0 +* Updated gitweb: + + - can show combined diff for merges; + - uses font size of user's preference, not hardcoded in pixels; + * New commands and options. - "git bisect start" can optionally take a single bad commit and @@ -141,6 +146,8 @@ Updates since v1.5.1 - Optimized "git-add $path" in a large directory, most of whose contents are ignored. + - Optimized "git-diff-tree" for reduced memory footprint. + - The recursive merge strategy updated a worktree file that was changed identically in two branches, when one of them renamed it. We do not do that when there is no rename, so @@ -187,6 +194,6 @@ this release, unless otherwise noted. -- exec >/var/tmp/1 -O=v1.5.2-rc2-45-g618e613 +O=v1.5.2-rc2-91-g616e40b echo O=`git describe refs/heads/master` git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index f6adb91ed2..bd4a0443c4 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.5.2-rc2.GIT +DEF_VER=v1.5.2-rc3.GIT LF=' '
" . + $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + file_name=>$diff{'to_file'}, + hash_base=>$hash), + -class => "list"}, esc_path($diff{'to_file'})) . + "" . + esc_path($diff{'to_file'}) . + "" . + $cgi->a({-href => "#patch$patchno"}, "patch") . + " | " . + " | " . + $cgi->a({-href => href(action=>"blob", + hash_base=>$hash, + hash=>$from_hash, + file_name=>$from_path)}, + "blob" . ($i+1)) . + " | "; + } + print $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, + hash_parent=>$from_hash, + hash_base=>$hash, + hash_parent_base=>$hash_parent, + file_name=>$diff{'to_file'}, + file_parent=>$from_path)}, + "diff" . ($i+1)) . + " | "; + if ($not_deleted) { + print $cgi->a({-href => href(action=>"blob", + hash=>$diff{'to_id'}, + file_name=>$diff{'to_file'}, + hash_base=>$hash)}, + "blob"); + print " | " if ($has_history); + } + if ($has_history) { + print $cgi->a({-href => href(action=>"history", + file_name=>$diff{'to_file'}, + hash_base=>$hash)}, + "history"); + } + print "
" . @@ -2288,16 +2371,12 @@ sub git_difftree_body { for (my $i = 0; $i < $diff{'nparents'}; $i++) { my $hash_parent = $parents[$i]; my $from_hash = $diff{'from_id'}[$i]; - my $from_path = undef; + my $from_path = $diff{'from_file'}[$i]; my $status = $diff{'status'}[$i]; $has_history ||= ($status ne 'A'); $not_deleted ||= ($status ne 'D'); - if ($status eq 'R' || $status eq 'C') { - $from_path = git_get_path_by_hash($hash_parent, $from_hash); - } - if ($status eq 'A') { print " |
" . - $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - file_name=>$diff{'to_file'}, + $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'}, + file_name=>$diff->{'to_file'}, hash_base=>$hash), - -class => "list"}, esc_path($diff{'to_file'})) . + -class => "list"}, esc_path($diff->{'to_file'})) . "" . - esc_path($diff{'to_file'}) . + esc_path($diff->{'to_file'}) . ""; } print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, + hash=>$diff->{'to_id'}, hash_parent=>$from_hash, hash_base=>$hash, hash_parent_base=>$hash_parent, - file_name=>$diff{'to_file'}, + file_name=>$diff->{'to_file'}, file_parent=>$from_path)}, "diff" . ($i+1)) . " | "; if ($not_deleted) { print $cgi->a({-href => href(action=>"blob", - hash=>$diff{'to_id'}, - file_name=>$diff{'to_file'}, + hash=>$diff->{'to_id'}, + file_name=>$diff->{'to_file'}, hash_base=>$hash)}, "blob"); print " | " if ($has_history); } if ($has_history) { print $cgi->a({-href => href(action=>"history", - file_name=>$diff{'to_file'}, + file_name=>$diff->{'to_file'}, hash_base=>$hash)}, "history"); } @@ -2429,29 +2436,29 @@ sub git_difftree_body { my ($to_mode_oct, $to_mode_str, $to_file_type); my ($from_mode_oct, $from_mode_str, $from_file_type); - if ($diff{'to_mode'} ne ('0' x 6)) { - $to_mode_oct = oct $diff{'to_mode'}; + if ($diff->{'to_mode'} ne ('0' x 6)) { + $to_mode_oct = oct $diff->{'to_mode'}; if (S_ISREG($to_mode_oct)) { # only for regular file $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits } - $to_file_type = file_type($diff{'to_mode'}); + $to_file_type = file_type($diff->{'to_mode'}); } - if ($diff{'from_mode'} ne ('0' x 6)) { - $from_mode_oct = oct $diff{'from_mode'}; + if ($diff->{'from_mode'} ne ('0' x 6)) { + $from_mode_oct = oct $diff->{'from_mode'}; if (S_ISREG($to_mode_oct)) { # only for regular file $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits } - $from_file_type = file_type($diff{'from_mode'}); + $from_file_type = file_type($diff->{'from_mode'}); } - if ($diff{'status'} eq "A") { # created + if ($diff->{'status'} eq "A") { # created my $mode_chng = "[new $to_file_type"; $mode_chng .= " with mode: $to_mode_str" if $to_mode_str; $mode_chng .= "]"; print ""; - print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'}), - -class => "list"}, esc_path($diff{'file'})); + print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'}, + hash_base=>$hash, file_name=>$diff->{'file'}), + -class => "list"}, esc_path($diff->{'file'})); print "$mode_chng"; @@ -2461,17 +2468,17 @@ sub git_difftree_body { print $cgi->a({-href => "#patch$patchno"}, "patch"); print " | "; } - print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'})}, + print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'}, + hash_base=>$hash, file_name=>$diff->{'file'})}, "blob"); print ""; - print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, - hash_base=>$parent, file_name=>$diff{'file'}), - -class => "list"}, esc_path($diff{'file'})); + print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'}, + hash_base=>$parent, file_name=>$diff->{'file'}), + -class => "list"}, esc_path($diff->{'file'})); print "$mode_chng"; @@ -2481,22 +2488,22 @@ sub git_difftree_body { print $cgi->a({-href => "#patch$patchno"}, "patch"); print " | "; } - print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, - hash_base=>$parent, file_name=>$diff{'file'})}, + print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'}, + hash_base=>$parent, file_name=>$diff->{'file'})}, "blob") . " | "; if ($have_blame) { print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, - file_name=>$diff{'file'})}, + file_name=>$diff->{'file'})}, "blame") . " | "; } print $cgi->a({-href => href(action=>"history", hash_base=>$parent, - file_name=>$diff{'file'})}, + file_name=>$diff->{'file'})}, "history"); print ""; - print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'}), - -class => "list"}, esc_path($diff{'file'})); + print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'}, + hash_base=>$hash, file_name=>$diff->{'file'}), + -class => "list"}, esc_path($diff->{'file'})); print "$mode_chnge"; @@ -2522,70 +2529,70 @@ sub git_difftree_body { $patchno++; print $cgi->a({-href => "#patch$patchno"}, "patch") . " | "; - } elsif ($diff{'to_id'} ne $diff{'from_id'}) { + } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) { # "commit" view and modified file (not onlu mode changed) print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'}, hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'file'})}, + file_name=>$diff->{'file'})}, "diff") . " | "; } - print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'})}, + print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'}, + hash_base=>$hash, file_name=>$diff->{'file'})}, "blob") . " | "; if ($have_blame) { print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, - file_name=>$diff{'file'})}, + file_name=>$diff->{'file'})}, "blame") . " | "; } print $cgi->a({-href => href(action=>"history", hash_base=>$hash, - file_name=>$diff{'file'})}, + file_name=>$diff->{'file'})}, "history"); print "" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diff{'to_id'}, file_name=>$diff{'to_file'}), - -class => "list"}, esc_path($diff{'to_file'})) . "[$nstatus from " . $cgi->a({-href => href(action=>"blob", hash_base=>$parent, - hash=>$diff{'from_id'}, file_name=>$diff{'from_file'}), - -class => "list"}, esc_path($diff{'from_file'})) . - " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]"; if ($action eq 'commitdiff') { # link to patch $patchno++; print $cgi->a({-href => "#patch$patchno"}, "patch") . " | "; - } elsif ($diff{'to_id'} ne $diff{'from_id'}) { + } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) { # "commit" view and modified file (not only pure rename or copy) print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'}, hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, + file_name=>$diff->{'to_file'}, file_parent=>$diff->{'from_file'})}, "diff") . " | "; } - print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$parent, file_name=>$diff{'to_file'})}, + print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'}, + hash_base=>$parent, file_name=>$diff->{'to_file'})}, "blob") . " | "; if ($have_blame) { print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, - file_name=>$diff{'to_file'})}, + file_name=>$diff->{'to_file'})}, "blame") . " | "; } print $cgi->a({-href => href(action=>"history", hash_base=>$hash, - file_name=>$diff{'to_file'})}, + file_name=>$diff->{'to_file'})}, "history"); print "