From cb07fc2a29c86d1bc11f5415368f778d25d3d20a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 6 Nov 2006 14:20:27 -0500 Subject: [PATCH 001/548] git-gui: Initial revision. This is based on Paul Mackerras' gitool prototype which he offered up to the community earlier in 2006. Its mostly however a rewrite from scratch of a Tcl/Tk based graphical interface for Git and the most common commands users might need to perform. Currently it can display the status of the current repository, and not much else. Signed-off-by: Shawn O. Pearce --- git-gui | 764 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 764 insertions(+) create mode 100755 git-gui diff --git a/git-gui b/git-gui new file mode 100755 index 0000000000..dfa3000260 --- /dev/null +++ b/git-gui @@ -0,0 +1,764 @@ +#!/bin/sh +# Tcl ignores the next line -*- tcl -*- \ +exec wish "$0" -- "$@" + +# Copyright (C) 2006 Shawn Pearce, Paul Mackerras. All rights reserved. +# This program is free software; it may be used, copied, modified +# and distributed under the terms of the GNU General Public Licence, +# either version 2, or (at your option) any later version. + + +###################################################################### +## +## status + +set status_active 0 + +proc update_status {} { + global gitdir HEAD commit_type + global ui_index ui_other ui_status_value + global status_active file_states + + if {$status_active > 0} return + + array unset file_states + set ui_status_value {Refreshing file status...} + foreach w [list $ui_index $ui_other] { + $w conf -state normal + $w delete 0.0 end + $w conf -state disabled + } + + if {[catch {set HEAD [exec git rev-parse --verify HEAD]}]} { + set commit_type initial + } else { + set commit_type normal + } + + set ls_others [list | git ls-files --others -z \ + --exclude-per-directory=.gitignore] + set info_exclude [file join $gitdir info exclude] + if {[file readable $info_exclude]} { + lappend ls_others "--exclude-from=$info_exclude" + } + + set fd_di [open "| git diff-index --cached -z $HEAD" r] + set fd_df [open "| git diff-files -z" r] + set fd_lo [open $ls_others r] + set status_active 3 + + fconfigure $fd_di -blocking 0 -translation binary + fconfigure $fd_df -blocking 0 -translation binary + fconfigure $fd_lo -blocking 0 -translation binary + fileevent $fd_di readable [list read_diff_index $fd_di] + fileevent $fd_df readable [list read_diff_files $fd_df] + fileevent $fd_lo readable [list read_ls_others $fd_lo] +} + +proc read_diff_index {fd} { + global buf_rdi + + append buf_rdi [read $fd] + set pck [split $buf_rdi "\0"] + set buf_rdi [lindex $pck end] + foreach {m p} [lrange $pck 0 end-1] { + if {$m != {} && $p != {}} { + display_file $p [string index $m end]_ + } + } + status_eof $fd buf_rdi +} + +proc read_diff_files {fd} { + global buf_rdf + + append buf_rdf [read $fd] + set pck [split $buf_rdf "\0"] + set buf_rdf [lindex $pck end] + foreach {m p} [lrange $pck 0 end-1] { + if {$m != {} && $p != {}} { + display_file $p _[string index $m end] + } + } + status_eof $fd buf_rdf +} + +proc read_ls_others {fd} { + global buf_rlo + + append buf_rlo [read $fd] + set pck [split $buf_rlo "\0"] + set buf_rlo [lindex $pck end] + foreach p [lrange $pck 0 end-1] { + display_file $p _O + } + status_eof $fd buf_rlo +} + +proc status_eof {fd buf} { + global status_active $buf + global ui_fname_value ui_status_value + + if {[eof $fd]} { + set $buf {} + close $fd + if {[incr status_active -1] == 0} { + set ui_status_value {Ready.} + if {$ui_fname_value != {}} { + show_diff $ui_fname_value + } + } + } +} + +###################################################################### +## +## diff + +set diff_active 0 + +proc clear_diff {} { + global ui_diff ui_fname_value ui_fstatus_value + + $ui_diff conf -state normal + $ui_diff delete 0.0 end + $ui_diff conf -state disabled + set ui_fname_value {} + set ui_fstatus_value {} +} + +proc show_diff {path} { + global file_states HEAD status_active diff_3way diff_active + global ui_diff ui_fname_value ui_fstatus_value ui_status_value + + if {$status_active > 0} return + if {$diff_active} return + + clear_diff + set s $file_states($path) + set m [lindex $s 0] + set diff_3way 0 + set diff_active 1 + set ui_fname_value $path + set ui_fstatus_value [mapdesc $m $path] + set ui_status_value "Loading diff of $path..." + + set cmd [list | git diff-index -p $HEAD -- $path] + switch $m { + AM { + } + MM { + set cmd [list | git diff-index -p -c $HEAD $path] + } + _O { + if {[catch { + set fd [open $path r] + set content [read $fd] + close $fd + } err ]} { + set ui_status_value "Unable to display $path" + error_popup "Error loading file:\n$err" + return + } + $ui_diff conf -state normal + $ui_diff insert end $content + $ui_diff conf -state disabled + return + } + } + + if {[catch {set fd [open $cmd r]} err]} { + set ui_status_value "Unable to display $path" + error_popup "Error loading diff:\n$err" + return + } + + fconfigure $fd -blocking 0 + fileevent $fd readable [list read_diff $fd] +} + +proc read_diff {fd} { + global ui_diff ui_status_value diff_3way diff_active + + while {[gets $fd line] >= 0} { + if {[string match index* $line]} { + if {[string first , $line] >= 0} { + set diff_3way 1 + } + } + + $ui_diff conf -state normal + if {!$diff_3way} { + set x [string index $line 0] + switch -- $x { + "@" {set tags da} + "+" {set tags dp} + "-" {set tags dm} + default {set tags {}} + } + } else { + set x [string range $line 0 1] + switch -- $x { + default {set tags {}} + "@@" {set tags da} + "++" {set tags dp; set x " +"} + " +" {set tags {di bold}; set x "++"} + "+ " {set tags dni; set x "-+"} + "--" {set tags dm; set x " -"} + " -" {set tags {dm bold}; set x "--"} + "- " {set tags di; set x "+-"} + default {set tags {}} + } + set line [string replace $line 0 1 $x] + } + $ui_diff insert end $line $tags + $ui_diff insert end "\n" + $ui_diff conf -state disabled + } + + if {[eof $fd]} { + close $fd + set diff_active 0 + set ui_status_value {Ready.} + } +} + +###################################################################### +## +## ui helpers + +proc mapcol {state path} { + global all_cols + + if {[catch {set r $all_cols($state)}]} { + puts "error: no column for state={$state} $path" + return o + } + return $r +} + +proc mapicon {state path} { + global all_icons + + if {[catch {set r $all_icons($state)}]} { + puts "error: no icon for state={$state} $path" + return file_plain + } + return $r +} + +proc mapdesc {state path} { + global all_descs + + if {[catch {set r $all_descs($state)}]} { + puts "error: no desc for state={$state} $path" + return $state + } + return $r +} + +proc bsearch {w path} { + set hi [expr [lindex [split [$w index end] .] 0] - 2] + if {$hi == 0} { + return -1 + } + set lo 0 + while {$lo < $hi} { + set mi [expr [expr $lo + $hi] / 2] + set ti [expr $mi + 1] + set cmp [string compare [$w get $ti.1 $ti.end] $path] + if {$cmp < 0} { + set lo $ti + } elseif {$cmp == 0} { + return $mi + } else { + set hi $mi + } + } + return -[expr $lo + 1] +} + +proc merge_state {path state} { + global file_states + + if {[array names file_states -exact $path] == {}} { + set o __ + set s [list $o none none] + } else { + set s $file_states($path) + set o [lindex $s 0] + } + + set m [lindex $s 0] + if {[string index $state 0] == "_"} { + set state [string index $m 0][string index $state 1] + } elseif {[string index $state 0] == "*"} { + set state _[string index $state 1] + } + + if {[string index $state 1] == "_"} { + set state [string index $state 0][string index $m 1] + } elseif {[string index $state 1] == "*"} { + set state [string index $state 0]_ + } + + set file_states($path) [lreplace $s 0 0 $state] + return $o +} + +proc display_file {path state} { + global ui_index ui_other file_states + + set old_m [merge_state $path $state] + set s $file_states($path) + set m [lindex $s 0] + + if {[mapcol $m $path] == "o"} { + set ii 1 + set ai 2 + set iw $ui_index + set aw $ui_other + } else { + set ii 2 + set ai 1 + set iw $ui_other + set aw $ui_index + } + + set d [lindex $s $ii] + if {$d != "none"} { + set lno [bsearch $iw $path] + if {$lno >= 0} { + incr lno + $iw conf -state normal + $iw delete $lno.0 [expr $lno + 1].0 + $iw conf -state disabled + set s [lreplace $s $ii $ii none] + } + } + + set d [lindex $s $ai] + if {$d == "none"} { + set lno [expr abs([bsearch $aw $path] + 1) + 1] + $aw conf -state normal + set ico [$aw image create $lno.0 \ + -align center -padx 5 -pady 1 \ + -image [mapicon $m $path]] + $aw insert $lno.1 "$path\n" + $aw conf -state disabled + set file_states($path) [lreplace $s $ai $ai [list $ico]] + } elseif {[mapicon $m $path] != [mapicon $old_m $path]} { + set ico [lindex $d 0] + $aw image conf $ico -image [mapicon $m $path] + } +} + +proc toggle_mode {path} { + global file_states + + set s $file_states($path) + set m [lindex $s 0] + + switch -- $m { + AM - + _O { + set new A* + set cmd [list exec git update-index --add $path] + } + MM { + set new M* + set cmd [list exec git update-index $path] + } + _D { + set new D* + set cmd [list exec git update-index --remove $path] + } + default { + return + } + } + + if {[catch {eval $cmd} err]} { + error_popup "Error processing file:\n$err" + return + } + display_file $path $new +} + +###################################################################### +## +## icons + +set filemask { +#define mask_width 14 +#define mask_height 15 +static unsigned char mask_bits[] = { + 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, + 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, + 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f}; +} + +image create bitmap file_plain -background white -foreground black -data { +#define plain_width 14 +#define plain_height 15 +static unsigned char plain_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10, + 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, + 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_mod -background white -foreground blue -data { +#define mod_width 14 +#define mod_height 15 +static unsigned char mod_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10, + 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, + 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_tick -background white -foreground "#007000" -data { +#define file_tick_width 14 +#define file_tick_height 15 +static unsigned char file_tick_bits[] = { + 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16, + 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10, + 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_parttick -background white -foreground "#005050" -data { +#define parttick_width 14 +#define parttick_height 15 +static unsigned char parttick_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10, + 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10, + 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_question -background white -foreground black -data { +#define file_question_width 14 +#define file_question_height 15 +static unsigned char file_question_bits[] = { + 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13, + 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10, + 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_removed -background white -foreground red -data { +#define file_removed_width 14 +#define file_removed_height 15 +static unsigned char file_removed_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10, + 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13, + 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_merge -background white -foreground blue -data { +#define file_merge_width 14 +#define file_merge_height 15 +static unsigned char file_merge_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10, + 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, + 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +foreach i { + {__ i "Unmodified" plain} + {_M i "Modified" mod} + {M_ i "Checked in" tick} + {MM i "Partially checked in" parttick} + + {_O o "Untracked" plain} + {A_ o "Added" tick} + {AM o "Partially added" parttick} + + {_D i "Missing" question} + {D_ i "Removed" removed} + {DD i "Removed" removed} + {DO i "Partially removed" removed} + + {UM i "Merge conflicts" merge} + {U_ i "Merge conflicts" merge} + } { + set all_cols([lindex $i 0]) [lindex $i 1] + set all_descs([lindex $i 0]) [lindex $i 2] + set all_icons([lindex $i 0]) file_[lindex $i 3] +} +unset filemask i + +###################################################################### +## +## util + +proc error_popup {msg} { + set w .error + toplevel $w + wm transient $w . + show_msg $w $w $msg +} + +proc show_msg {w top msg} { + message $w.m -text $msg -justify center -aspect 400 + pack $w.m -side top -fill x -padx 20 -pady 20 + button $w.ok -text OK -command "destroy $top" + pack $w.ok -side bottom -fill x + bind $top "grab $top; focus $top" + bind $top "destroy $top" + tkwait window $top +} + +###################################################################### +## +## ui commands + +proc do_gitk {} { + global tcl_platform + + if {$tcl_platform(platform) == "windows"} { + exec sh -c gitk & + } else { + exec gitk & + } +} + +proc do_quit {} { + destroy . +} + +proc do_rescan {} { + update_status +} + +# shift == 1: left click +# 3: right click +proc click {w x y shift wx wy} { + set pos [split [$w index @$x,$y] .] + set lno [lindex $pos 0] + set col [lindex $pos 1] + set path [$w get $lno.1 $lno.end] + if {$path == {}} return + + if {$col > 0 && $shift == 1} { + show_diff $path + } +} + +proc unclick {w x y} { + set pos [split [$w index @$x,$y] .] + set lno [lindex $pos 0] + set col [lindex $pos 1] + set path [$w get $lno.1 $lno.end] + if {$path == {}} return + + if {$col == 0} { + toggle_mode $path + } +} + +###################################################################### +## +## ui init + +set mainfont {Helvetica 10} +set difffont {Courier 10} +set maincursor [. cget -cursor] + +# -- Menu Bar +menu .mbar -tearoff 0 +.mbar add cascade -label Project -menu .mbar.project +.mbar add cascade -label Commit -menu .mbar.commit +.mbar add cascade -label Fetch -menu .mbar.fetch +.mbar add cascade -label Pull -menu .mbar.pull +. configure -menu .mbar + +# -- Project Menu +menu .mbar.project +.mbar.project add command -label Visulize \ + -command do_gitk \ + -font $mainfont +.mbar.project add command -label Quit \ + -command do_quit \ + -font $mainfont + +# -- Commit Menu +menu .mbar.commit +.mbar.commit add command -label Rescan \ + -command do_rescan \ + -font $mainfont + +# -- Fetch Menu +menu .mbar.fetch + +# -- Pull Menu +menu .mbar.pull + +# -- Main Window Layout +panedwindow .vpane -orient vertical +panedwindow .vpane.files -orient horizontal +.vpane add .vpane.files -sticky nsew +pack .vpane -anchor n -side top -fill both -expand 1 + +# -- Index File List +set ui_index .vpane.files.index.list +frame .vpane.files.index -height 100 -width 400 +label .vpane.files.index.title -text {Modified Files} \ + -background green \ + -font $mainfont +text $ui_index -background white -borderwidth 0 \ + -width 40 -height 10 \ + -font $mainfont \ + -yscrollcommand {.vpane.files.index.sb set} \ + -cursor $maincursor \ + -state disabled +scrollbar .vpane.files.index.sb -command [list $ui_index yview] +pack .vpane.files.index.title -side top -fill x +pack .vpane.files.index.sb -side right -fill y +pack $ui_index -side left -fill both -expand 1 +.vpane.files add .vpane.files.index -sticky nsew + +# -- Other (Add) File List +set ui_other .vpane.files.other.list +frame .vpane.files.other -height 100 -width 100 +label .vpane.files.other.title -text {Untracked Files} \ + -background red \ + -font $mainfont +text $ui_other -background white -borderwidth 0 \ + -width 40 -height 10 \ + -font $mainfont \ + -yscrollcommand {.vpane.files.other.sb set} \ + -cursor $maincursor \ + -state disabled +scrollbar .vpane.files.other.sb -command [list $ui_other yview] +pack .vpane.files.other.title -side top -fill x +pack .vpane.files.other.sb -side right -fill y +pack $ui_other -side left -fill both -expand 1 +.vpane.files add .vpane.files.other -sticky nsew + +# -- Diff Header +set ui_fname_value {} +set ui_fstatus_value {} +frame .vpane.diff -height 100 -width 100 +frame .vpane.diff.header +label .vpane.diff.header.l1 -text {File:} -font $mainfont +label .vpane.diff.header.l2 -textvariable ui_fname_value \ + -anchor w \ + -justify left \ + -font $mainfont +label .vpane.diff.header.l3 -text {Status:} -font $mainfont +label .vpane.diff.header.l4 -textvariable ui_fstatus_value \ + -width 20 \ + -anchor w \ + -justify left \ + -font $mainfont +pack .vpane.diff.header.l1 -side left +pack .vpane.diff.header.l2 -side left -fill x +pack .vpane.diff.header.l4 -side right +pack .vpane.diff.header.l3 -side right + +# -- Diff Body +frame .vpane.diff.body +set ui_diff .vpane.diff.body.t +text $ui_diff -background white -borderwidth 0 \ + -width 40 -height 20 \ + -font $difffont \ + -xscrollcommand {.vpane.diff.body.sbx set} \ + -yscrollcommand {.vpane.diff.body.sby set} \ + -cursor $maincursor \ + -state disabled +scrollbar .vpane.diff.body.sbx -orient horizontal \ + -command [list $ui_diff xview] +scrollbar .vpane.diff.body.sby -orient vertical \ + -command [list $ui_diff yview] +pack .vpane.diff.body.sbx -side bottom -fill x +pack .vpane.diff.body.sby -side right -fill y +pack $ui_diff -side left -fill both -expand 1 +pack .vpane.diff.header -side top -fill x +pack .vpane.diff.body -side bottom -fill both -expand 1 +.vpane add .vpane.diff -stick nsew + +$ui_diff tag conf dm -foreground red +$ui_diff tag conf dp -foreground blue +$ui_diff tag conf da -font [concat $difffont bold] +$ui_diff tag conf di -foreground "#00a000" +$ui_diff tag conf dni -foreground "#a000a0" +$ui_diff tag conf bold -font [concat $difffont bold] + +# -- Commit Area +frame .vpane.commarea -height 50 +.vpane add .vpane.commarea -stick nsew + +# -- Commit Area Buttons +frame .vpane.commarea.buttons +label .vpane.commarea.buttons.l -text {} \ + -anchor w \ + -justify left \ + -font $mainfont +pack .vpane.commarea.buttons.l -side top -fill x +button .vpane.commarea.buttons.rescan -text {Rescan} \ + -command do_rescan \ + -font $mainfont +pack .vpane.commarea.buttons.rescan -side top -fill x +button .vpane.commarea.buttons.ciall -text {Check-in All} \ + -command do_checkin_all \ + -font $mainfont +pack .vpane.commarea.buttons.ciall -side top -fill x +button .vpane.commarea.buttons.commit -text {Commit} \ + -command do_commit \ + -font $mainfont +pack .vpane.commarea.buttons.commit -side top -fill x +pack .vpane.commarea.buttons -side left -fill y + +# -- Commit Message Buffer +frame .vpane.commarea.buffer +set ui_comm .vpane.commarea.buffer.t +label .vpane.commarea.buffer.l -text {Commit Message:} \ + -anchor w \ + -justify left \ + -font $mainfont +text $ui_comm -background white -borderwidth 1 \ + -relief sunken \ + -width 75 -height 10 -wrap none \ + -font $difffont \ + -yscrollcommand {.vpane.commarea.buffer.sby set} \ + -cursor $maincursor +scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview] +pack .vpane.commarea.buffer.l -side top -fill x +pack .vpane.commarea.buffer.sby -side right -fill y +pack $ui_comm -side left -fill y +pack .vpane.commarea.buffer -side left -fill y + +# -- Status Bar +set ui_status_value {Initializing...} +label .status -textvariable ui_status_value \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken \ + -font $mainfont +pack .status -anchor w -side bottom -fill x + +# -- Key Bindings +bind . do_quit +bind . do_rescan +bind . do_rescan +bind . do_rescan +bind . do_quit +bind . do_quit +foreach i [list $ui_index $ui_other] { + bind $i {click %W %x %y 1 %X %Y; break} + bind $i {click %W %x %y 3 %X %Y; break} + bind $i {unclick %W %x %y; break} +} +unset i + +###################################################################### +## +## main + +if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} { + show_msg {} . "Cannot find the git directory: $err" + exit 1 +} + +wm title . "git-ui ([file normalize [file dirname $gitdir]])" +focus -force $ui_comm +update_status From 131f503b7262b001eae434182feb08cb5e6fa4be Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 6 Nov 2006 16:07:32 -0500 Subject: [PATCH 002/548] git-gui: Additional early feature development. * Run refresh before diff-index. * Load saved commit message during rescan. * Save current commit message (if any) during quit. * Add Signed-off-by line to commit buffer. * Batch update-index invocations through --stdin. * Better highlight which file is in the diff viewer. * Key bindings for signoff, check-in all and commit. * Improved formatting of status table within source. Signed-off-by: Shawn O. Pearce --- git-gui | 264 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 211 insertions(+), 53 deletions(-) diff --git a/git-gui b/git-gui index dfa3000260..d74509a20b 100755 --- a/git-gui +++ b/git-gui @@ -13,16 +13,30 @@ exec wish "$0" -- "$@" ## status set status_active 0 +set diff_active 0 +set checkin_active 0 +set update_index_fd {} + +proc is_busy {} { + global status_active diff_active checkin_active update_index_fd + + if {$status_active > 0 + || $diff_active + || $checkin_active + || $update_index_fd != {}} { + return 1 + } + return 0 +} proc update_status {} { global gitdir HEAD commit_type - global ui_index ui_other ui_status_value + global ui_index ui_other ui_status_value ui_comm global status_active file_states - if {$status_active > 0} return + if {[is_busy]} return array unset file_states - set ui_status_value {Refreshing file status...} foreach w [list $ui_index $ui_other] { $w conf -state normal $w delete 0.0 end @@ -35,6 +49,31 @@ proc update_status {} { set commit_type normal } + if {![$ui_comm edit modified] + || [string trim [$ui_comm get 0.0 end]] == {}} { + if {[load_message GITGUI_MSG]} { + } elseif {[load_message MERGE_MSG]} { + } elseif {[load_message SQUASH_MSG]} { + } + $ui_comm edit modified false + } + + set status_active 1 + set ui_status_value {Refreshing file status...} + set fd_rf [open "| git update-index -q --unmerged --refresh" r] + fconfigure $fd_rf -blocking 0 -translation binary + fileevent $fd_rf readable [list read_refresh $fd_rf] +} + +proc read_refresh {fd} { + global gitdir HEAD commit_type + global ui_index ui_other ui_status_value ui_comm + global status_active file_states + + read $fd + if {![eof $fd]} return + close $fd + set ls_others [list | git ls-files --others -z \ --exclude-per-directory=.gitignore] set info_exclude [file join $gitdir info exclude] @@ -42,10 +81,11 @@ proc update_status {} { lappend ls_others "--exclude-from=$info_exclude" } + set status_active 3 + set ui_status_value {Scanning for modified files ...} set fd_di [open "| git diff-index --cached -z $HEAD" r] set fd_df [open "| git diff-files -z" r] set fd_lo [open $ls_others r] - set status_active 3 fconfigure $fd_di -blocking 0 -translation binary fconfigure $fd_df -blocking 0 -translation binary @@ -55,6 +95,23 @@ proc update_status {} { fileevent $fd_lo readable [list read_ls_others $fd_lo] } +proc load_message {file} { + global gitdir ui_comm + + set f [file join $gitdir $file] + if {[file exists $f]} { + if {[catch {set fd [open $f r]}]} { + return 0 + } + set content [read $fd] + close $fd + $ui_comm delete 0.0 end + $ui_comm insert end $content + return 1 + } + return 0 +} + proc read_diff_index {fd} { global buf_rdi @@ -115,8 +172,6 @@ proc status_eof {fd buf} { ## ## diff -set diff_active 0 - proc clear_diff {} { global ui_diff ui_fname_value ui_fstatus_value @@ -128,11 +183,10 @@ proc clear_diff {} { } proc show_diff {path} { - global file_states HEAD status_active diff_3way diff_active + global file_states HEAD diff_3way diff_active global ui_diff ui_fname_value ui_fstatus_value ui_status_value - if {$status_active > 0} return - if {$diff_active} return + if {[is_busy]} return clear_diff set s $file_states($path) @@ -156,6 +210,7 @@ proc show_diff {path} { set content [read $fd] close $fd } err ]} { + set diff_active 0 set ui_status_value "Unable to display $path" error_popup "Error loading file:\n$err" return @@ -168,12 +223,13 @@ proc show_diff {path} { } if {[catch {set fd [open $cmd r]} err]} { + set diff_active 0 set ui_status_value "Unable to display $path" error_popup "Error loading diff:\n$err" return } - fconfigure $fd -blocking 0 + fconfigure $fd -blocking 0 -translation binary fileevent $fd readable [list read_diff $fd] } @@ -353,6 +409,32 @@ proc display_file {path state} { } } +proc with_update_index {body} { + global update_index_fd + + if {$update_index_fd == {}} { + set update_index_fd [open \ + "| git update-index --add --remove -z --stdin" \ + w] + fconfigure $update_index_fd -translation binary + uplevel 1 $body + close $update_index_fd + set update_index_fd {} + } else { + uplevel 1 $body + } +} + +proc update_index {path} { + global update_index_fd + + if {$update_index_fd == {}} { + error {not in with_update_index} + } else { + puts -nonewline $update_index_fd "$path\0" + } +} + proc toggle_mode {path} { global file_states @@ -361,27 +443,14 @@ proc toggle_mode {path} { switch -- $m { AM - - _O { - set new A* - set cmd [list exec git update-index --add $path] - } - MM { - set new M* - set cmd [list exec git update-index $path] - } - _D { - set new D* - set cmd [list exec git update-index --remove $path] - } - default { - return - } + _O {set new A*} + _M - + MM {set new M*} + _D {set new D*} + default {return} } - if {[catch {eval $cmd} err]} { - error_popup "Error processing file:\n$err" - return - } + with_update_index {update_index $path} display_file $path $new } @@ -416,10 +485,10 @@ static unsigned char mod_bits[] = { 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; } -maskdata $filemask -image create bitmap file_tick -background white -foreground "#007000" -data { -#define file_tick_width 14 -#define file_tick_height 15 -static unsigned char file_tick_bits[] = { +image create bitmap file_fulltick -background white -foreground "#007000" -data { +#define file_fulltick_width 14 +#define file_fulltick_height 15 +static unsigned char file_fulltick_bits[] = { 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16, 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f}; @@ -461,27 +530,31 @@ static unsigned char file_merge_bits[] = { 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; } -maskdata $filemask +set max_status_desc 0 foreach i { - {__ i "Unmodified" plain} - {_M i "Modified" mod} - {M_ i "Checked in" tick} - {MM i "Partially checked in" parttick} + {__ i plain "Unmodified"} + {_M i mod "Modified"} + {M_ i fulltick "Checked in"} + {MM i parttick "Partially checked in"} - {_O o "Untracked" plain} - {A_ o "Added" tick} - {AM o "Partially added" parttick} + {_O o plain "Untracked"} + {A_ o fulltick "Added"} + {AM o parttick "Partially added"} - {_D i "Missing" question} - {D_ i "Removed" removed} - {DD i "Removed" removed} - {DO i "Partially removed" removed} + {_D i question "Missing"} + {D_ i removed "Removed"} + {DD i removed "Removed"} + {DO i removed "Removed (still exists)"} - {UM i "Merge conflicts" merge} - {U_ i "Merge conflicts" merge} + {UM i merge "Merge conflicts"} + {U_ i merge "Merge conflicts"} } { + if {$max_status_desc < [string length [lindex $i 3]]} { + set max_status_desc [string length [lindex $i 3]] + } set all_cols([lindex $i 0]) [lindex $i 1] - set all_descs([lindex $i 0]) [lindex $i 2] - set all_icons([lindex $i 0]) file_[lindex $i 3] + set all_icons([lindex $i 0]) file_[lindex $i 2] + set all_descs([lindex $i 0]) [lindex $i 3] } unset filemask i @@ -521,6 +594,20 @@ proc do_gitk {} { } proc do_quit {} { + global gitdir ui_comm + + set save [file join $gitdir GITGUI_MSG] + if {[$ui_comm edit modified] + && [string trim [$ui_comm get 0.0 end]] != {}} { + catch { + set fd [open $save w] + puts $fd [string trim [$ui_comm get 0.0 end]] + close $fd + } + } elseif {[file exists $save]} { + file delete $save + } + destroy . } @@ -528,9 +615,52 @@ proc do_rescan {} { update_status } +proc do_checkin_all {} { + global checkin_active ui_status_value + + if {[is_busy]} return + + set checkin_active 1 + set ui_status_value {Checking in all files...} + after 1 { + with_update_index { + foreach path [array names file_states] { + set s $file_states($path) + set m [lindex $s 0] + switch -- $m { + AM - + MM - + _M - + _D {toggle_mode $path} + } + } + } + set checkin_active 0 + set ui_status_value {Ready.} + } +} + +proc do_signoff {} { + global ui_comm + + catch { + set me [exec git var GIT_COMMITTER_IDENT] + if {[regexp {(.*) [0-9]+ [-+0-9]+$} $me me name]} { + set str "Signed-off-by: $name" + if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} { + $ui_comm insert end "\n" + $ui_comm insert end $str + $ui_comm see end + } + } + } +} + # shift == 1: left click # 3: right click proc click {w x y shift wx wy} { + global ui_index ui_other + set pos [split [$w index @$x,$y] .] set lno [lindex $pos 0] set col [lindex $pos 1] @@ -538,6 +668,9 @@ proc click {w x y shift wx wy} { if {$path == {}} return if {$col > 0 && $shift == 1} { + $ui_index tag remove in_diff 0.0 end + $ui_other tag remove in_diff 0.0 end + $w tag add in_diff $lno.0 [expr $lno + 1].0 show_diff $path } } @@ -549,7 +682,7 @@ proc unclick {w x y} { set path [$w get $lno.1 $lno.end] if {$path == {}} return - if {$col == 0} { + if {$col == 0 && ![is_busy]} { toggle_mode $path } } @@ -584,6 +717,15 @@ menu .mbar.commit .mbar.commit add command -label Rescan \ -command do_rescan \ -font $mainfont +.mbar.commit add command -label {Check-in All Files} \ + -command do_checkin_all \ + -font $mainfont +.mbar.commit add command -label {Sign Off} \ + -command do_signoff \ + -font $mainfont +.mbar.commit add command -label Commit \ + -command do_commit \ + -font $mainfont # -- Fetch Menu menu .mbar.fetch @@ -633,10 +775,13 @@ pack .vpane.files.other.sb -side right -fill y pack $ui_other -side left -fill both -expand 1 .vpane.files add .vpane.files.other -sticky nsew +$ui_index tag conf in_diff -font [concat $mainfont bold] +$ui_other tag conf in_diff -font [concat $mainfont bold] + # -- Diff Header set ui_fname_value {} set ui_fstatus_value {} -frame .vpane.diff -height 100 -width 100 +frame .vpane.diff -height 50 -width 400 frame .vpane.diff.header label .vpane.diff.header.l1 -text {File:} -font $mainfont label .vpane.diff.header.l2 -textvariable ui_fname_value \ @@ -645,7 +790,7 @@ label .vpane.diff.header.l2 -textvariable ui_fname_value \ -font $mainfont label .vpane.diff.header.l3 -text {Status:} -font $mainfont label .vpane.diff.header.l4 -textvariable ui_fstatus_value \ - -width 20 \ + -width $max_status_desc \ -anchor w \ -justify left \ -font $mainfont @@ -658,7 +803,7 @@ pack .vpane.diff.header.l3 -side right frame .vpane.diff.body set ui_diff .vpane.diff.body.t text $ui_diff -background white -borderwidth 0 \ - -width 40 -height 20 \ + -width 80 -height 15 \ -font $difffont \ -xscrollcommand {.vpane.diff.body.sbx set} \ -yscrollcommand {.vpane.diff.body.sby set} \ @@ -693,19 +838,27 @@ label .vpane.commarea.buttons.l -text {} \ -justify left \ -font $mainfont pack .vpane.commarea.buttons.l -side top -fill x +pack .vpane.commarea.buttons -side left -fill y + button .vpane.commarea.buttons.rescan -text {Rescan} \ -command do_rescan \ -font $mainfont pack .vpane.commarea.buttons.rescan -side top -fill x + button .vpane.commarea.buttons.ciall -text {Check-in All} \ -command do_checkin_all \ -font $mainfont pack .vpane.commarea.buttons.ciall -side top -fill x + +button .vpane.commarea.buttons.signoff -text {Sign Off} \ + -command do_signoff \ + -font $mainfont +pack .vpane.commarea.buttons.signoff -side top -fill x + button .vpane.commarea.buttons.commit -text {Commit} \ -command do_commit \ -font $mainfont pack .vpane.commarea.buttons.commit -side top -fill x -pack .vpane.commarea.buttons -side left -fill y # -- Commit Message Buffer frame .vpane.commarea.buffer @@ -741,6 +894,11 @@ bind . do_quit bind . do_rescan bind . do_rescan bind . do_rescan +bind . do_signoff +bind . do_signoff +bind . do_checkin_all +bind . do_checkin_all +bind . do_commit bind . do_quit bind . do_quit foreach i [list $ui_index $ui_other] { From 6f6eed286f6a056fab1f887871ba5287f609e1f1 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 6 Nov 2006 18:22:19 -0500 Subject: [PATCH 003/548] git-gui: Fixed UI layout problems on Windows. Signed-off-by: Shawn O. Pearce --- git-gui | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/git-gui b/git-gui index d74509a20b..3ee32e105c 100755 --- a/git-gui +++ b/git-gui @@ -229,7 +229,7 @@ proc show_diff {path} { return } - fconfigure $fd -blocking 0 -translation binary + fconfigure $fd -blocking 0 -translation auto fileevent $fd readable [list read_diff $fd] } @@ -237,6 +237,10 @@ proc read_diff {fd} { global ui_diff ui_status_value diff_3way diff_active while {[gets $fd line] >= 0} { + if {[string match {diff --git *} $line]} continue + if {[string match {diff --combined *} $line]} continue + if {[string match {--- *} $line]} continue + if {[string match {+++ *} $line]} continue if {[string match index* $line]} { if {[string first , $line] >= 0} { set diff_3way 1 @@ -584,8 +588,9 @@ proc show_msg {w top msg} { ## ui commands proc do_gitk {} { - global tcl_platform + global tcl_platform ui_status_value + set ui_status_value "Please wait... Starting gitk..." if {$tcl_platform(platform) == "windows"} { exec sh -c gitk & } else { @@ -705,7 +710,7 @@ menu .mbar -tearoff 0 # -- Project Menu menu .mbar.project -.mbar.project add command -label Visulize \ +.mbar.project add command -label Visualize \ -command do_gitk \ -font $mainfont .mbar.project add command -label Quit \ @@ -736,7 +741,7 @@ menu .mbar.pull # -- Main Window Layout panedwindow .vpane -orient vertical panedwindow .vpane.files -orient horizontal -.vpane add .vpane.files -sticky nsew +.vpane add .vpane.files -sticky nsew -height 100 -width 400 pack .vpane -anchor n -side top -fill both -expand 1 # -- Index File List @@ -781,7 +786,7 @@ $ui_other tag conf in_diff -font [concat $mainfont bold] # -- Diff Header set ui_fname_value {} set ui_fstatus_value {} -frame .vpane.diff -height 50 -width 400 +frame .vpane.diff -height 200 -width 400 frame .vpane.diff.header label .vpane.diff.header.l1 -text {File:} -font $mainfont label .vpane.diff.header.l2 -textvariable ui_fname_value \ @@ -803,7 +808,7 @@ pack .vpane.diff.header.l3 -side right frame .vpane.diff.body set ui_diff .vpane.diff.body.t text $ui_diff -background white -borderwidth 0 \ - -width 80 -height 15 \ + -width 80 -height 15 -wrap none \ -font $difffont \ -xscrollcommand {.vpane.diff.body.sbx set} \ -yscrollcommand {.vpane.diff.body.sby set} \ @@ -828,7 +833,7 @@ $ui_diff tag conf dni -foreground "#a000a0" $ui_diff tag conf bold -font [concat $difffont bold] # -- Commit Area -frame .vpane.commarea -height 50 +frame .vpane.commarea -height 150 .vpane add .vpane.commarea -stick nsew # -- Commit Area Buttons From e210e67451f22f97c1476d6b78b6fa7fdd5817f9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 6 Nov 2006 19:12:58 -0500 Subject: [PATCH 004/548] git-gui: Corrected keyboard bindings on Windows, improved state management. When we are refreshing from the index or updating the index we shouldn't let the user cause other index based operations to occur as these would likely conflict with the currently running operations possibly causing some index changes to be lost. Signed-off-by: Shawn O. Pearce --- git-gui | 99 +++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/git-gui b/git-gui index 3ee32e105c..168062e67b 100755 --- a/git-gui +++ b/git-gui @@ -7,34 +7,53 @@ exec wish "$0" -- "$@" # and distributed under the terms of the GNU General Public Licence, # either version 2, or (at your option) any later version. - ###################################################################### ## -## status +## task management set status_active 0 set diff_active 0 set checkin_active 0 set update_index_fd {} -proc is_busy {} { - global status_active diff_active checkin_active update_index_fd +set disable_on_lock [list] +set index_lock_type none - if {$status_active > 0 - || $diff_active - || $checkin_active - || $update_index_fd != {}} { +proc lock_index {type} { + global index_lock_type disable_on_lock + + if {$index_lock_type == {none}} { + set index_lock_type $type + foreach w $disable_on_lock { + uplevel #0 $w disabled + } + return 1 + } elseif {$index_lock_type == {begin-update} && $type == {update}} { + set index_lock_type $type return 1 } return 0 } +proc unlock_index {} { + global index_lock_type disable_on_lock + + set index_lock_type none + foreach w $disable_on_lock { + uplevel #0 $w normal + } +} + +###################################################################### +## +## status + proc update_status {} { global gitdir HEAD commit_type global ui_index ui_other ui_status_value ui_comm global status_active file_states - if {[is_busy]} return + if {$status_active || ![lock_index read]} return array unset file_states foreach w [list $ui_index $ui_other] { @@ -160,6 +179,7 @@ proc status_eof {fd buf} { set $buf {} close $fd if {[incr status_active -1] == 0} { + unlock_index set ui_status_value {Ready.} if {$ui_fname_value != {}} { show_diff $ui_fname_value @@ -186,7 +206,7 @@ proc show_diff {path} { global file_states HEAD diff_3way diff_active global ui_diff ui_fname_value ui_fstatus_value ui_status_value - if {[is_busy]} return + if {$diff_active || ![lock_index read]} return clear_diff set s $file_states($path) @@ -211,6 +231,7 @@ proc show_diff {path} { close $fd } err ]} { set diff_active 0 + unlock_index set ui_status_value "Unable to display $path" error_popup "Error loading file:\n$err" return @@ -224,6 +245,7 @@ proc show_diff {path} { if {[catch {set fd [open $cmd r]} err]} { set diff_active 0 + unlock_index set ui_status_value "Unable to display $path" error_popup "Error loading diff:\n$err" return @@ -279,6 +301,7 @@ proc read_diff {fd} { if {[eof $fd]} { close $fd set diff_active 0 + unlock_index set ui_status_value {Ready.} } } @@ -417,6 +440,7 @@ proc with_update_index {body} { global update_index_fd if {$update_index_fd == {}} { + if {![lock_index update]} return set update_index_fd [open \ "| git update-index --add --remove -z --stdin" \ w] @@ -424,6 +448,7 @@ proc with_update_index {body} { uplevel 1 $body close $update_index_fd set update_index_fd {} + unlock_index } else { uplevel 1 $body } @@ -587,10 +612,17 @@ proc show_msg {w top msg} { ## ## ui commands +set starting_gitk_msg {Please wait... Starting gitk...} proc do_gitk {} { - global tcl_platform ui_status_value + global tcl_platform ui_status_value starting_gitk_msg + + set ui_status_value $starting_gitk_msg + after 5000 { + if {$ui_status_value == $starting_gitk_msg} { + set ui_status_value {Ready.} + } + } - set ui_status_value "Please wait... Starting gitk..." if {$tcl_platform(platform) == "windows"} { exec sh -c gitk & } else { @@ -623,7 +655,7 @@ proc do_rescan {} { proc do_checkin_all {} { global checkin_active ui_status_value - if {[is_busy]} return + if {$checkin_active || ![lock_index begin-update]} return set checkin_active 1 set ui_status_value {Checking in all files...} @@ -687,7 +719,7 @@ proc unclick {w x y} { set path [$w get $lno.1 $lno.end] if {$path == {}} return - if {$col == 0 && ![is_busy]} { + if {$col == 0} { toggle_mode $path } } @@ -700,6 +732,11 @@ set mainfont {Helvetica 10} set difffont {Courier 10} set maincursor [. cget -cursor] +switch -- $tcl_platform(platform) { +windows {set M1B Control; set M1T Ctrl} +default {set M1B M1; set M1T M1} +} + # -- Menu Bar menu .mbar -tearoff 0 .mbar add cascade -label Project -menu .mbar.project @@ -715,22 +752,33 @@ menu .mbar.project -font $mainfont .mbar.project add command -label Quit \ -command do_quit \ + -accelerator $M1T-Q \ -font $mainfont # -- Commit Menu menu .mbar.commit .mbar.commit add command -label Rescan \ -command do_rescan \ + -accelerator F5 \ -font $mainfont +lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Check-in All Files} \ -command do_checkin_all \ + -accelerator $M1T-U \ -font $mainfont +lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Sign Off} \ -command do_signoff \ + -accelerator $M1T-S \ -font $mainfont .mbar.commit add command -label Commit \ -command do_commit \ + -accelerator $M1T-Return \ -font $mainfont +lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] # -- Fetch Menu menu .mbar.fetch @@ -849,11 +897,13 @@ button .vpane.commarea.buttons.rescan -text {Rescan} \ -command do_rescan \ -font $mainfont pack .vpane.commarea.buttons.rescan -side top -fill x +lappend disable_on_lock {.vpane.commarea.buttons.rescan conf -state} button .vpane.commarea.buttons.ciall -text {Check-in All} \ -command do_checkin_all \ -font $mainfont pack .vpane.commarea.buttons.ciall -side top -fill x +lappend disable_on_lock {.vpane.commarea.buttons.ciall conf -state} button .vpane.commarea.buttons.signoff -text {Sign Off} \ -command do_signoff \ @@ -864,6 +914,7 @@ button .vpane.commarea.buttons.commit -text {Commit} \ -command do_commit \ -font $mainfont pack .vpane.commarea.buttons.commit -side top -fill x +lappend disable_on_lock {.vpane.commarea.buttons.commit conf -state} # -- Commit Message Buffer frame .vpane.commarea.buffer @@ -897,21 +948,21 @@ pack .status -anchor w -side bottom -fill x # -- Key Bindings bind . do_quit bind . do_rescan -bind . do_rescan -bind . do_rescan -bind . do_signoff -bind . do_signoff -bind . do_checkin_all -bind . do_checkin_all -bind . do_commit -bind . do_quit -bind . do_quit +bind . <$M1B-Key-r> do_rescan +bind . <$M1B-Key-R> do_rescan +bind . <$M1B-Key-s> do_signoff +bind . <$M1B-Key-S> do_signoff +bind . <$M1B-Key-u> do_checkin_all +bind . <$M1B-Key-U> do_checkin_all +bind . <$M1B-Key-Return> do_commit +bind . <$M1B-Key-q> do_quit +bind . <$M1B-Key-Q> do_quit foreach i [list $ui_index $ui_other] { bind $i {click %W %x %y 1 %X %Y; break} bind $i {click %W %x %y 3 %X %Y; break} bind $i {unclick %W %x %y; break} } -unset i +unset i M1B M1T ###################################################################### ## From 6e27d826c807153a4773f197e5056d66a6a809c0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 6 Nov 2006 20:03:36 -0500 Subject: [PATCH 005/548] git-gui: Verify we should actually perform a commit when asked to do so. A user shouldn't perform a commit if any of the following are true: * The repository state has changed since the last rescan. * There are no files updated in the index to commit. * There are unmerged stages still in the index. * The commit message has not been provided. * The pre-commit hook is executable and declined. Signed-off-by: Shawn O. Pearce --- git-gui | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 2 deletions(-) diff --git a/git-gui b/git-gui index 168062e67b..0b941c3ffb 100755 --- a/git-gui +++ b/git-gui @@ -599,15 +599,62 @@ proc error_popup {msg} { } proc show_msg {w top msg} { - message $w.m -text $msg -justify center -aspect 400 + global gitdir + + message $w.m -text $msg -justify left -aspect 400 pack $w.m -side top -fill x -padx 20 -pady 20 button $w.ok -text OK -command "destroy $top" - pack $w.ok -side bottom -fill x + pack $w.ok -side bottom bind $top "grab $top; focus $top" bind $top "destroy $top" + wm title $top "error: git-ui ([file normalize [file dirname $gitdir]])" tkwait window $top } +proc hook_failed_popup {hook msg} { + global gitdir mainfont difffont + + set w .hookfail + toplevel $w + wm transient $w . + + frame $w.m + label $w.m.l1 -text "$hook hook failed:" \ + -anchor w \ + -justify left \ + -font [concat $mainfont bold] + text $w.m.t \ + -background white -borderwidth 1 \ + -relief sunken \ + -width 80 -height 10 \ + -font $difffont \ + -yscrollcommand [list $w.m.sby set] + label $w.m.l2 \ + -text {You must correct the above errors before committing.} \ + -anchor w \ + -justify left \ + -font [concat $mainfont bold] + 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 + + bind $w "grab $w; focus $w" + bind $w "destroy $w" + wm title $w "error: git-ui ([file normalize [file dirname $gitdir]])" + tkwait window $w +} + ###################################################################### ## ## ui commands @@ -693,6 +740,94 @@ proc do_signoff {} { } } +proc do_commit {} { + global tcl_platform HEAD gitdir commit_type file_states + global ui_comm + + # -- Our in memory state should match the repository. + # + if {[catch {set curHEAD [exec git rev-parse --verify HEAD]}]} { + set cur_type initial + } else { + set cur_type normal + } + if {$commit_type != $commit_type || $HEAD != $curHEAD} { + error_popup {Last scanned state does not match repository state. + +Its highly likely that another Git program modified the +repository since our last scan. A rescan is required +before committing. +} + update_status + return + } + + # -- At least one file should differ in the index. + # + set files_ready 0 + foreach path [array names file_states] { + set s $file_states($path) + switch -glob -- [lindex $s 0] { + _* {continue} + A* - + D* - + M* {set files_ready 1; break} + U* { + error_popup "Unmerged files cannot be committed. + +File $path has merge conflicts. +You must resolve them and check the file in before committing. +" + return + } + default { + error_popup "Unknown file state [lindex $s 0] detected. + +File $path cannot be committed by this program. +" + } + } + } + if {!$files_ready} { + error_popup {No checked-in files to commit. + +You must check-in at least 1 file before you can commit. +} + return + } + + # -- A message is required. + # + set msg [string trim [$ui_comm get 1.0 end]] + if {$msg == {}} { + 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. +} + return + } + + # -- Ask the pre-commit hook for the go-ahead. + # + set pchook [file join $gitdir hooks pre-commit] + if {$tcl_platform(platform) == {windows} && [file exists $pchook]} { + set pchook [list sh -c \ + "if test -x \"$pchook\"; then exec \"$pchook\"; fi"] + } elseif {[file executable $pchook]} { + set pchook [list $pchook] + } else { + set pchook {} + } + if {$pchook != {} && [catch {eval exec $pchook} err]} { + hook_failed_popup pre-commit $err + return + } +} + # shift == 1: left click # 3: right click proc click {w x y shift wx wy} { From ec6b424abb8d0a5c6399bcffdfde19aa47f16d18 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 6 Nov 2006 20:50:59 -0500 Subject: [PATCH 006/548] git-gui: Finished commit implementation. We can now commit any type of commit (initial, normal or merge) using the same techniques as git-commit.sh does for these types of things. If invoked as git-citool we run exit immediately after the commit was finished. If invoked as git-gui then we stay running. Also fixed a bug which caused the commit message buffer to be lost when the application shutdown and restarted. Signed-off-by: Shawn O. Pearce --- git-citool | 1 + git-gui | 339 +++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 238 insertions(+), 102 deletions(-) create mode 120000 git-citool diff --git a/git-citool b/git-citool new file mode 120000 index 0000000000..b5f620fd09 --- /dev/null +++ b/git-citool @@ -0,0 +1 @@ +git-gui \ No newline at end of file diff --git a/git-gui b/git-gui index 0b941c3ffb..0e9e519636 100755 --- a/git-gui +++ b/git-gui @@ -11,9 +11,11 @@ exec wish "$0" -- "$@" ## ## task management +set single_commit 0 set status_active 0 set diff_active 0 set checkin_active 0 +set commit_active 0 set update_index_fd {} set disable_on_lock [list] @@ -48,13 +50,27 @@ proc unlock_index {} { ## ## status +proc repository_state {hdvar ctvar} { + global gitdir + upvar $hdvar hd $ctvar ct + + if {[catch {set hd [exec git rev-parse --verify HEAD]}]} { + set ct initial + } elseif {[file exists [file join $gitdir MERGE_HEAD]]} { + set ct merge + } else { + set ct normal + } +} + proc update_status {} { - global gitdir HEAD commit_type + global HEAD commit_type global ui_index ui_other ui_status_value ui_comm global status_active file_states if {$status_active || ![lock_index read]} return + repository_state HEAD commit_type array unset file_states foreach w [list $ui_index $ui_other] { $w conf -state normal @@ -62,12 +78,6 @@ proc update_status {} { $w conf -state disabled } - if {[catch {set HEAD [exec git rev-parse --verify HEAD]}]} { - set commit_type initial - } else { - set commit_type normal - } - if {![$ui_comm edit modified] || [string trim [$ui_comm get 0.0 end]] == {}} { if {[load_message GITGUI_MSG]} { @@ -306,6 +316,207 @@ proc read_diff {fd} { } } +###################################################################### +## +## commit + +proc commit_tree {} { + global tcl_platform HEAD gitdir commit_type file_states + global commit_active ui_status_value + global ui_comm + + if {$commit_active || ![lock_index update]} return + + # -- Our in memory state should match the repository. + # + repository_state curHEAD cur_type + if {$commit_type != $cur_type || $HEAD != $curHEAD} { + error_popup {Last scanned state does not match repository state. + +Its highly likely that another Git program modified the +repository since our last scan. A rescan is required +before committing. +} + unlock_index + update_status + return + } + + # -- At least one file should differ in the index. + # + set files_ready 0 + foreach path [array names file_states] { + set s $file_states($path) + switch -glob -- [lindex $s 0] { + _* {continue} + A* - + D* - + M* {set files_ready 1; break} + U* { + error_popup "Unmerged files cannot be committed. + +File $path has merge conflicts. +You must resolve them and check the file in before committing. +" + unlock_index + return + } + default { + error_popup "Unknown file state [lindex $s 0] detected. + +File $path cannot be committed by this program. +" + } + } + } + if {!$files_ready} { + error_popup {No checked-in files to commit. + +You must check-in 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]] + if {$msg == {}} { + 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 + } + + # -- Ask the pre-commit hook for the go-ahead. + # + set pchook [file join $gitdir hooks pre-commit] + if {$tcl_platform(platform) == {windows} && [file exists $pchook]} { + set pchook [list sh -c \ + "if test -x \"$pchook\"; then exec \"$pchook\"; fi"] + } elseif {[file executable $pchook]} { + set pchook [list $pchook] + } else { + set pchook {} + } + if {$pchook != {} && [catch {eval exec $pchook} err]} { + hook_failed_popup pre-commit $err + unlock_index + return + } + + # -- Write the tree in the background. + # + set commit_active 1 + set ui_status_value {Committing changes...} + + set fd_wt [open "| git write-tree" r] + fileevent $fd_wt readable \ + [list commit_stage2 $fd_wt $curHEAD $msg] +} + +proc commit_stage2 {fd_wt curHEAD msg} { + global single_commit gitdir HEAD commit_type + global commit_active ui_status_value comm_ui + + gets $fd_wt tree_id + close $fd_wt + + if {$tree_id == {}} { + error_popup "write-tree failed" + set commit_active 0 + set ui_status_value {Commit failed.} + unlock_index + return + } + + # -- Create the commit. + # + set cmd [list git commit-tree $tree_id] + if {$commit_type != {initial}} { + lappend cmd -p $HEAD + } + if {$commit_type == {merge}} { + if {[catch { + set fd_mh [open [file join $gitdir MERGE_HEAD] r] + while {[gets $fd_mh merge_head] > 0} { + lappend -p $merge_head + } + close $fd_mh + } err]} { + error_popup "Loading MERGE_HEADs failed:\n$err" + set commit_active 0 + set ui_status_value {Commit failed.} + unlock_index + return + } + } + if {$commit_type == {initial}} { + # git commit-tree writes to stderr during initial commit. + lappend cmd 2>/dev/null + } + lappend cmd << $msg + if {[catch {set cmt_id [eval exec $cmd]} err]} { + error_popup "commit-tree failed:\n$err" + set commit_active 0 + set ui_status_value {Commit failed.} + unlock_index + return + } + + # -- Update the HEAD ref. + # + set reflogm commit + if {$commit_type != {normal}} { + append reflogm " ($commit_type)" + } + set i [string first "\n" $msg] + if {$i >= 0} { + append reflogm {: } [string range $msg 0 [expr $i - 1]] + } else { + append reflogm {: } $msg + } + set cmd [list git update-ref \ + -m $reflogm \ + HEAD $cmt_id $curHEAD] + if {[catch {eval exec $cmd} err]} { + error_popup "update-ref failed:\n$err" + set commit_active 0 + set ui_status_value {Commit failed.} + unlock_index + return + } + + # -- Cleanup after ourselves. + # + catch {file delete [file join $gitdir MERGE_HEAD]} + catch {file delete [file join $gitdir MERGE_MSG]} + catch {file delete [file join $gitdir SQUASH_MSG]} + catch {file delete [file join $gitdir GITGUI_MSG]} + + # -- Let rerere do its thing. + # + if {[file isdirectory [file join $gitdir rr-cache]]} { + catch {exec git rerere} + } + + $comm_ui delete 0.0 end + $comm_ui edit modified false + + if {$single_commit} do_quit + + set commit_active 0 + set ui_status_value "Changes committed as $cmt_id." + unlock_index + update_status +} + ###################################################################### ## ## ui helpers @@ -599,20 +810,22 @@ proc error_popup {msg} { } proc show_msg {w top msg} { - global gitdir + global gitdir appname message $w.m -text $msg -justify left -aspect 400 - pack $w.m -side top -fill x -padx 20 -pady 20 - button $w.ok -text OK -command "destroy $top" + pack $w.m -side top -fill x -padx 5 -pady 10 + button $w.ok -text OK \ + -width 15 \ + -command "destroy $top" pack $w.ok -side bottom bind $top "grab $top; focus $top" bind $top "destroy $top" - wm title $top "error: git-ui ([file normalize [file dirname $gitdir]])" + wm title $top "error: $appname ([file normalize [file dirname $gitdir]])" tkwait window $top } proc hook_failed_popup {hook msg} { - global gitdir mainfont difffont + global gitdir mainfont difffont appname set w .hookfail toplevel $w @@ -651,7 +864,7 @@ proc hook_failed_popup {hook msg} { bind $w "grab $w; focus $w" bind $w "destroy $w" - wm title $w "error: git-ui ([file normalize [file dirname $gitdir]])" + wm title $w "error: $appname ([file normalize [file dirname $gitdir]])" tkwait window $w } @@ -681,14 +894,14 @@ proc do_quit {} { global gitdir ui_comm set save [file join $gitdir GITGUI_MSG] - if {[$ui_comm edit modified] - && [string trim [$ui_comm get 0.0 end]] != {}} { + set msg [string trim [$ui_comm get 0.0 end]] + if {[$ui_comm edit modified] && $msg != {}} { catch { set fd [open $save w] puts $fd [string trim [$ui_comm get 0.0 end]] close $fd } - } elseif {[file exists $save]} { + } elseif {$msg == {} && [file exists $save]} { file delete $save } @@ -741,91 +954,7 @@ proc do_signoff {} { } proc do_commit {} { - global tcl_platform HEAD gitdir commit_type file_states - global ui_comm - - # -- Our in memory state should match the repository. - # - if {[catch {set curHEAD [exec git rev-parse --verify HEAD]}]} { - set cur_type initial - } else { - set cur_type normal - } - if {$commit_type != $commit_type || $HEAD != $curHEAD} { - error_popup {Last scanned state does not match repository state. - -Its highly likely that another Git program modified the -repository since our last scan. A rescan is required -before committing. -} - update_status - return - } - - # -- At least one file should differ in the index. - # - set files_ready 0 - foreach path [array names file_states] { - set s $file_states($path) - switch -glob -- [lindex $s 0] { - _* {continue} - A* - - D* - - M* {set files_ready 1; break} - U* { - error_popup "Unmerged files cannot be committed. - -File $path has merge conflicts. -You must resolve them and check the file in before committing. -" - return - } - default { - error_popup "Unknown file state [lindex $s 0] detected. - -File $path cannot be committed by this program. -" - } - } - } - if {!$files_ready} { - error_popup {No checked-in files to commit. - -You must check-in at least 1 file before you can commit. -} - return - } - - # -- A message is required. - # - set msg [string trim [$ui_comm get 1.0 end]] - if {$msg == {}} { - 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. -} - return - } - - # -- Ask the pre-commit hook for the go-ahead. - # - set pchook [file join $gitdir hooks pre-commit] - if {$tcl_platform(platform) == {windows} && [file exists $pchook]} { - set pchook [list sh -c \ - "if test -x \"$pchook\"; then exec \"$pchook\"; fi"] - } elseif {[file executable $pchook]} { - set pchook [list $pchook] - } else { - set pchook {} - } - if {$pchook != {} && [catch {eval exec $pchook} err]} { - hook_failed_popup pre-commit $err - return - } + commit_tree } # shift == 1: left click @@ -1081,6 +1210,7 @@ label .status -textvariable ui_status_value \ pack .status -anchor w -side bottom -fill x # -- Key Bindings +bind $ui_comm <$M1B-Key-Return> {do_commit;break} bind . do_quit bind . do_rescan bind . <$M1B-Key-r> do_rescan @@ -1108,6 +1238,11 @@ if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} { exit 1 } -wm title . "git-ui ([file normalize [file dirname $gitdir]])" +set appname [lindex [file split $argv0] end] +if {$appname == {git-citool}} { + set single_commit 1 +} + +wm title . "$appname ([file normalize [file dirname $gitdir]])" focus -force $ui_comm update_status From e57ca85e113437649ec908ce48fc43a659d650f8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 6 Nov 2006 21:34:10 -0500 Subject: [PATCH 007/548] git-gui: Implemented amended commits. Also fixed a bug related that caused a crash if the file currently in the diff viewer is no longer modified after the commit. Signed-off-by: Shawn O. Pearce --- git-gui | 160 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 119 insertions(+), 41 deletions(-) diff --git a/git-gui b/git-gui index 0e9e519636..bb89da4332 100755 --- a/git-gui +++ b/git-gui @@ -21,6 +21,10 @@ set update_index_fd {} set disable_on_lock [list] set index_lock_type none +set HEAD {} +set PARENT {} +set commit_type {} + proc lock_index {type} { global index_lock_type disable_on_lock @@ -63,14 +67,23 @@ proc repository_state {hdvar ctvar} { } } -proc update_status {} { - global HEAD commit_type +proc update_status {{final Ready.}} { + global HEAD PARENT commit_type global ui_index ui_other ui_status_value ui_comm global status_active file_states if {$status_active || ![lock_index read]} return - repository_state HEAD commit_type + repository_state new_HEAD new_type + if {$commit_type == {amend} + && $new_type == {normal} + && $new_HEAD == $HEAD} { + } else { + set HEAD $new_HEAD + set PARENT $new_HEAD + set commit_type $new_type + } + array unset file_states foreach w [list $ui_index $ui_other] { $w conf -state normal @@ -91,11 +104,11 @@ proc update_status {} { set ui_status_value {Refreshing file status...} set fd_rf [open "| git update-index -q --unmerged --refresh" r] fconfigure $fd_rf -blocking 0 -translation binary - fileevent $fd_rf readable [list read_refresh $fd_rf] + fileevent $fd_rf readable [list read_refresh $fd_rf $final] } -proc read_refresh {fd} { - global gitdir HEAD commit_type +proc read_refresh {fd final} { + global gitdir PARENT commit_type global ui_index ui_other ui_status_value ui_comm global status_active file_states @@ -112,27 +125,27 @@ proc read_refresh {fd} { set status_active 3 set ui_status_value {Scanning for modified files ...} - set fd_di [open "| git diff-index --cached -z $HEAD" r] + set fd_di [open "| git diff-index --cached -z $PARENT" r] set fd_df [open "| git diff-files -z" r] set fd_lo [open $ls_others r] fconfigure $fd_di -blocking 0 -translation binary fconfigure $fd_df -blocking 0 -translation binary fconfigure $fd_lo -blocking 0 -translation binary - fileevent $fd_di readable [list read_diff_index $fd_di] - fileevent $fd_df readable [list read_diff_files $fd_df] - fileevent $fd_lo readable [list read_ls_others $fd_lo] + fileevent $fd_di readable [list read_diff_index $fd_di $final] + fileevent $fd_df readable [list read_diff_files $fd_df $final] + fileevent $fd_lo readable [list read_ls_others $fd_lo $final] } proc load_message {file} { global gitdir ui_comm set f [file join $gitdir $file] - if {[file exists $f]} { + if {[file isfile $f]} { if {[catch {set fd [open $f r]}]} { return 0 } - set content [read $fd] + set content [string trim [read $fd]] close $fd $ui_comm delete 0.0 end $ui_comm insert end $content @@ -141,7 +154,7 @@ proc load_message {file} { return 0 } -proc read_diff_index {fd} { +proc read_diff_index {fd final} { global buf_rdi append buf_rdi [read $fd] @@ -152,10 +165,10 @@ proc read_diff_index {fd} { display_file $p [string index $m end]_ } } - status_eof $fd buf_rdi + status_eof $fd buf_rdi $final } -proc read_diff_files {fd} { +proc read_diff_files {fd final} { global buf_rdf append buf_rdf [read $fd] @@ -166,10 +179,10 @@ proc read_diff_files {fd} { display_file $p _[string index $m end] } } - status_eof $fd buf_rdf + status_eof $fd buf_rdf $final } -proc read_ls_others {fd} { +proc read_ls_others {fd final} { global buf_rlo append buf_rlo [read $fd] @@ -178,21 +191,25 @@ proc read_ls_others {fd} { foreach p [lrange $pck 0 end-1] { display_file $p _O } - status_eof $fd buf_rlo + status_eof $fd buf_rlo $final } -proc status_eof {fd buf} { +proc status_eof {fd buf final} { global status_active $buf - global ui_fname_value ui_status_value + global ui_fname_value ui_status_value file_states if {[eof $fd]} { set $buf {} close $fd if {[incr status_active -1] == 0} { unlock_index - set ui_status_value {Ready.} - if {$ui_fname_value != {}} { + + set ui_status_value $final + if {$ui_fname_value != {} && [array names file_states \ + -exact $ui_fname_value] != {}} { show_diff $ui_fname_value + } else { + clear_diff } } } @@ -213,7 +230,7 @@ proc clear_diff {} { } proc show_diff {path} { - global file_states HEAD diff_3way diff_active + global file_states PARENT diff_3way diff_active global ui_diff ui_fname_value ui_fstatus_value ui_status_value if {$diff_active || ![lock_index read]} return @@ -227,12 +244,12 @@ proc show_diff {path} { set ui_fstatus_value [mapdesc $m $path] set ui_status_value "Loading diff of $path..." - set cmd [list | git diff-index -p $HEAD -- $path] + set cmd [list | git diff-index -p $PARENT -- $path] switch $m { AM { } MM { - set cmd [list | git diff-index -p -c $HEAD $path] + set cmd [list | git diff-index -p -c $PARENT $path] } _O { if {[catch { @@ -320,6 +337,51 @@ proc read_diff {fd} { ## ## commit +proc load_last_commit {} { + global HEAD PARENT commit_type ui_comm + + if {$commit_type == {amend}} return + if {$commit_type != {normal}} { + error_popup "Can't amend a $commit_type commit." + return + } + + set msg {} + set parent {} + set parent_count 0 + if {[catch { + set fd [open "| git cat-file commit $HEAD" r] + while {[gets $fd line] > 0} { + if {[string match {parent *} $line]} { + set parent [string range $line 7 end] + incr parent_count + } + } + set msg [string trim [read $fd]] + close $fd + } err]} { + error_popup "Error loading commit data for amend:\n$err" + return + } + + if {$parent_count == 0} { + set commit_type amend + set HEAD {} + set PARENT {} + update_status + } elseif {$parent_count == 1} { + set commit_type amend + set PARENT $parent + $ui_comm delete 0.0 end + $ui_comm insert end $msg + $ui_comm edit modified false + update_status + } else { + error_popup {You can't amend a merge commit.} + return + } +} + proc commit_tree {} { global tcl_platform HEAD gitdir commit_type file_states global commit_active ui_status_value @@ -330,7 +392,10 @@ proc commit_tree {} { # -- Our in memory state should match the repository. # repository_state curHEAD cur_type - if {$commit_type != $cur_type || $HEAD != $curHEAD} { + if {$commit_type == {amend} + && $cur_type == {normal} + && $curHEAD == $HEAD} { + } elseif {$commit_type != $cur_type || $HEAD != $curHEAD} { error_popup {Last scanned state does not match repository state. Its highly likely that another Git program modified the @@ -397,7 +462,7 @@ A good commit message has the following format: # -- Ask the pre-commit hook for the go-ahead. # set pchook [file join $gitdir hooks pre-commit] - if {$tcl_platform(platform) == {windows} && [file exists $pchook]} { + if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} { set pchook [list sh -c \ "if test -x \"$pchook\"; then exec \"$pchook\"; fi"] } elseif {[file executable $pchook]} { @@ -422,8 +487,8 @@ A good commit message has the following format: } proc commit_stage2 {fd_wt curHEAD msg} { - global single_commit gitdir HEAD commit_type - global commit_active ui_status_value comm_ui + global single_commit gitdir PARENT commit_type + global commit_active ui_status_value ui_comm gets $fd_wt tree_id close $fd_wt @@ -439,8 +504,8 @@ proc commit_stage2 {fd_wt curHEAD msg} { # -- Create the commit. # set cmd [list git commit-tree $tree_id] - if {$commit_type != {initial}} { - lappend cmd -p $HEAD + if {$PARENT != {}} { + lappend cmd -p $PARENT } if {$commit_type == {merge}} { if {[catch { @@ -457,7 +522,7 @@ proc commit_stage2 {fd_wt curHEAD msg} { return } } - if {$commit_type == {initial}} { + if {$PARENT == {}} { # git commit-tree writes to stderr during initial commit. lappend cmd 2>/dev/null } @@ -482,9 +547,7 @@ proc commit_stage2 {fd_wt curHEAD msg} { } else { append reflogm {: } $msg } - set cmd [list git update-ref \ - -m $reflogm \ - HEAD $cmt_id $curHEAD] + set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD] if {[catch {eval exec $cmd} err]} { error_popup "update-ref failed:\n$err" set commit_active 0 @@ -506,15 +569,15 @@ proc commit_stage2 {fd_wt curHEAD msg} { catch {exec git rerere} } - $comm_ui delete 0.0 end - $comm_ui edit modified false + $ui_comm delete 0.0 end + $ui_comm edit modified false if {$single_commit} do_quit + set commit_type {} set commit_active 0 - set ui_status_value "Changes committed as $cmt_id." unlock_index - update_status + update_status "Changes committed as $cmt_id." } ###################################################################### @@ -877,7 +940,7 @@ proc do_gitk {} { global tcl_platform ui_status_value starting_gitk_msg set ui_status_value $starting_gitk_msg - after 5000 { + after 10000 { if {$ui_status_value == $starting_gitk_msg} { set ui_status_value {Ready.} } @@ -953,6 +1016,10 @@ proc do_signoff {} { } } +proc do_amend_last {} { + load_last_commit +} + proc do_commit {} { commit_tree } @@ -1027,6 +1094,11 @@ menu .mbar.commit -font $mainfont lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] +.mbar.commit add command -label {Amend Last Commit} \ + -command do_amend_last \ + -font $mainfont +lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Check-in All Files} \ -command do_checkin_all \ -accelerator $M1T-U \ @@ -1145,7 +1217,7 @@ $ui_diff tag conf dni -foreground "#a000a0" $ui_diff tag conf bold -font [concat $difffont bold] # -- Commit Area -frame .vpane.commarea -height 150 +frame .vpane.commarea -height 170 .vpane add .vpane.commarea -stick nsew # -- Commit Area Buttons @@ -1163,6 +1235,12 @@ button .vpane.commarea.buttons.rescan -text {Rescan} \ pack .vpane.commarea.buttons.rescan -side top -fill x lappend disable_on_lock {.vpane.commarea.buttons.rescan conf -state} +button .vpane.commarea.buttons.amend -text {Amend Last} \ + -command do_amend_last \ + -font $mainfont +pack .vpane.commarea.buttons.amend -side top -fill x +lappend disable_on_lock {.vpane.commarea.buttons.amend conf -state} + button .vpane.commarea.buttons.ciall -text {Check-in All} \ -command do_checkin_all \ -font $mainfont From bd1e2b4028e183f7b4d564aad69c3eae8bdb661a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 6 Nov 2006 22:03:05 -0500 Subject: [PATCH 008/548] git-gui: Misc. nit type of bug fixes. * Make sure we are in the top level working directory. This way we can access files using their repository path. * Reload the diff viewer if the current file's status has changed; as the diff may now be different. * Correctly handle the 'AD' file state: added but now gone from the working directory. Signed-off-by: Shawn O. Pearce --- git-gui | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/git-gui b/git-gui index bb89da4332..8ad3f2d033 100755 --- a/git-gui +++ b/git-gui @@ -266,6 +266,9 @@ proc show_diff {path} { $ui_diff conf -state normal $ui_diff insert end $content $ui_diff conf -state disabled + set diff_active 0 + unlock_index + set ui_status_value {Ready.} return } } @@ -482,8 +485,7 @@ A good commit message has the following format: set ui_status_value {Committing changes...} set fd_wt [open "| git write-tree" r] - fileevent $fd_wt readable \ - [list commit_stage2 $fd_wt $curHEAD $msg] + fileevent $fd_wt readable [list commit_stage2 $fd_wt $curHEAD $msg] } proc commit_stage2 {fd_wt curHEAD msg} { @@ -510,8 +512,8 @@ proc commit_stage2 {fd_wt curHEAD msg} { if {$commit_type == {merge}} { if {[catch { set fd_mh [open [file join $gitdir MERGE_HEAD] r] - while {[gets $fd_mh merge_head] > 0} { - lappend -p $merge_head + while {[gets $fd_mh merge_head] >= 0} { + lappend cmd -p $merge_head } close $fd_mh } err]} { @@ -576,6 +578,8 @@ proc commit_stage2 {fd_wt curHEAD msg} { set commit_type {} set commit_active 0 + set HEAD $cmt_id + set PARENT $cmt_id unlock_index update_status "Changes committed as $cmt_id." } @@ -739,7 +743,7 @@ proc update_index {path} { } proc toggle_mode {path} { - global file_states + global file_states ui_fname_value set s $file_states($path) set m [lindex $s 0] @@ -749,12 +753,16 @@ proc toggle_mode {path} { _O {set new A*} _M - MM {set new M*} + AD - _D {set new D*} default {return} } with_update_index {update_index $path} display_file $path $new + if {$ui_fname_value == $path} { + show_diff $path + } } ###################################################################### @@ -843,6 +851,7 @@ foreach i { {_O o plain "Untracked"} {A_ o fulltick "Added"} {AM o parttick "Partially added"} + {AD o question "Added (but now gone)"} {_D i question "Missing"} {D_ i removed "Removed"} @@ -1261,10 +1270,18 @@ lappend disable_on_lock {.vpane.commarea.buttons.commit conf -state} # -- Commit Message Buffer frame .vpane.commarea.buffer set ui_comm .vpane.commarea.buffer.t -label .vpane.commarea.buffer.l -text {Commit Message:} \ +set ui_coml .vpane.commarea.buffer.l +label $ui_coml -text {Commit Message:} \ -anchor w \ -justify left \ -font $mainfont +trace add variable commit_type write {uplevel #0 { + switch -glob $commit_type \ + initial {$ui_coml conf -text {Initial Commit Message:}} \ + amend {$ui_coml conf -text {Amended Commit Message:}} \ + merge {$ui_coml conf -text {Merge Commit Message:}} \ + * {$ui_coml conf -text {Commit Message:}} +}} text $ui_comm -background white -borderwidth 1 \ -relief sunken \ -width 75 -height 10 -wrap none \ @@ -1272,7 +1289,7 @@ text $ui_comm -background white -borderwidth 1 \ -yscrollcommand {.vpane.commarea.buffer.sby set} \ -cursor $maincursor scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview] -pack .vpane.commarea.buffer.l -side top -fill x +pack $ui_coml -side top -fill x pack .vpane.commarea.buffer.sby -side right -fill y pack $ui_comm -side left -fill y pack .vpane.commarea.buffer -side left -fill y @@ -1315,6 +1332,11 @@ if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} { show_msg {} . "Cannot find the git directory: $err" exit 1 } +set cdup [exec git rev-parse --show-cdup] +if {$cdup != ""} { + cd $cdup +} +unset cdup set appname [lindex [file split $argv0] end] if {$appname == {git-citool}} { From 8c0ce436826f01e17cba90ebcaf6483ad3b8c984 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 6 Nov 2006 23:13:23 -0500 Subject: [PATCH 009/548] git-gui: Started construction of fetch and push operations. Signed-off-by: Shawn O. Pearce --- git-gui | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/git-gui b/git-gui index 8ad3f2d033..a43fb2f662 100755 --- a/git-gui +++ b/git-gui @@ -584,6 +584,34 @@ proc commit_stage2 {fd_wt curHEAD msg} { update_status "Changes committed as $cmt_id." } +###################################################################### +## +## fetch pull push + +proc fetch_from {remote} { + set w [new_console "fetch $remote" \ + "Fetching new changes from $remote"] + set cmd [list | git fetch] + lappend -v + lappend cmd $remote + lappend cmd |& cat + set fd_f [open $cmd r] + fconfigure $fd_f -blocking 0 -translation auto + fileevent $fd_f readable [list console_read $w $fd_f] +} + +proc push_to {remote} { + set w [new_console "push $remote" \ + "Pushing changes to $remote"] + set cmd [list | git push] + lappend -v + lappend cmd $remote + lappend cmd |& cat + set fd_f [open $cmd r] + fconfigure $fd_f -blocking 0 -translation auto + fileevent $fd_f readable [list console_read $w $fd_f] +} + ###################################################################### ## ## ui helpers @@ -765,6 +793,41 @@ proc toggle_mode {path} { } } +###################################################################### +## +## config (fetch push pull) + +proc load_all_remotes {} { + global gitdir all_remotes + + set all_remotes [list] + set rm_dir [file join $gitdir remotes] + if {[file isdirectory $rm_dir]} { + set all_remotes [concat $all_remotes \ + [glob -types f -tails -directory $rm_dir * *]] + } + + set fd_rc [open "| git repo-config --list" r] + while {[gets $fd_rc line] >= 0} { + if {[regexp ^remote\.(.*)\.url= $line line name]} { + lappend all_remotes $name + } + } + close $fd_rc + + set all_remotes [lsort -unique $all_remotes] +} + +proc populate_remote_menu {m pfx op} { + global gitdir all_remotes mainfont + + foreach remote $all_remotes { + $m add command -label "$pfx $remote..." \ + -command [list $op $remote] \ + -font $mainfont + } +} + ###################################################################### ## ## icons @@ -888,6 +951,7 @@ proc show_msg {w top msg} { pack $w.m -side top -fill x -padx 5 -pady 10 button $w.ok -text OK \ -width 15 \ + -font $mainfont \ -command "destroy $top" pack $w.ok -side bottom bind $top "grab $top; focus $top" @@ -931,6 +995,7 @@ proc hook_failed_popup {hook msg} { button $w.ok -text OK \ -width 15 \ + -font $mainfont \ -command "destroy $w" pack $w.ok -side bottom @@ -940,6 +1005,58 @@ proc hook_failed_popup {hook msg} { tkwait window $w } +set next_console_id 0 + +proc new_console {short_title long_title} { + global next_console_id gitdir appname mainfont difffont + + set w .console[incr next_console_id] + toplevel $w + frame $w.m + label $w.m.l1 -text "$long_title:" \ + -anchor w \ + -justify left \ + -font [concat $mainfont bold] + text $w.m.t \ + -background white -borderwidth 1 \ + -relief sunken \ + -width 80 -height 10 \ + -font $difffont \ + -state disabled \ + -yscrollcommand [list $w.m.sby set] + scrollbar $w.m.sby -command [list $w.m.t yview] + pack $w.m.l1 -side top -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 + + button $w.ok -text {OK} \ + -width 15 \ + -font $mainfont \ + -state disabled \ + -command "destroy $w" + pack $w.ok -side bottom + + bind $w "focus $w" + bind $w break + wm title $w "$appname ([file dirname [file normalize [file dirname $gitdir]]]): $short_title" + return $w +} + +proc console_read {w fd} { + $w.m.t conf -state normal + while {[gets $fd line] >= 0} { + $w.m.t insert end $line + $w.m.t insert end "\n" + } + $w.m.t conf -state disabled + + if {[eof $fd]} { + close $fd + $w.ok conf -state normal + } +} + ###################################################################### ## ## ui commands @@ -1083,6 +1200,7 @@ menu .mbar -tearoff 0 .mbar add cascade -label Commit -menu .mbar.commit .mbar add cascade -label Fetch -menu .mbar.fetch .mbar add cascade -label Pull -menu .mbar.pull +.mbar add cascade -label Push -menu .mbar.push . configure -menu .mbar # -- Project Menu @@ -1131,6 +1249,9 @@ menu .mbar.fetch # -- Pull Menu menu .mbar.pull +# -- Push Menu +menu .mbar.push + # -- Main Window Layout panedwindow .vpane -orient vertical panedwindow .vpane.files -orient horizontal @@ -1345,4 +1466,7 @@ if {$appname == {git-citool}} { wm title . "$appname ([file normalize [file dirname $gitdir]])" focus -force $ui_comm +load_all_remotes +populate_remote_menu .mbar.fetch From fetch_from +populate_remote_menu .mbar.push To push_to update_status From cc4b1c0213ad2d99121135125b8c2ea630bebe3d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 6 Nov 2006 23:47:05 -0500 Subject: [PATCH 010/548] git-gui: Worked around environment variable problems on Windows. Apparently the Cygwin tclsh/wish executables don't pass the environment that they inherited onto any children that they invoke. This causes a problem for some users during 'git fetch' or 'git push' as critical environment variables like GIT_SSH and SSH_AUTH_SOCK aren't available to the git processes. So we work around this by forcing sh to start a login shell, thus reloading the user's environment, then cd to the current directory, and finally start the requested process. Of course this won't correctly handle any transient environment variables that were inherited but were not supplied by the user's login shell. Signed-off-by: Shawn O. Pearce --- git-gui | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/git-gui b/git-gui index a43fb2f662..be361dec17 100755 --- a/git-gui +++ b/git-gui @@ -92,7 +92,7 @@ proc update_status {{final Ready.}} { } if {![$ui_comm edit modified] - || [string trim [$ui_comm get 0.0 end]] == {}} { + || [string trim [$ui_comm get 0.0 end]] == {}} { if {[load_message GITGUI_MSG]} { } elseif {[load_message MERGE_MSG]} { } elseif {[load_message SQUASH_MSG]} { @@ -591,25 +591,19 @@ proc commit_stage2 {fd_wt curHEAD msg} { proc fetch_from {remote} { set w [new_console "fetch $remote" \ "Fetching new changes from $remote"] - set cmd [list | git fetch] - lappend -v + set cmd [list git fetch] + lappend cmd -v lappend cmd $remote - lappend cmd |& cat - set fd_f [open $cmd r] - fconfigure $fd_f -blocking 0 -translation auto - fileevent $fd_f readable [list console_read $w $fd_f] + console_exec $w $cmd } proc push_to {remote} { set w [new_console "push $remote" \ "Pushing changes to $remote"] - set cmd [list | git push] + set cmd [list git push] lappend -v lappend cmd $remote - lappend cmd |& cat - set fd_f [open $cmd r] - fconfigure $fd_f -blocking 0 -translation auto - fileevent $fd_f readable [list console_read $w $fd_f] + console_exec $w $cmd } ###################################################################### @@ -1043,6 +1037,26 @@ proc new_console {short_title long_title} { return $w } +proc console_exec {w cmd} { + global tcl_platform + + # -- Windows tosses the enviroment when we exec our child. + # But most users need that so we have to relogin. :-( + # + if {$tcl_platform(platform) == {windows}} { + 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 auto + fileevent $fd_f readable [list console_read $w $fd_f] +} + proc console_read {w fd} { $w.m.t conf -state normal while {[gets $fd line] >= 0} { @@ -1050,6 +1064,7 @@ proc console_read {w fd} { $w.m.t insert end "\n" } $w.m.t conf -state disabled + $w.m.t see end if {[eof $fd]} { close $fd @@ -1062,6 +1077,7 @@ proc console_read {w fd} { ## ui commands set starting_gitk_msg {Please wait... Starting gitk...} + proc do_gitk {} { global tcl_platform ui_status_value starting_gitk_msg @@ -1072,7 +1088,7 @@ proc do_gitk {} { } } - if {$tcl_platform(platform) == "windows"} { + if {$tcl_platform(platform) == {windows}} { exec sh -c gitk & } else { exec gitk & From b8ce6f0ec8a275bf9e32ff7666d00a88154fd50b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 00:20:56 -0500 Subject: [PATCH 011/548] git-gui: Reorganized startup procedure to ensure gitdir is right. Because we cd after getting the cdup value from Git we can't try to get the gitdir until after we perform the cd, as usually the gitdir is relative to the current working directory. Signed-off-by: Shawn O. Pearce --- git-gui | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/git-gui b/git-gui index be361dec17..87dcbaef4c 100755 --- a/git-gui +++ b/git-gui @@ -939,7 +939,7 @@ proc error_popup {msg} { } proc show_msg {w top msg} { - global gitdir appname + global gitdir appname mainfont message $w.m -text $msg -justify left -aspect 400 pack $w.m -side top -fill x -padx 5 -pady 10 @@ -1465,17 +1465,23 @@ unset i M1B M1T ## ## main -if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} { +set appname [lindex [file split $argv0] end] +set gitdir {} + +if {[catch {set cdup [exec git rev-parse --show-cdup]} err]} { show_msg {} . "Cannot find the git directory: $err" exit 1 } -set cdup [exec git rev-parse --show-cdup] if {$cdup != ""} { cd $cdup } unset cdup -set appname [lindex [file split $argv0] end] +if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} { + show_msg {} . "Cannot find the git directory: $err" + exit 1 +} + if {$appname == {git-citool}} { set single_commit 1 } From 661448922fd55b907449962d35d3fdb92397ce9d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 00:53:39 -0500 Subject: [PATCH 012/548] git-gui: Fix menu item accelerator display on Mac OS X. Apparently accelerators really only work correctly for function keys (F1-F12) and "Cmd-q". Apparently wish on Mac OS X reports itself as unix and the OS is Darwin, this makes it a little difficult to be sure we are running under Aqua. Signed-off-by: Shawn O. Pearce --- git-gui | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/git-gui b/git-gui index 87dcbaef4c..0bbb0064f0 100755 --- a/git-gui +++ b/git-gui @@ -1205,9 +1205,10 @@ set mainfont {Helvetica 10} set difffont {Courier 10} set maincursor [. cget -cursor] -switch -- $tcl_platform(platform) { -windows {set M1B Control; set M1T Ctrl} -default {set M1B M1; set M1T M1} +switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" { +windows,* {set M1B Control; set M1T Ctrl} +unix,Darwin {set M1B M1; set M1T Cmd} +default {set M1B M1; set M1T M1} } # -- Menu Bar From ee3dc9354d23b1262fb9c71c648435e0ccf01d7e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 02:18:18 -0500 Subject: [PATCH 013/548] git-gui: Correctly handle CR vs. LF within the console of fetch. Because the remote end is likely to send us progress meters by resetting each line with a CR (and no LF) we should display those meters by replacing the last line of text with the next line, just like a normal xterm would do. This makes the output of fetch look about the same as if we ran it from within an xterm. Signed-off-by: Shawn O. Pearce --- git-gui | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/git-gui b/git-gui index 0bbb0064f0..2645cd5d1a 100755 --- a/git-gui +++ b/git-gui @@ -1002,9 +1002,11 @@ proc hook_failed_popup {hook msg} { set next_console_id 0 proc new_console {short_title long_title} { - global next_console_id gitdir appname mainfont difffont + global next_console_id console_cr + global gitdir appname mainfont difffont set w .console[incr next_console_id] + set console_cr($w) 1.0 toplevel $w frame $w.m label $w.m.l1 -text "$long_title:" \ @@ -1024,7 +1026,7 @@ proc new_console {short_title long_title} { pack $w.m.t -side left -fill both -expand 1 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 - button $w.ok -text {OK} \ + button $w.ok -text {Running...} \ -width 15 \ -font $mainfont \ -state disabled \ @@ -1053,22 +1055,44 @@ proc console_exec {w cmd} { set cmd [concat | $cmd |& cat] set fd_f [open $cmd r] - fconfigure $fd_f -blocking 0 -translation auto + fconfigure $fd_f -blocking 0 -translation binary fileevent $fd_f readable [list console_read $w $fd_f] } proc console_read {w fd} { + global console_cr + $w.m.t conf -state normal - while {[gets $fd line] >= 0} { - $w.m.t insert end $line - $w.m.t insert end "\n" + set buf [read $fd] + 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 if {[eof $fd]} { close $fd + $w.ok conf -text Close $w.ok conf -state normal + array unset console_cr $w } } From 07123f4002efdb881f6ce08c5c4411e17e34e141 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 02:57:46 -0500 Subject: [PATCH 014/548] git-gui: Check for fetch or push command failure and denote it. Signed-off-by: Shawn O. Pearce --- git-gui | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/git-gui b/git-gui index 2645cd5d1a..0cd85e3c92 100755 --- a/git-gui +++ b/git-gui @@ -1020,8 +1020,12 @@ proc new_console {short_title long_title} { -font $difffont \ -state disabled \ -yscrollcommand [list $w.m.sby set] + label $w.m.s -anchor w \ + -justify left \ + -font [concat $mainfont bold] 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 @@ -1088,12 +1092,19 @@ proc console_read {w fd} { $w.m.t conf -state disabled $w.m.t see end + fconfigure $fd -blocking 1 if {[eof $fd]} { - close $fd + if {[catch {close $fd}]} { + $w.m.s conf -background red -text {Error: Command Failed} + } else { + $w.m.s conf -background green -text {Success} + } $w.ok conf -text Close $w.ok conf -state normal array unset console_cr $w + return } + fconfigure $fd -blocking 0 } ###################################################################### @@ -1468,17 +1479,19 @@ pack .status -anchor w -side bottom -fill x # -- Key Bindings bind $ui_comm <$M1B-Key-Return> {do_commit;break} -bind . do_quit -bind . do_rescan -bind . <$M1B-Key-r> do_rescan -bind . <$M1B-Key-R> do_rescan -bind . <$M1B-Key-s> do_signoff -bind . <$M1B-Key-S> do_signoff -bind . <$M1B-Key-u> do_checkin_all -bind . <$M1B-Key-U> do_checkin_all -bind . <$M1B-Key-Return> do_commit -bind . <$M1B-Key-q> do_quit -bind . <$M1B-Key-Q> do_quit +bind . do_quit +bind all do_rescan +bind all <$M1B-Key-r> do_rescan +bind all <$M1B-Key-R> do_rescan +bind . <$M1B-Key-s> do_signoff +bind . <$M1B-Key-S> do_signoff +bind . <$M1B-Key-u> do_checkin_all +bind . <$M1B-Key-U> do_checkin_all +bind . <$M1B-Key-Return> do_commit +bind all <$M1B-Key-q> do_quit +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]} foreach i [list $ui_index $ui_other] { bind $i {click %W %x %y 1 %X %Y; break} bind $i {click %W %x %y 3 %X %Y; break} From d47ae541cef50f5fbfaec195597cf1cebc2840f5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 03:00:20 -0500 Subject: [PATCH 015/548] git-gui: Don't complain if no .git/remotes exist. The user might be using the new style config syntax remote.name.url rather than the older standalone remote file. Signed-off-by: Shawn O. Pearce --- git-gui | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/git-gui b/git-gui index 0cd85e3c92..b8e7c89586 100755 --- a/git-gui +++ b/git-gui @@ -797,8 +797,11 @@ proc load_all_remotes {} { set all_remotes [list] set rm_dir [file join $gitdir remotes] if {[file isdirectory $rm_dir]} { - set all_remotes [concat $all_remotes \ - [glob -types f -tails -directory $rm_dir * *]] + set all_remotes [concat $all_remotes [glob \ + -types f \ + -tails \ + -nocomplain \ + -directory $rm_dir *]] } set fd_rc [open "| git repo-config --list" r] From 393ec6f01ce38eb731d12557c8f596c36044cf02 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 03:18:34 -0500 Subject: [PATCH 016/548] git-gui: Added current TODO list. Signed-off-by: Shawn O. Pearce --- TODO | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 0000000000..b05495847b --- /dev/null +++ b/TODO @@ -0,0 +1,32 @@ +Items outstanding: + + * Checkout $PARENT version to working directory, overwriting current + version. ($PARENT is HEAD, except when amending). + + * Update index with $PARENT version but leave working directory + alone. + + * Populate the pull menu with local branches. + + * Populate the pull menu with default merge branch from each remote. + + * Make use of the new default merge data stored in repo-config. + + * Indicate what the current branch is. + + * Checkout or create a different local branch. + + * Delete a local branch. + + * Store user preferences (like font, window layout) in global + repo-config. + + * Better organize fetch/push/pull console windows. + + * Clone UI (to download a new repository). + + * Remotes editor (for .git/config format only). + + * Show a shortlog of the last couple of commits in the main window, + to give the user warm fuzzy feelings that we have their data + saved. From 3d9d029bde946d7a230ce85460c53686e10bdfe0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 03:30:26 -0500 Subject: [PATCH 017/548] git-gui: Last minute idea about fetch shortcuts. Signed-off-by: Shawn O. Pearce --- TODO | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TODO b/TODO index b05495847b..b425da30e2 100644 --- a/TODO +++ b/TODO @@ -21,6 +21,12 @@ Items outstanding: * Store user preferences (like font, window layout) in global repo-config. + * Allow user to define keyboard shortcuts for frequently used fetch + or merge operations. Or maybe just define a keyboard shortcut + for default fetch/default merge of current branch is enough; + but I do know a few users who merge a couple of common branches + also into the same branch so one default isn't quite enough. + * Better organize fetch/push/pull console windows. * Clone UI (to download a new repository). From 37af79d10d980418eaeca953c4ba1c08a85d37e9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 04:19:49 -0500 Subject: [PATCH 018/548] git-gui: Automatically reopen any console closed by the user. If the user closes a console and we get more ouptut for it then we will get a Tcl error in the readable event handle for the file channel. Since this loses the actual output and is quite unfriendly to the end user instead reopen any console which the user closed prior to the additional output arriving. Signed-off-by: Shawn O. Pearce --- git-gui | 72 +++++++++++++++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/git-gui b/git-gui index b8e7c89586..adc89474a7 100755 --- a/git-gui +++ b/git-gui @@ -1005,14 +1005,20 @@ proc hook_failed_popup {hook msg} { set next_console_id 0 proc new_console {short_title long_title} { - global next_console_id console_cr + 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 global gitdir appname mainfont difffont - set w .console[incr next_console_id] set console_cr($w) 1.0 toplevel $w frame $w.m - label $w.m.l1 -text "$long_title:" \ + label $w.m.l1 -text "[lindex $console_data($w) 1]:" \ -anchor w \ -justify left \ -font [concat $mainfont bold] @@ -1041,8 +1047,7 @@ proc new_console {short_title long_title} { pack $w.ok -side bottom bind $w "focus $w" - bind $w break - wm title $w "$appname ([file dirname [file normalize [file dirname $gitdir]]]): $short_title" + wm title $w "$appname ([file dirname [file normalize [file dirname $gitdir]]]): [lindex $console_data($w) 0]" return $w } @@ -1067,44 +1072,51 @@ proc console_exec {w cmd} { } proc console_read {w fd} { - global console_cr + global console_cr console_data - $w.m.t conf -state normal set buf [read $fd] - 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 {$buf != {}} { + 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 + 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 } - $w.m.t conf -state disabled - $w.m.t see end fconfigure $fd -blocking 1 if {[eof $fd]} { if {[catch {close $fd}]} { + if {![winfo exists $w]} {console_init $w} $w.m.s conf -background red -text {Error: Command Failed} - } else { + $w.ok conf -text Close + $w.ok conf -state normal + } elseif {[winfo exists $w]} { $w.m.s conf -background green -text {Success} + $w.ok conf -text Close + $w.ok conf -state normal } - $w.ok conf -text Close - $w.ok conf -state normal array unset console_cr $w + array unset console_data $w return } fconfigure $fd -blocking 0 From 0d4f3eb5f330a53300e630c1ffe4ea1fa61f5dd9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 04:26:02 -0500 Subject: [PATCH 019/548] git-gui: Cache all repo-config data in an array. We're likely going to need key/value pairs from the repo-config beyond just remote.*.url, so cache them all up front into a Tcl array where we have fast access to them without needing to refork a repo-config --list process. Signed-off-by: Shawn O. Pearce --- git-gui | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/git-gui b/git-gui index adc89474a7..de67aa2a68 100755 --- a/git-gui +++ b/git-gui @@ -791,8 +791,23 @@ proc toggle_mode {path} { ## ## config (fetch push pull) +proc load_repo_config {} { + global repo_config + + array unset repo_config + catch { + set fd_rc [open "| git repo-config --list" r] + while {[gets $fd_rc line] >= 0} { + if {[regexp {^([^=]+)=(.*)$} $line line name value]} { + lappend repo_config($name) $value + } + } + close $fd_rc + } +} + proc load_all_remotes {} { - global gitdir all_remotes + global gitdir all_remotes repo_config set all_remotes [list] set rm_dir [file join $gitdir remotes] @@ -804,13 +819,11 @@ proc load_all_remotes {} { -directory $rm_dir *]] } - set fd_rc [open "| git repo-config --list" r] - while {[gets $fd_rc line] >= 0} { - if {[regexp ^remote\.(.*)\.url= $line line name]} { + foreach line [array names repo_config remote.*.url] { + if {[regexp ^remote\.(.*)\.url\$ $line line name]} { lappend all_remotes $name } } - close $fd_rc set all_remotes [lsort -unique $all_remotes] } @@ -1541,6 +1554,7 @@ if {$appname == {git-citool}} { wm title . "$appname ([file normalize [file dirname $gitdir]])" focus -force $ui_comm +load_repo_config load_all_remotes populate_remote_menu .mbar.fetch From fetch_from populate_remote_menu .mbar.push To push_to From d33ba5fa809be1714c7f31f128a7a72461a96d96 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 05:02:15 -0500 Subject: [PATCH 020/548] git-gui: Added support for pulling from default branch of a remote. We now create one menu entry per remote listing the first Pull: or fetch entry associated with that remote as the branch to pull into the current branch. This is actually quite incorrect as we should be using the default remote branch name listed in branch..merge for a new-style remote described in the config file. But its a good default to get started with. Signed-off-by: Shawn O. Pearce --- git-gui | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/git-gui b/git-gui index de67aa2a68..4aa035a6c4 100755 --- a/git-gui +++ b/git-gui @@ -592,16 +592,31 @@ proc fetch_from {remote} { set w [new_console "fetch $remote" \ "Fetching new changes from $remote"] set cmd [list git fetch] - lappend cmd -v lappend cmd $remote console_exec $w $cmd } +proc pull_remote {remote branch} { + set w [new_console "pull $remote $branch" \ + "Pulling new changes from branch $branch in $remote"] + set cmd [list git pull] + lappend cmd $remote + lappend cmd $branch + console_exec $w $cmd [list post_pull_remote $remote $branch] +} + +proc post_pull_remote {remote branch success} { + if {$success} { + update_status "Successfully pulled $branch from $remote." + } else { + update_status "Conflicts detected while pulling $branch from $remote." + } +} + proc push_to {remote} { set w [new_console "push $remote" \ "Pushing changes to $remote"] set cmd [list git push] - lappend -v lappend cmd $remote console_exec $w $cmd } @@ -829,7 +844,7 @@ proc load_all_remotes {} { } proc populate_remote_menu {m pfx op} { - global gitdir all_remotes mainfont + global all_remotes mainfont foreach remote $all_remotes { $m add command -label "$pfx $remote..." \ @@ -838,6 +853,40 @@ proc populate_remote_menu {m pfx op} { } } +proc populate_pull_menu {m} { + global gitdir repo_config all_remotes mainfont + + foreach remote $all_remotes { + set rb {} + if {[array get repo_config remote.$remote.url] != {}} { + if {[array get repo_config remote.$remote.fetch] != {}} { + regexp {^([^:]+):} \ + [lindex $repo_config(remote.$remote.fetch) 0] \ + line rb + } + } else { + catch { + set fd [open [file join $gitdir remotes $remote] r] + while {[gets $fd line] >= 0} { + if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} { + break + } + } + close $fd + } + } + + set rb_short $rb + regsub ^refs/heads/ $rb {} rb_short + if {$rb_short != {}} { + $m add command \ + -label "Branch $rb_short from $remote..." \ + -command [list pull_remote $remote $rb] \ + -font $mainfont + } + } +} + ###################################################################### ## ## icons @@ -966,7 +1015,9 @@ proc show_msg {w top msg} { pack $w.ok -side bottom bind $top "grab $top; focus $top" bind $top "destroy $top" - wm title $top "error: $appname ([file normalize [file dirname $gitdir]])" + wm title $w "$appname ([lindex [file split \ + [file normalize [file dirname $gitdir]]] \ + end]): error" tkwait window $top } @@ -1011,7 +1062,9 @@ proc hook_failed_popup {hook msg} { bind $w "grab $w; focus $w" bind $w "destroy $w" - wm title $w "error: $appname ([file normalize [file dirname $gitdir]])" + wm title $w "$appname ([lindex [file split \ + [file normalize [file dirname $gitdir]]] \ + end]): error" tkwait window $w } @@ -1060,11 +1113,13 @@ proc console_init {w} { pack $w.ok -side bottom bind $w "focus $w" - wm title $w "$appname ([file dirname [file normalize [file dirname $gitdir]]]): [lindex $console_data($w) 0]" + wm title $w "$appname ([lindex [file split \ + [file normalize [file dirname $gitdir]]] \ + end]): [lindex $console_data($w) 0]" return $w } -proc console_exec {w cmd} { +proc console_exec {w cmd {after {}}} { global tcl_platform # -- Windows tosses the enviroment when we exec our child. @@ -1081,10 +1136,10 @@ proc console_exec {w cmd} { set fd_f [open $cmd r] fconfigure $fd_f -blocking 0 -translation binary - fileevent $fd_f readable [list console_read $w $fd_f] + fileevent $fd_f readable [list console_read $w $fd_f $after] } -proc console_read {w fd} { +proc console_read {w fd after} { global console_cr console_data set buf [read $fd] @@ -1123,13 +1178,18 @@ proc console_read {w fd} { $w.m.s conf -background red -text {Error: Command Failed} $w.ok conf -text Close $w.ok conf -state normal + set ok 0 } elseif {[winfo exists $w]} { $w.m.s conf -background green -text {Success} $w.ok conf -text Close $w.ok conf -state normal + set ok 1 } array unset console_cr $w array unset console_data $w + if {$after != {}} { + uplevel #0 $after $ok + } return } fconfigure $fd -blocking 0 @@ -1558,4 +1618,5 @@ load_repo_config load_all_remotes populate_remote_menu .mbar.fetch From fetch_from populate_remote_menu .mbar.push To push_to +populate_pull_menu .mbar.pull update_status From c5437df168379f33c7148c60b232b5c2887dd537 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 05:06:40 -0500 Subject: [PATCH 021/548] git-gui: Updated TODO list now that pull is starting to work. Signed-off-by: Shawn O. Pearce --- TODO | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/TODO b/TODO index b425da30e2..136e87849c 100644 --- a/TODO +++ b/TODO @@ -6,9 +6,11 @@ Items outstanding: * Update index with $PARENT version but leave working directory alone. - * Populate the pull menu with local branches. + * Add all new files in one shot (e.g. "git add ."). - * Populate the pull menu with default merge branch from each remote. + * Add file to .gitignore or info/excludes. + + * Populate the pull menu with local branches. * Make use of the new default merge data stored in repo-config. From 868c8752451c0b75502a0134068f0cfe055eb7ad Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 18:34:09 -0500 Subject: [PATCH 022/548] git-gui: Corrected diff-index/diff-files protocol parsing errors. When we were receiving a lot of output from diff-index we split records at the wrong locations and started using the file status information (mode and SHA1s) as path names, and then proceeded to try to use part of the path names as status data. This caused all sorts of havoc. So I rewrote the parsing implementation to scan for the pair of null bytes along the buffer and stop scanning (waiting for more data) if both can't be found during this event. This seems to work well under high load (like when processing 6,983 added files). Signed-off-by: Shawn O. Pearce --- git-gui | 59 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/git-gui b/git-gui index 4aa035a6c4..48e1c5601f 100755 --- a/git-gui +++ b/git-gui @@ -111,6 +111,7 @@ proc read_refresh {fd final} { global gitdir PARENT commit_type global ui_index ui_other ui_status_value ui_comm global status_active file_states + global buf_rdi buf_rdf buf_rlo read $fd if {![eof $fd]} return @@ -123,6 +124,10 @@ proc read_refresh {fd final} { lappend ls_others "--exclude-from=$info_exclude" } + set buf_rdi {} + set buf_rdf {} + set buf_rlo {} + set status_active 3 set ui_status_value {Scanning for modified files ...} set fd_di [open "| git diff-index --cached -z $PARENT" r] @@ -158,13 +163,28 @@ proc read_diff_index {fd final} { global buf_rdi append buf_rdi [read $fd] - set pck [split $buf_rdi "\0"] - set buf_rdi [lindex $pck end] - foreach {m p} [lrange $pck 0 end-1] { - if {$m != {} && $p != {}} { - display_file $p [string index $m end]_ - } + set c 0 + set n [string length $buf_rdi] + while {$c < $n} { + set z1 [string first "\0" $buf_rdi $c] + if {$z1 == -1} break + incr z1 + set z2 [string first "\0" $buf_rdi $z1] + if {$z2 == -1} break + + set c $z2 + incr z2 -1 + display_file \ + [string range $buf_rdi $z1 $z2] \ + [string index $buf_rdi [expr $z1 - 2]]_ + incr c } + if {$c < $n} { + set buf_rdi [string range $buf_rdi $c end] + } else { + set buf_rdi {} + } + status_eof $fd buf_rdi $final } @@ -172,13 +192,28 @@ proc read_diff_files {fd final} { global buf_rdf append buf_rdf [read $fd] - set pck [split $buf_rdf "\0"] - set buf_rdf [lindex $pck end] - foreach {m p} [lrange $pck 0 end-1] { - if {$m != {} && $p != {}} { - display_file $p _[string index $m end] - } + set c 0 + set n [string length $buf_rdf] + while {$c < $n} { + set z1 [string first "\0" $buf_rdf $c] + if {$z1 == -1} break + incr z1 + set z2 [string first "\0" $buf_rdf $z1] + if {$z2 == -1} break + + set c $z2 + incr z2 -1 + display_file \ + [string range $buf_rdf $z1 $z2] \ + _[string index $buf_rdf [expr $z1 - 2]] + incr c } + if {$c < $n} { + set buf_rdf [string range $buf_rdf $c end] + } else { + set buf_rdf {} + } + status_eof $fd buf_rdf $final } From 93f654df7ec9a509e9362e06821c2af0b2bacc82 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 19:30:54 -0500 Subject: [PATCH 023/548] git-gui: Performance improvements for large file sets. Loading 6900 newly added files required about 90 seconds on one system. This is just far too long to perform a "status" type of operation. git-status on the same system completes in just 8.2 seconds if it is redirected to /dev/null. Most of our performance improvement comes from moving all of the UI updating out of the main fileevent handlers for the status process. Instead we are only updating the file_states array and then only doing the UI update when all states are known and have been finally determined. The rescan execution is now down to almost 30 seconds for the same case, a good (but not really all that impressive) improvement. Signed-off-by: Shawn O. Pearce --- git-gui | 113 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 70 insertions(+), 43 deletions(-) diff --git a/git-gui b/git-gui index 48e1c5601f..ed745ee52e 100755 --- a/git-gui +++ b/git-gui @@ -236,10 +236,13 @@ proc status_eof {fd buf final} { if {[eof $fd]} { set $buf {} close $fd + if {[incr status_active -1] == 0} { unlock_index set ui_status_value $final + display_all_files + if {$ui_fname_value != {} && [array names file_states \ -exact $ui_fname_value] != {}} { show_diff $ui_fname_value @@ -711,79 +714,103 @@ proc bsearch {w path} { return -[expr $lo + 1] } +set next_icon_id 0 + proc merge_state {path state} { - global file_states + global file_states next_icon_id if {[array names file_states -exact $path] == {}} { - set o __ - set s [list $o none none] + set m __ + set s [list $m icon[incr next_icon_id]] } else { set s $file_states($path) - set o [lindex $s 0] + set m [lindex $s 0] } - set m [lindex $s 0] - if {[string index $state 0] == "_"} { + if {[string index $state 0] == {_}} { set state [string index $m 0][string index $state 1] - } elseif {[string index $state 0] == "*"} { + } elseif {[string index $state 0] == {*}} { set state _[string index $state 1] } - if {[string index $state 1] == "_"} { + if {[string index $state 1] == {_}} { set state [string index $state 0][string index $m 1] - } elseif {[string index $state 1] == "*"} { + } elseif {[string index $state 1] == {*}} { set state [string index $state 0]_ } set file_states($path) [lreplace $s 0 0 $state] - return $o + return $m } proc display_file {path state} { - global ui_index ui_other file_states + global ui_index ui_other file_states status_active set old_m [merge_state $path $state] - set s $file_states($path) - set m [lindex $s 0] + if {$status_active} return - if {[mapcol $m $path] == "o"} { - set ii 1 - set ai 2 - set iw $ui_index - set aw $ui_other + set s $file_states($path) + set new_m [lindex $s 0] + set new_col [mapcol $new_m $path] + set new_ico [mapicon $new_m $path] + + if {$new_col == {o}} { + set old_w $ui_index + set new_w $ui_other } else { - set ii 2 - set ai 1 - set iw $ui_other - set aw $ui_index + set old_w $ui_other + set new_w $ui_index } - set d [lindex $s $ii] - if {$d != "none"} { - set lno [bsearch $iw $path] + if {$new_col != [mapcol $old_m $path]} { + set lno [bsearch $old_w $path] if {$lno >= 0} { incr lno - $iw conf -state normal - $iw delete $lno.0 [expr $lno + 1].0 - $iw conf -state disabled - set s [lreplace $s $ii $ii none] + $old_w conf -state normal + $old_w delete $lno.0 [expr $lno + 1].0 + $old_w conf -state disabled } + + set lno [expr abs([bsearch $new_w $path] + 1) + 1] + $new_w conf -state normal + $new_w image create $lno.0 \ + -align center -padx 5 -pady 1 \ + -name [lindex $s 1] \ + -image [mapicon $m $path] + $new_w insert $lno.1 "$path\n" + $new_w conf -state disabled + } elseif {$new_icon != [mapicon $old_m $path]} { + $new_w conf -state normal + $new_w image conf [lindex $s 1] -image $new_icon + $new_w conf -state disabled + } +} + +proc display_all_files {} { + global ui_index ui_other file_states + + $ui_index conf -state normal + $ui_other conf -state normal + + foreach path [lsort [array names file_states]] { + set s $file_states($path) + set m [lindex $s 0] + + if {[mapcol $m $path] == {o}} { + set aw $ui_other + } else { + set aw $ui_index + } + + $aw image create end \ + -align center -padx 5 -pady 1 \ + -name [lindex $s 1] \ + -image [mapicon $m $path] + $aw insert end "$path\n" } - set d [lindex $s $ai] - if {$d == "none"} { - set lno [expr abs([bsearch $aw $path] + 1) + 1] - $aw conf -state normal - set ico [$aw image create $lno.0 \ - -align center -padx 5 -pady 1 \ - -image [mapicon $m $path]] - $aw insert $lno.1 "$path\n" - $aw conf -state disabled - set file_states($path) [lreplace $s $ai $ai [list $ico]] - } elseif {[mapicon $m $path] != [mapicon $old_m $path]} { - set ico [lindex $d 0] - $aw image conf $ico -image [mapicon $m $path] - } + $ui_index conf -state disabled + $ui_other conf -state disabled } proc with_update_index {body} { From 6b29267542546b443fd885b2ed6dc896680b09ed Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 19:58:37 -0500 Subject: [PATCH 024/548] git-gui: More performance improvements to rescan logic. Removed as much as possible from the merge_state proc, which is where we spent most of our time before UI update. This change makes our running time match that of git status, except that we then need about 7 additional seconds to draw 6900 files on screen. Apparently the [array names a -exact $v] operator in Tcl is O(n) rather than O(1), which is really quite disappointing given that each array can only have one entry for a given value. Switching to a lookup with a catch (whose error we ignore) runs in O(1) time and bought us most of that improvement. Signed-off-by: Shawn O. Pearce --- git-gui | 87 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/git-gui b/git-gui index ed745ee52e..1833fa86ce 100755 --- a/git-gui +++ b/git-gui @@ -70,7 +70,7 @@ proc repository_state {hdvar ctvar} { proc update_status {{final Ready.}} { global HEAD PARENT commit_type global ui_index ui_other ui_status_value ui_comm - global status_active file_states + global status_active file_states status_start if {$status_active || ![lock_index read]} return @@ -84,6 +84,7 @@ proc update_status {{final Ready.}} { set commit_type $new_type } + set status_start [clock seconds] array unset file_states foreach w [list $ui_index $ui_other] { $w conf -state normal @@ -230,7 +231,7 @@ proc read_ls_others {fd final} { } proc status_eof {fd buf final} { - global status_active $buf + global status_active status_start $buf global ui_fname_value ui_status_value file_states if {[eof $fd]} { @@ -240,8 +241,12 @@ proc status_eof {fd buf final} { if {[incr status_active -1] == 0} { unlock_index - set ui_status_value $final + set e1 [clock seconds] display_all_files + set e2 [clock seconds] +puts "TIME [expr $e1 - $status_start] + [expr $e2 - $e1] = [expr $e2 - $status_start]" + + set ui_status_value $final if {$ui_fname_value != {} && [array names file_states \ -exact $ui_fname_value] != {}} { @@ -664,11 +669,11 @@ proc push_to {remote} { ## ui helpers proc mapcol {state path} { - global all_cols + global all_cols ui_other if {[catch {set r $all_cols($state)}]} { puts "error: no column for state={$state} $path" - return o + return $ui_other } return $r } @@ -716,31 +721,34 @@ proc bsearch {w path} { set next_icon_id 0 -proc merge_state {path state} { +proc merge_state {path new_state} { global file_states next_icon_id - if {[array names file_states -exact $path] == {}} { - set m __ - set s [list $m icon[incr next_icon_id]] + set s0 [string index $new_state 0] + set s1 [string index $new_state 1] + + if {[catch {set info $file_states($path)}]} { + set state __ + set icon n[incr next_icon_id] } else { - set s $file_states($path) - set m [lindex $s 0] + set state [lindex $info 0] + set icon [lindex $info 1] } - if {[string index $state 0] == {_}} { - set state [string index $m 0][string index $state 1] - } elseif {[string index $state 0] == {*}} { - set state _[string index $state 1] + if {$s0 == {_}} { + set s0 [string index $state 0] + } elseif {$s0 == {*}} { + set s0 _ } - if {[string index $state 1] == {_}} { - set state [string index $state 0][string index $m 1] - } elseif {[string index $state 1] == {*}} { - set state [string index $state 0]_ + if {$s1 == {_}} { + set s1 [string index $state 1] + } elseif {$s1 == {*}} { + set s1 _ } - set file_states($path) [lreplace $s 0 0 $state] - return $m + set file_states($path) [list $s0$s1 $icon] + return $state } proc display_file {path state} { @@ -750,19 +758,12 @@ proc display_file {path state} { if {$status_active} return set s $file_states($path) + set old_w [mapcol $old_m $path] + set new_w [mapcol $new_m $path] set new_m [lindex $s 0] - set new_col [mapcol $new_m $path] set new_ico [mapicon $new_m $path] - if {$new_col == {o}} { - set old_w $ui_index - set new_w $ui_other - } else { - set old_w $ui_other - set new_w $ui_index - } - - if {$new_col != [mapcol $old_m $path]} { + if {$new_w != $old_w} { set lno [bsearch $old_w $path] if {$lno >= 0} { incr lno @@ -795,18 +796,12 @@ proc display_all_files {} { foreach path [lsort [array names file_states]] { set s $file_states($path) set m [lindex $s 0] - - if {[mapcol $m $path] == {o}} { - set aw $ui_other - } else { - set aw $ui_index - } - - $aw image create end \ + set w [mapcol $m $path] + $w image create end \ -align center -padx 5 -pady 1 \ -name [lindex $s 1] \ -image [mapicon $m $path] - $aw insert end "$path\n" + $w insert end "$path\n" } $ui_index conf -state disabled @@ -1025,6 +1020,8 @@ static unsigned char file_merge_bits[] = { 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; } -maskdata $filemask +set ui_index .vpane.files.index.list +set ui_other .vpane.files.other.list set max_status_desc 0 foreach i { {__ i plain "Unmodified"} @@ -1035,7 +1032,7 @@ foreach i { {_O o plain "Untracked"} {A_ o fulltick "Added"} {AM o parttick "Partially added"} - {AD o question "Added (but now gone)"} + {AD o question "Added (but now gone)"} {_D i question "Missing"} {D_ i removed "Removed"} @@ -1048,7 +1045,11 @@ foreach i { if {$max_status_desc < [string length [lindex $i 3]]} { set max_status_desc [string length [lindex $i 3]] } - set all_cols([lindex $i 0]) [lindex $i 1] + if {[lindex $i 1] == {i}} { + set all_cols([lindex $i 0]) $ui_index + } else { + set all_cols([lindex $i 0]) $ui_other + } set all_icons([lindex $i 0]) file_[lindex $i 2] set all_descs([lindex $i 0]) [lindex $i 3] } @@ -1461,7 +1462,6 @@ panedwindow .vpane.files -orient horizontal pack .vpane -anchor n -side top -fill both -expand 1 # -- Index File List -set ui_index .vpane.files.index.list frame .vpane.files.index -height 100 -width 400 label .vpane.files.index.title -text {Modified Files} \ -background green \ @@ -1479,7 +1479,6 @@ pack $ui_index -side left -fill both -expand 1 .vpane.files add .vpane.files.index -sticky nsew # -- Other (Add) File List -set ui_other .vpane.files.other.list frame .vpane.files.other -height 100 -width 100 label .vpane.files.other.title -text {Untracked Files} \ -background red \ From 0fb8f9ce05bf65cc9c22f078c4a6dc7dec3ec27a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 20:27:46 -0500 Subject: [PATCH 025/548] git-gui: Flip commit message buffer and diff area. Since Tk will only supply new space gained from growing the top level to the bottom/right most widget within a panedwindow and most users will be growing a git-gui main window for the purposes of seeing more of the currently shown diff, flipping the order around makes Tk do what the user wants by default. Of course because we also removed the paned window from the commit buffer area it is now impossible to increase the visible space for the commit message. But I don't see this as a huge concern right now as its actually very awkward to try and balance three paned window dividers within the same top level window. We could always add it back if users really want to expand the commit buffer and see more. I also corrected a number of bugs that I accidentally introduced in the last commit. Signed-off-by: Shawn O. Pearce --- git-gui | 179 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 91 insertions(+), 88 deletions(-) diff --git a/git-gui b/git-gui index 1833fa86ce..83f713535f 100755 --- a/git-gui +++ b/git-gui @@ -70,7 +70,7 @@ proc repository_state {hdvar ctvar} { proc update_status {{final Ready.}} { global HEAD PARENT commit_type global ui_index ui_other ui_status_value ui_comm - global status_active file_states status_start + global status_active file_states if {$status_active || ![lock_index read]} return @@ -84,7 +84,6 @@ proc update_status {{final Ready.}} { set commit_type $new_type } - set status_start [clock seconds] array unset file_states foreach w [list $ui_index $ui_other] { $w conf -state normal @@ -231,7 +230,7 @@ proc read_ls_others {fd final} { } proc status_eof {fd buf final} { - global status_active status_start $buf + global status_active $buf global ui_fname_value ui_status_value file_states if {[eof $fd]} { @@ -241,11 +240,7 @@ proc status_eof {fd buf final} { if {[incr status_active -1] == 0} { unlock_index - set e1 [clock seconds] display_all_files - set e2 [clock seconds] -puts "TIME [expr $e1 - $status_start] + [expr $e2 - $e1] = [expr $e2 - $status_start]" - set ui_status_value $final if {$ui_fname_value != {} && [array names file_states \ @@ -758,10 +753,10 @@ proc display_file {path state} { if {$status_active} return set s $file_states($path) - set old_w [mapcol $old_m $path] - set new_w [mapcol $new_m $path] set new_m [lindex $s 0] - set new_ico [mapicon $new_m $path] + set new_w [mapcol $new_m $path] + set old_w [mapcol $old_m $path] + set new_icon [mapicon $new_m $path] if {$new_w != $old_w} { set lno [bsearch $old_w $path] @@ -1498,101 +1493,56 @@ pack $ui_other -side left -fill both -expand 1 $ui_index tag conf in_diff -font [concat $mainfont bold] $ui_other tag conf in_diff -font [concat $mainfont bold] -# -- Diff Header -set ui_fname_value {} -set ui_fstatus_value {} -frame .vpane.diff -height 200 -width 400 -frame .vpane.diff.header -label .vpane.diff.header.l1 -text {File:} -font $mainfont -label .vpane.diff.header.l2 -textvariable ui_fname_value \ - -anchor w \ - -justify left \ - -font $mainfont -label .vpane.diff.header.l3 -text {Status:} -font $mainfont -label .vpane.diff.header.l4 -textvariable ui_fstatus_value \ - -width $max_status_desc \ - -anchor w \ - -justify left \ - -font $mainfont -pack .vpane.diff.header.l1 -side left -pack .vpane.diff.header.l2 -side left -fill x -pack .vpane.diff.header.l4 -side right -pack .vpane.diff.header.l3 -side right - -# -- Diff Body -frame .vpane.diff.body -set ui_diff .vpane.diff.body.t -text $ui_diff -background white -borderwidth 0 \ - -width 80 -height 15 -wrap none \ - -font $difffont \ - -xscrollcommand {.vpane.diff.body.sbx set} \ - -yscrollcommand {.vpane.diff.body.sby set} \ - -cursor $maincursor \ - -state disabled -scrollbar .vpane.diff.body.sbx -orient horizontal \ - -command [list $ui_diff xview] -scrollbar .vpane.diff.body.sby -orient vertical \ - -command [list $ui_diff yview] -pack .vpane.diff.body.sbx -side bottom -fill x -pack .vpane.diff.body.sby -side right -fill y -pack $ui_diff -side left -fill both -expand 1 -pack .vpane.diff.header -side top -fill x -pack .vpane.diff.body -side bottom -fill both -expand 1 -.vpane add .vpane.diff -stick nsew - -$ui_diff tag conf dm -foreground red -$ui_diff tag conf dp -foreground blue -$ui_diff tag conf da -font [concat $difffont bold] -$ui_diff tag conf di -foreground "#00a000" -$ui_diff tag conf dni -foreground "#a000a0" -$ui_diff tag conf bold -font [concat $difffont bold] - -# -- Commit Area -frame .vpane.commarea -height 170 -.vpane add .vpane.commarea -stick nsew +# -- Diff and Commit Area +frame .vpane.lower -height 400 -width 400 +frame .vpane.lower.commarea +frame .vpane.lower.diff -relief sunken -borderwidth 1 +pack .vpane.lower.commarea -side top -fill x +pack .vpane.lower.diff -side bottom -fill both -expand 1 +.vpane add .vpane.lower -stick nsew # -- Commit Area Buttons -frame .vpane.commarea.buttons -label .vpane.commarea.buttons.l -text {} \ +frame .vpane.lower.commarea.buttons +label .vpane.lower.commarea.buttons.l -text {} \ -anchor w \ -justify left \ -font $mainfont -pack .vpane.commarea.buttons.l -side top -fill x -pack .vpane.commarea.buttons -side left -fill y +pack .vpane.lower.commarea.buttons.l -side top -fill x +pack .vpane.lower.commarea.buttons -side left -fill y -button .vpane.commarea.buttons.rescan -text {Rescan} \ +button .vpane.lower.commarea.buttons.rescan -text {Rescan} \ -command do_rescan \ -font $mainfont -pack .vpane.commarea.buttons.rescan -side top -fill x -lappend disable_on_lock {.vpane.commarea.buttons.rescan conf -state} +pack .vpane.lower.commarea.buttons.rescan -side top -fill x +lappend disable_on_lock {.vpane.lower.commarea.buttons.rescan conf -state} -button .vpane.commarea.buttons.amend -text {Amend Last} \ +button .vpane.lower.commarea.buttons.amend -text {Amend Last} \ -command do_amend_last \ -font $mainfont -pack .vpane.commarea.buttons.amend -side top -fill x -lappend disable_on_lock {.vpane.commarea.buttons.amend conf -state} +pack .vpane.lower.commarea.buttons.amend -side top -fill x +lappend disable_on_lock {.vpane.lower.commarea.buttons.amend conf -state} -button .vpane.commarea.buttons.ciall -text {Check-in All} \ +button .vpane.lower.commarea.buttons.ciall -text {Check-in All} \ -command do_checkin_all \ -font $mainfont -pack .vpane.commarea.buttons.ciall -side top -fill x -lappend disable_on_lock {.vpane.commarea.buttons.ciall conf -state} +pack .vpane.lower.commarea.buttons.ciall -side top -fill x +lappend disable_on_lock {.vpane.lower.commarea.buttons.ciall conf -state} -button .vpane.commarea.buttons.signoff -text {Sign Off} \ +button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \ -command do_signoff \ -font $mainfont -pack .vpane.commarea.buttons.signoff -side top -fill x +pack .vpane.lower.commarea.buttons.signoff -side top -fill x -button .vpane.commarea.buttons.commit -text {Commit} \ +button .vpane.lower.commarea.buttons.commit -text {Commit} \ -command do_commit \ -font $mainfont -pack .vpane.commarea.buttons.commit -side top -fill x -lappend disable_on_lock {.vpane.commarea.buttons.commit conf -state} +pack .vpane.lower.commarea.buttons.commit -side top -fill x +lappend disable_on_lock {.vpane.lower.commarea.buttons.commit conf -state} # -- Commit Message Buffer -frame .vpane.commarea.buffer -set ui_comm .vpane.commarea.buffer.t -set ui_coml .vpane.commarea.buffer.l +frame .vpane.lower.commarea.buffer +set ui_comm .vpane.lower.commarea.buffer.t +set ui_coml .vpane.lower.commarea.buffer.l label $ui_coml -text {Commit Message:} \ -anchor w \ -justify left \ @@ -1606,15 +1556,68 @@ trace add variable commit_type write {uplevel #0 { }} text $ui_comm -background white -borderwidth 1 \ -relief sunken \ - -width 75 -height 10 -wrap none \ + -width 75 -height 9 -wrap none \ -font $difffont \ - -yscrollcommand {.vpane.commarea.buffer.sby set} \ + -yscrollcommand {.vpane.lower.commarea.buffer.sby set} \ -cursor $maincursor -scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview] +scrollbar .vpane.lower.commarea.buffer.sby -command [list $ui_comm yview] pack $ui_coml -side top -fill x -pack .vpane.commarea.buffer.sby -side right -fill y +pack .vpane.lower.commarea.buffer.sby -side right -fill y pack $ui_comm -side left -fill y -pack .vpane.commarea.buffer -side left -fill y +pack .vpane.lower.commarea.buffer -side left -fill y + +# -- Diff Header +set ui_fname_value {} +set ui_fstatus_value {} +frame .vpane.lower.diff.header -background orange +label .vpane.lower.diff.header.l1 -text {File:} \ + -background orange \ + -font $mainfont +label .vpane.lower.diff.header.l2 -textvariable ui_fname_value \ + -background orange \ + -anchor w \ + -justify left \ + -font $mainfont +label .vpane.lower.diff.header.l3 -text {Status:} \ + -background orange \ + -font $mainfont +label .vpane.lower.diff.header.l4 -textvariable ui_fstatus_value \ + -background orange \ + -width $max_status_desc \ + -anchor w \ + -justify left \ + -font $mainfont +pack .vpane.lower.diff.header.l1 -side left +pack .vpane.lower.diff.header.l2 -side left -fill x +pack .vpane.lower.diff.header.l4 -side right +pack .vpane.lower.diff.header.l3 -side right + +# -- Diff Body +frame .vpane.lower.diff.body +set ui_diff .vpane.lower.diff.body.t +text $ui_diff -background white -borderwidth 0 \ + -width 80 -height 15 -wrap none \ + -font $difffont \ + -xscrollcommand {.vpane.lower.diff.body.sbx set} \ + -yscrollcommand {.vpane.lower.diff.body.sby set} \ + -cursor $maincursor \ + -state disabled +scrollbar .vpane.lower.diff.body.sbx -orient horizontal \ + -command [list $ui_diff xview] +scrollbar .vpane.lower.diff.body.sby -orient vertical \ + -command [list $ui_diff yview] +pack .vpane.lower.diff.body.sbx -side bottom -fill x +pack .vpane.lower.diff.body.sby -side right -fill y +pack $ui_diff -side left -fill both -expand 1 +pack .vpane.lower.diff.header -side top -fill x +pack .vpane.lower.diff.body -side bottom -fill both -expand 1 + +$ui_diff tag conf dm -foreground red +$ui_diff tag conf dp -foreground blue +$ui_diff tag conf da -font [concat $difffont bold] +$ui_diff tag conf di -foreground "#00a000" +$ui_diff tag conf dni -foreground "#a000a0" +$ui_diff tag conf bold -font [concat $difffont bold] # -- Status Bar set ui_status_value {Initializing...} From d1536c488e2f410c9f622356e29d9a5870d2403f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 20:40:35 -0500 Subject: [PATCH 026/548] git-gui: Added repack database menu option, to invoke git repack. Most users of git-gui probably shouldn't be invoking git repack directly; instead we should be looking at how many loose objects they have and how many active packs they have and making the decision for them. But that's more work to code, and there's always going to be discussion about what is the right default threshold and how do we know that the user is willing to do the repack when we decide its time, etc. So instead we'll just keep it simple and offer up the menu option. Unfortunately right now we get now progress indication back from git-pack-objects as its being invoked not through a tty, which makes it disable progress output and the git-repack.sh wrapper won't let us pass through --progress. Signed-off-by: Shawn O. Pearce --- git-gui | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/git-gui b/git-gui index 83f713535f..eb7329c218 100755 --- a/git-gui +++ b/git-gui @@ -1276,6 +1276,14 @@ proc do_gitk {} { } } +proc do_repack {} { + set w [new_console "repack" "Repacking the object database"] + set cmd [list git repack] + lappend cmd -a + lappend cmd -d + console_exec $w $cmd +} + proc do_quit {} { global gitdir ui_comm @@ -1406,6 +1414,9 @@ menu .mbar.project .mbar.project add command -label Visualize \ -command do_gitk \ -font $mainfont +.mbar.project add command -label {Repack Database} \ + -command do_repack \ + -font $mainfont .mbar.project add command -label Quit \ -command do_quit \ -accelerator $M1T-Q \ From e534f3a88676fe0a08eb20359e1a43c6aa7dfe84 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 21:27:29 -0500 Subject: [PATCH 027/548] git-gui: Allow the user to disable update-index --refresh during rescan. On very large projects (~1000 files) on Windows systems the update-index --refresh stage of the rescan process takes a nontrival amount of time. If the user is generally very careful with their file modification such that the modification timestamp of the file differs only when the content also differs then we can skip this somewhat expensive step and go right to the diff-index and diff-files processes. We save the user's prefernce in the current repository if they modify the setting during a git-gui session, but we also load it through our dump of repo-config --list so the user could move it to their ~/.gitconfig file if they wanted it globally disabled. We still keep update-index --refresh enabled by default however, as most users will probably want it. Signed-off-by: Shawn O. Pearce --- git-gui | 51 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/git-gui b/git-gui index eb7329c218..8562983d36 100755 --- a/git-gui +++ b/git-gui @@ -71,6 +71,7 @@ proc update_status {{final Ready.}} { global HEAD PARENT commit_type global ui_index ui_other ui_status_value ui_comm global status_active file_states + global cfg_trust_mtime if {$status_active || ![lock_index read]} return @@ -100,22 +101,28 @@ proc update_status {{final Ready.}} { $ui_comm edit modified false } - set status_active 1 - set ui_status_value {Refreshing file status...} - set fd_rf [open "| git update-index -q --unmerged --refresh" r] - fconfigure $fd_rf -blocking 0 -translation binary - fileevent $fd_rf readable [list read_refresh $fd_rf $final] + if {$cfg_trust_mtime == {true}} { + update_status_stage2 {} $final + } else { + set status_active 1 + set ui_status_value {Refreshing file status...} + set fd_rf [open "| git update-index -q --unmerged --refresh" r] + fconfigure $fd_rf -blocking 0 -translation binary + fileevent $fd_rf readable [list update_status_stage2 $fd_rf $final] + } } -proc read_refresh {fd final} { +proc update_status_stage2 {fd final} { global gitdir PARENT commit_type global ui_index ui_other ui_status_value ui_comm global status_active file_states global buf_rdi buf_rdf buf_rlo - read $fd - if {![eof $fd]} return - close $fd + if {$fd != {}} { + read $fd + if {![eof $fd]} return + close $fd + } set ls_others [list | git ls-files --others -z \ --exclude-per-directory=.gitignore] @@ -860,6 +867,7 @@ proc toggle_mode {path} { proc load_repo_config {} { global repo_config + global cfg_trust_mtime array unset repo_config catch { @@ -871,6 +879,22 @@ proc load_repo_config {} { } close $fd_rc } + + if {[catch {set cfg_trust_mtime $repo_config(gui.trustmtime)}]} { + set cfg_trust_mtime false + } +} + +proc save_my_config {} { + global repo_config + global cfg_trust_mtime + + if {[catch {set rc_trustMTime $repo_config(gui.trustmtime)}]} { + set rc_trustMTime false + } + if {$cfg_trust_mtime != $rc_trustMTime} { + exec git repo-config gui.trustMTime $cfg_trust_mtime + } } proc load_all_remotes {} { @@ -1299,6 +1323,7 @@ proc do_quit {} { file delete $save } + save_my_config destroy . } @@ -1407,6 +1432,7 @@ menu .mbar -tearoff 0 .mbar add cascade -label Fetch -menu .mbar.fetch .mbar add cascade -label Pull -menu .mbar.pull .mbar add cascade -label Push -menu .mbar.push +.mbar add cascade -label Options -menu .mbar.options . configure -menu .mbar # -- Project Menu @@ -1461,6 +1487,13 @@ menu .mbar.pull # -- Push Menu menu .mbar.push +# -- Options Menu +menu .mbar.options +.mbar.options add checkbutton -label {Trust File Modification Timestamp} \ + -offvalue false \ + -onvalue true \ + -variable cfg_trust_mtime + # -- Main Window Layout panedwindow .vpane -orient vertical panedwindow .vpane.files -orient horizontal From 988b8a7d63ff52f252bb4c517d9f05c3952aa728 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 21:30:46 -0500 Subject: [PATCH 028/548] git-gui: Grab the index lock while running pull. The user must not modify the index while a git pull operation is running, doing so might cause problems for the merge driver and specific strategy being used. Normally on the command line people are just really good and don't try to run index altering operations while they are also running a pull. But in a slick GUI like git-gui we can't trust the user quite as much. Signed-off-by: Shawn O. Pearce --- git-gui | 2 ++ 1 file changed, 2 insertions(+) diff --git a/git-gui b/git-gui index 8562983d36..2d82152b36 100755 --- a/git-gui +++ b/git-gui @@ -642,6 +642,7 @@ proc fetch_from {remote} { } proc pull_remote {remote branch} { + if {![lock_index update]} return set w [new_console "pull $remote $branch" \ "Pulling new changes from branch $branch in $remote"] set cmd [list git pull] @@ -651,6 +652,7 @@ proc pull_remote {remote branch} { } proc post_pull_remote {remote branch success} { + unlock_index if {$success} { update_status "Successfully pulled $branch from $remote." } else { From 2bc5b3487e47dc6caa8d543fc50a944e80651c59 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 21:38:22 -0500 Subject: [PATCH 029/548] git-gui: Pluralize timestamps within the options menu. Signed-off-by: Shawn O. Pearce --- git-gui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-gui b/git-gui index 2d82152b36..465c2f388b 100755 --- a/git-gui +++ b/git-gui @@ -1491,7 +1491,7 @@ menu .mbar.push # -- Options Menu menu .mbar.options -.mbar.options add checkbutton -label {Trust File Modification Timestamp} \ +.mbar.options add checkbutton -label {Trust File Modification Timestamps} \ -offvalue false \ -onvalue true \ -variable cfg_trust_mtime From 0a462d67761b8178f09e23ef85a9298d1e19a2eb Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 21:43:16 -0500 Subject: [PATCH 030/548] git-gui: Disable pull menu items when the index is locked. If we have the index locked then no pull command is allowed to proceed (as it would fail to get the index lock itself). So disable the pull menu items when we are doing any index based operations. Signed-off-by: Shawn O. Pearce --- git-gui | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/git-gui b/git-gui index 465c2f388b..4041aaacd9 100755 --- a/git-gui +++ b/git-gui @@ -932,7 +932,7 @@ proc populate_remote_menu {m pfx op} { } proc populate_pull_menu {m} { - global gitdir repo_config all_remotes mainfont + global gitdir repo_config all_remotes mainfont disable_on_lock foreach remote $all_remotes { set rb {} @@ -961,6 +961,8 @@ proc populate_pull_menu {m} { -label "Branch $rb_short from $remote..." \ -command [list pull_remote $remote $rb] \ -font $mainfont + lappend disable_on_lock \ + [list $m entryconf [$m index last] -state] } } } From ec39d83a55124660d03a744e0e7f67072a833950 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 22:00:38 -0500 Subject: [PATCH 031/548] git-gui: Don't let the user pull into an uncommitted working directory. If there are modified files present in the working directory then we should not let the user perform a pull as it may fail due to the modified files being uncommitted but needing to be merged at the file level. Yes there are many cases where a merge will complete successfully even though there are modified or untracked files sitting in the working directory. But users generally shouldn't be attempting merges like that, and if they are they probably are advanced enough to just use the command line and bypass this little safety check. We also no longer run a rescan after a successful pull has completed. Usually this is unnecessary as a successful pull won't leave modified files laying around. Instead we just update our HEAD and PARENT values with the new commit, if there is one. Unfortunately this does let the user get into an insane state as there are bugs in core Git's git-pull and git-merge programs where the exit status is sent back as a 0 rather than non-0 when a failure is detected. Signed-off-by: Shawn O. Pearce --- git-gui | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/git-gui b/git-gui index 4041aaacd9..d405008d2c 100755 --- a/git-gui +++ b/git-gui @@ -642,7 +642,40 @@ proc fetch_from {remote} { } proc pull_remote {remote branch} { + global HEAD commit_type + global file_states + if {![lock_index update]} return + + # -- Our in memory state should match the repository. + # + repository_state curHEAD cur_type + if {$commit_type != $cur_type || $HEAD != $curHEAD} { + error_popup {Last scanned state does not match repository state. + +Its highly likely that another Git program modified the +repository since our last scan. A rescan is required +before a pull can be started. +} + unlock_index + update_status + return + } + + # -- No differences should exist before a pull. + # + if {[array size file_states] != 0} { + error_popup {Uncommitted but modified files are present. + +You should not perform a pull with unmodified files in your working +directory as Git would be unable to recover from an incorrect merge. + +Commit or throw away all changes before starting a pull operation. +} + unlock_index + return + } + set w [new_console "pull $remote $branch" \ "Pulling new changes from branch $branch in $remote"] set cmd [list git pull] @@ -652,9 +685,14 @@ proc pull_remote {remote branch} { } proc post_pull_remote {remote branch success} { + global HEAD PARENT commit_type + global ui_status_value + unlock_index if {$success} { - update_status "Successfully pulled $branch from $remote." + repository_state HEAD commit_type + set PARENT $HEAD + set $ui_status_value {Ready.} } else { update_status "Conflicts detected while pulling $branch from $remote." } From bfe4c924da3853956d1b476afaa5f04429695223 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 22:03:03 -0500 Subject: [PATCH 032/548] git-gui: Update TODO list. Signed-off-by: Shawn O. Pearce --- TODO | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO b/TODO index 136e87849c..0b1fa2ca59 100644 --- a/TODO +++ b/TODO @@ -10,6 +10,8 @@ Items outstanding: * Add file to .gitignore or info/excludes. + * Make initial commits work (currently it crashes the UI). + * Populate the pull menu with local branches. * Make use of the new default merge data stored in repo-config. From e4ee9af4946557370cfe18efdfa46c265a10639f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Nov 2006 22:09:55 -0500 Subject: [PATCH 033/548] git-gui: Bug fix for bad variable reference in display_file. When a file jumps between the file lists due to its state changing we crashed thanks to a stale variable reference within the procedure as we tried to setup the new icon. Signed-off-by: Shawn O. Pearce --- git-gui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-gui b/git-gui index d405008d2c..515cbb9dfb 100755 --- a/git-gui +++ b/git-gui @@ -819,7 +819,7 @@ proc display_file {path state} { $new_w image create $lno.0 \ -align center -padx 5 -pady 1 \ -name [lindex $s 1] \ - -image [mapicon $m $path] + -image $new_icon $new_w insert $lno.1 "$path\n" $new_w conf -state disabled } elseif {$new_icon != [mapicon $old_m $path]} { From 7fe7e733fafbab0373ee0d8fc35b9e284a598ee4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 8 Nov 2006 22:48:34 -0500 Subject: [PATCH 034/548] git-gui: Changed term 'check-in' to 'include'. At least one user was confused by the term 'check-in'; he thought that clicking that button would commit just that one file, but he wanted to include all modified files into his next commit. Since git doesn't really have a check-in concept this really was poor language to use. Git does have an update-index concept but that is a little too low level to show to the user. So instead we now talk about including files in a commit. Signed-off-by: Shawn O. Pearce --- git-gui | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/git-gui b/git-gui index 515cbb9dfb..c57d713144 100755 --- a/git-gui +++ b/git-gui @@ -14,7 +14,7 @@ exec wish "$0" -- "$@" set single_commit 0 set status_active 0 set diff_active 0 -set checkin_active 0 +set update_active 0 set commit_active 0 set update_index_fd {} @@ -469,7 +469,7 @@ before committing. error_popup "Unmerged files cannot be committed. File $path has merge conflicts. -You must resolve them and check the file in before committing. +You must resolve them and include the file before committing. " unlock_index return @@ -483,9 +483,9 @@ File $path cannot be committed by this program. } } if {!$files_ready} { - error_popup {No checked-in files to commit. + error_popup {No included files to commit. -You must check-in at least 1 file before you can commit. +You must include at least 1 file before you can commit. } unlock_index return @@ -1088,7 +1088,7 @@ foreach i { {__ i plain "Unmodified"} {_M i mod "Modified"} {M_ i fulltick "Checked in"} - {MM i parttick "Partially checked in"} + {MM i parttick "Partially included"} {_O o plain "Untracked"} {A_ o fulltick "Added"} @@ -1373,13 +1373,13 @@ proc do_rescan {} { update_status } -proc do_checkin_all {} { - global checkin_active ui_status_value +proc do_include_all {} { + global update_active ui_status_value - if {$checkin_active || ![lock_index begin-update]} return + if {$update_active || ![lock_index begin-update]} return - set checkin_active 1 - set ui_status_value {Checking in all files...} + set update_active 1 + set ui_status_value {Including all modified files...} after 1 { with_update_index { foreach path [array names file_states] { @@ -1393,7 +1393,7 @@ proc do_checkin_all {} { } } } - set checkin_active 0 + set update_active 0 set ui_status_value {Ready.} } } @@ -1503,8 +1503,8 @@ lappend disable_on_lock \ -font $mainfont lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] -.mbar.commit add command -label {Check-in All Files} \ - -command do_checkin_all \ +.mbar.commit add command -label {Include All Files} \ + -command do_include_all \ -accelerator $M1T-U \ -font $mainfont lappend disable_on_lock \ @@ -1608,11 +1608,11 @@ button .vpane.lower.commarea.buttons.amend -text {Amend Last} \ pack .vpane.lower.commarea.buttons.amend -side top -fill x lappend disable_on_lock {.vpane.lower.commarea.buttons.amend conf -state} -button .vpane.lower.commarea.buttons.ciall -text {Check-in All} \ - -command do_checkin_all \ +button .vpane.lower.commarea.buttons.incall -text {Include All} \ + -command do_include_all \ -font $mainfont -pack .vpane.lower.commarea.buttons.ciall -side top -fill x -lappend disable_on_lock {.vpane.lower.commarea.buttons.ciall conf -state} +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 \ @@ -1723,8 +1723,8 @@ bind all <$M1B-Key-r> do_rescan bind all <$M1B-Key-R> do_rescan bind . <$M1B-Key-s> do_signoff bind . <$M1B-Key-S> do_signoff -bind . <$M1B-Key-u> do_checkin_all -bind . <$M1B-Key-U> do_checkin_all +bind . <$M1B-Key-u> do_include_all +bind . <$M1B-Key-U> do_include_all bind . <$M1B-Key-Return> do_commit bind all <$M1B-Key-q> do_quit bind all <$M1B-Key-Q> do_quit From d4ab2035ca64ccb380c42a73f1ce0aefe1669a39 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 8 Nov 2006 22:51:09 -0500 Subject: [PATCH 035/548] git-gui: Show only the abbreviated SHA1 after committing. There's really no great reason to show the entire commit object id within the GUI, especially if the user is unable to copy and paste it into another interface such as gitk or a terminal window. So we'll just show them the first 8 digits and hope that is unique within their repository. Signed-off-by: Shawn O. Pearce --- git-gui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-gui b/git-gui index c57d713144..1b1ffee5ea 100755 --- a/git-gui +++ b/git-gui @@ -626,7 +626,7 @@ proc commit_stage2 {fd_wt curHEAD msg} { set HEAD $cmt_id set PARENT $cmt_id unlock_index - update_status "Changes committed as $cmt_id." + update_status "Changes committed as [string range $cmt_id 0 7]." } ###################################################################### From 97bf01c46526a680fb04ac88cb0ecaefbd3b4d7f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 8 Nov 2006 23:05:46 -0500 Subject: [PATCH 036/548] git-gui: Cache the GIT_COMMITTER_IDENT value on first sign-off. Caching the Signed-Off-By line isn't very important (as its not performance critical). The major improvement here is that we now report an error to the user if we can't obtain their name from git-var. Signed-off-by: Shawn O. Pearce --- git-gui | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/git-gui b/git-gui index 1b1ffee5ea..ad3aa0727c 100755 --- a/git-gui +++ b/git-gui @@ -1398,19 +1398,28 @@ proc do_include_all {} { } } -proc do_signoff {} { - global ui_comm +set GIT_COMMITTER_IDENT {} - catch { - set me [exec git var GIT_COMMITTER_IDENT] - if {[regexp {(.*) [0-9]+ [-+0-9]+$} $me me name]} { - set str "Signed-off-by: $name" - if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} { - $ui_comm insert end "\n" - $ui_comm insert end $str - $ui_comm see end - } +proc do_signoff {} { + global ui_comm GIT_COMMITTER_IDENT + + if {$GIT_COMMITTER_IDENT == {}} { + if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} { + error_popup "Unable to obtain your identity:\n$err" + return } + if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \ + $me me GIT_COMMITTER_IDENT]} { + error_popup "Invalid GIT_COMMITTER_IDENT:\n$me" + return + } + } + + set str "Signed-off-by: $GIT_COMMITTER_IDENT" + if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} { + $ui_comm insert end "\n" + $ui_comm insert end $str + $ui_comm see end } } From 2d19516db4a6807e817879e6d30fd3137a4a7817 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 8 Nov 2006 23:42:51 -0500 Subject: [PATCH 037/548] git-gui: Save window geometry to .git/config during exit. I started to find it very annoying that my test application kept opening at the wrong location on my desktop, so now we save the basic window geometry and sash positions into the config file as gui.geometry. Signed-off-by: Shawn O. Pearce --- git-gui | 149 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 88 insertions(+), 61 deletions(-) diff --git a/git-gui b/git-gui index ad3aa0727c..aa73aa3deb 100755 --- a/git-gui +++ b/git-gui @@ -7,6 +7,86 @@ exec wish "$0" -- "$@" # and distributed under the terms of the GNU General Public Licence, # either version 2, or (at your option) any later version. +###################################################################### +## +## config + +proc load_repo_config {} { + global repo_config + global cfg_trust_mtime + + array unset repo_config + catch { + set fd_rc [open "| git repo-config --list" r] + while {[gets $fd_rc line] >= 0} { + if {[regexp {^([^=]+)=(.*)$} $line line name value]} { + lappend repo_config($name) $value + } + } + close $fd_rc + } + + if {[catch {set cfg_trust_mtime \ + [lindex $repo_config(gui.trustmtime) 0] + }]} { + set cfg_trust_mtime false + } +} + +proc save_my_config {} { + global repo_config + global cfg_trust_mtime + + if {[catch {set rc_trustMTime $repo_config(gui.trustmtime)}]} { + set rc_trustMTime [list false] + } + if {$cfg_trust_mtime != [lindex $rc_trustMTime 0]} { + exec git repo-config gui.trustMTime $cfg_trust_mtime + set repo_config(gui.trustmtime) [list $cfg_trust_mtime] + } + + set cfg_geometry [list \ + [wm geometry .] \ + [.vpane sash coord 0] \ + [.vpane.files sash coord 0] \ + ] + if {[catch {set rc_geometry $repo_config(gui.geometry)}]} { + set rc_geometry [list [list]] + } + if {$cfg_geometry != [lindex $rc_geometry 0]} { + exec git repo-config gui.geometry $cfg_geometry + set repo_config(gui.geometry) [list $cfg_geometry] + } +} + +###################################################################### +## +## repository setup + +set appname [lindex [file split $argv0] end] +set gitdir {} +set GIT_COMMITTER_IDENT {} + +if {[catch {set cdup [exec git rev-parse --show-cdup]} err]} { + show_msg {} . "Cannot find the git directory: $err" + exit 1 +} +if {$cdup != ""} { + cd $cdup +} +unset cdup + +if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} { + show_msg {} . "Cannot find the git directory: $err" + exit 1 +} + +if {$appname == {git-citool}} { + set single_commit 1 +} + +load_repo_config + ###################################################################### ## ## task management @@ -903,39 +983,7 @@ proc toggle_mode {path} { ###################################################################### ## -## config (fetch push pull) - -proc load_repo_config {} { - global repo_config - global cfg_trust_mtime - - array unset repo_config - catch { - set fd_rc [open "| git repo-config --list" r] - while {[gets $fd_rc line] >= 0} { - if {[regexp {^([^=]+)=(.*)$} $line line name value]} { - lappend repo_config($name) $value - } - } - close $fd_rc - } - - if {[catch {set cfg_trust_mtime $repo_config(gui.trustmtime)}]} { - set cfg_trust_mtime false - } -} - -proc save_my_config {} { - global repo_config - global cfg_trust_mtime - - if {[catch {set rc_trustMTime $repo_config(gui.trustmtime)}]} { - set rc_trustMTime false - } - if {$cfg_trust_mtime != $rc_trustMTime} { - exec git repo-config gui.trustMTime $cfg_trust_mtime - } -} +## remote management proc load_all_remotes {} { global gitdir all_remotes repo_config @@ -1398,8 +1446,6 @@ proc do_include_all {} { } } -set GIT_COMMITTER_IDENT {} - proc do_signoff {} { global ui_comm GIT_COMMITTER_IDENT @@ -1724,6 +1770,13 @@ label .status -textvariable ui_status_value \ -font $mainfont pack .status -anchor w -side bottom -fill x +# -- Load geometry +catch { +wm geometry . [lindex $repo_config(gui.geometry) 0 0] +eval .vpane sash place 0 [lindex $repo_config(gui.geometry) 0 1] +eval .vpane.files sash place 0 [lindex $repo_config(gui.geometry) 0 2] +} + # -- Key Bindings bind $ui_comm <$M1B-Key-Return> {do_commit;break} bind . do_quit @@ -1746,34 +1799,8 @@ foreach i [list $ui_index $ui_other] { } unset i M1B M1T -###################################################################### -## -## main - -set appname [lindex [file split $argv0] end] -set gitdir {} - -if {[catch {set cdup [exec git rev-parse --show-cdup]} err]} { - show_msg {} . "Cannot find the git directory: $err" - exit 1 -} -if {$cdup != ""} { - cd $cdup -} -unset cdup - -if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} { - show_msg {} . "Cannot find the git directory: $err" - exit 1 -} - -if {$appname == {git-citool}} { - set single_commit 1 -} - wm title . "$appname ([file normalize [file dirname $gitdir]])" focus -force $ui_comm -load_repo_config load_all_remotes populate_remote_menu .mbar.fetch From fetch_from populate_remote_menu .mbar.push To push_to From 49b86f010c3f2d6576a8fba45c5c6b9a23f76fa0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 15:16:01 -0500 Subject: [PATCH 038/548] git-gui: Change accelerator for "Include All" to M1-I. Now that we call the update-index all files action "Include All" it makes more sense to make this M1-I (so Control-I or Command-I depending on platform) than M1-U, which stood for update but is somewhat confusing to users. Signed-off-by: Shawn O. Pearce --- git-gui | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/git-gui b/git-gui index aa73aa3deb..20c36c2d2e 100755 --- a/git-gui +++ b/git-gui @@ -1560,7 +1560,7 @@ lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Include All Files} \ -command do_include_all \ - -accelerator $M1T-U \ + -accelerator $M1T-I \ -font $mainfont lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] @@ -1779,14 +1779,17 @@ eval .vpane.files sash place 0 [lindex $repo_config(gui.geometry) 0 2] # -- Key Bindings bind $ui_comm <$M1B-Key-Return> {do_commit;break} +bind $ui_comm <$M1B-Key-i> {do_include_all;break} +bind $ui_comm <$M1B-Key-I> {do_include_all;break} + bind . do_quit bind all do_rescan bind all <$M1B-Key-r> do_rescan bind all <$M1B-Key-R> do_rescan bind . <$M1B-Key-s> do_signoff bind . <$M1B-Key-S> do_signoff -bind . <$M1B-Key-u> do_include_all -bind . <$M1B-Key-U> do_include_all +bind . <$M1B-Key-i> do_include_all +bind . <$M1B-Key-I> do_include_all bind . <$M1B-Key-Return> do_commit bind all <$M1B-Key-q> do_quit bind all <$M1B-Key-Q> do_quit From 9861671de26bf7cd41f591bd2099ac299349f284 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 15:51:41 -0500 Subject: [PATCH 039/548] git-gui: Created edit menu and basic editing bindings. Users have come to expect basic editing features within their applications, such as cut/copy/paste/undo/redo/select-all. I found these features to be lacking in git-gui so now we have them. I also added basic keyboard bindings for the diff viewing area so that the arrow keys move around single units (lines or columns) and the M1-X/C keys will copy the selected text and the M1-A key will select the entire diff. Signed-off-by: Shawn O. Pearce --- git-gui | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/git-gui b/git-gui index 20c36c2d2e..640519c204 100755 --- a/git-gui +++ b/git-gui @@ -1525,6 +1525,7 @@ default {set M1B M1; set M1T M1} # -- Menu Bar menu .mbar -tearoff 0 .mbar add cascade -label Project -menu .mbar.project +.mbar add cascade -label Edit -menu .mbar.edit .mbar add cascade -label Commit -menu .mbar.commit .mbar add cascade -label Fetch -menu .mbar.fetch .mbar add cascade -label Pull -menu .mbar.pull @@ -1545,6 +1546,40 @@ menu .mbar.project -accelerator $M1T-Q \ -font $mainfont +# -- Edit Menu +# +menu .mbar.edit +.mbar.edit add command -label Undo \ + -command {catch {[focus] edit undo}} \ + -accelerator $M1T-Z \ + -font $mainfont +.mbar.edit add command -label Redo \ + -command {catch {[focus] edit redo}} \ + -accelerator $M1T-Y \ + -font $mainfont +.mbar.edit add separator +.mbar.edit add command -label Cut \ + -command {catch {tk_textCut [focus]}} \ + -accelerator $M1T-X \ + -font $mainfont +.mbar.edit add command -label Copy \ + -command {catch {tk_textCopy [focus]}} \ + -accelerator $M1T-C \ + -font $mainfont +.mbar.edit add command -label Paste \ + -command {catch {tk_textPaste [focus]; [focus] see insert}} \ + -accelerator $M1T-V \ + -font $mainfont +.mbar.edit add command -label Delete \ + -command {catch {[focus] delete sel.first sel.last}} \ + -accelerator Del \ + -font $mainfont +.mbar.edit add separator +.mbar.edit add command -label {Select All} \ + -command {catch {[focus] tag add sel 0.0 end}} \ + -accelerator $M1T-A \ + -font $mainfont + # -- Commit Menu menu .mbar.commit .mbar.commit add command -label Rescan \ @@ -1586,7 +1621,8 @@ menu .mbar.push # -- Options Menu menu .mbar.options -.mbar.options add checkbutton -label {Trust File Modification Timestamps} \ +.mbar.options add checkbutton \ + -label {Trust File Modification Timestamps} \ -offvalue false \ -onvalue true \ -variable cfg_trust_mtime @@ -1696,6 +1732,8 @@ trace add variable commit_type write {uplevel #0 { * {$ui_coml conf -text {Commit Message:}} }} text $ui_comm -background white -borderwidth 1 \ + -undo true \ + -autoseparators true \ -relief sunken \ -width 75 -height 9 -wrap none \ -font $difffont \ @@ -1781,6 +1819,27 @@ eval .vpane.files sash place 0 [lindex $repo_config(gui.geometry) 0 2] bind $ui_comm <$M1B-Key-Return> {do_commit;break} bind $ui_comm <$M1B-Key-i> {do_include_all;break} bind $ui_comm <$M1B-Key-I> {do_include_all;break} +bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break} +bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break} +bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break} +bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break} +bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break} +bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break} +bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break} +bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break} + +bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break} +bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break} +bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break} +bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break} +bind $ui_diff <$M1B-Key-v> {break} +bind $ui_diff <$M1B-Key-V> {break} +bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break} +bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break} +bind $ui_diff {%W yview scroll -1 units} +bind $ui_diff {%W yview scroll 1 units} +bind $ui_diff {%W xview scroll -1 units} +bind $ui_diff {%W xview scroll 1 units} bind . do_quit bind all do_rescan From b2c6fcf197fd09a6fedb39e4d8d13e6fdc2df4d4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 16:16:25 -0500 Subject: [PATCH 040/548] git-gui: Clear undo/redo stack when loading a message file from disk. If we load a message file (e.g. MERGE_MSG) or we have just finished making a commit and are clearing out the commit buffer we should also clear out the undo/redo stack associated with that buffer. The prior undo/redo stack has no associated with the new content and therefore is not useful to the user. Also modified the sign-off operation to perform the entire update in a single undo/redo operation, allowing the user to undo the signoff in case they didn't actually want to do that. I also noticed what may be a crash on Windows related to the up and down arrow keys navigating within the diff viewer. Since I got back no stack trace (just an application exit with a loss of the commit message) I suspect that the binding to scroll the text widget crashed with an error and the wish process just terminated. So now we catch (and ignore) any sort of error related to the arrow keys in the diff viewer. Signed-off-by: Shawn O. Pearce --- git-gui | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/git-gui b/git-gui index 640519c204..540d56397a 100755 --- a/git-gui +++ b/git-gui @@ -179,6 +179,7 @@ proc update_status {{final Ready.}} { } elseif {[load_message SQUASH_MSG]} { } $ui_comm edit modified false + $ui_comm edit reset } if {$cfg_trust_mtime == {true}} { @@ -503,6 +504,7 @@ proc load_last_commit {} { $ui_comm delete 0.0 end $ui_comm insert end $msg $ui_comm edit modified false + $ui_comm edit reset update_status } else { error_popup {You can't amend a merge commit.} @@ -698,6 +700,7 @@ proc commit_stage2 {fd_wt curHEAD msg} { $ui_comm delete 0.0 end $ui_comm edit modified false + $ui_comm edit reset if {$single_commit} do_quit @@ -1463,8 +1466,9 @@ proc do_signoff {} { set str "Signed-off-by: $GIT_COMMITTER_IDENT" if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} { - $ui_comm insert end "\n" - $ui_comm insert end $str + $ui_comm edit separator + $ui_comm insert end "\n$str" + $ui_comm edit separator $ui_comm see end } } @@ -1733,6 +1737,7 @@ trace add variable commit_type write {uplevel #0 { }} text $ui_comm -background white -borderwidth 1 \ -undo true \ + -maxundo 20 \ -autoseparators true \ -relief sunken \ -width 75 -height 9 -wrap none \ @@ -1836,10 +1841,10 @@ bind $ui_diff <$M1B-Key-v> {break} bind $ui_diff <$M1B-Key-V> {break} bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break} bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break} -bind $ui_diff {%W yview scroll -1 units} -bind $ui_diff {%W yview scroll 1 units} -bind $ui_diff {%W xview scroll -1 units} -bind $ui_diff {%W xview scroll 1 units} +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 . do_quit bind all do_rescan From 7d9e1d5e8a398a9ed3b782e7c2b49d1dcb5f53f7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 17:46:04 -0500 Subject: [PATCH 041/548] git-gui: Updated TODO list now that geometry is stored. Signed-off-by: Shawn O. Pearce --- TODO | 3 --- 1 file changed, 3 deletions(-) diff --git a/TODO b/TODO index 0b1fa2ca59..bb40a13bee 100644 --- a/TODO +++ b/TODO @@ -22,9 +22,6 @@ Items outstanding: * Delete a local branch. - * Store user preferences (like font, window layout) in global - repo-config. - * Allow user to define keyboard shortcuts for frequently used fetch or merge operations. Or maybe just define a keyboard shortcut for default fetch/default merge of current branch is enough; From 03e4ec5364f955f5e1af0775166c679ed60f7fb4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 17:52:16 -0500 Subject: [PATCH 042/548] git-gui: Always indicate the file in the diff viewer. When we did a rescan to update the file lists we lost the tag which indicated which file was currently in the diff viewer. This occurs because we delete every line from the two file list boxes (thus removing the tag) and then redisplay the diff in the diff viewer but then fail to restore the tag in the file list. Now we restore that tag by searching for the file in the file lists and adding the tag back when the diff viewer displays something. We also no longer obtain the file path directly from the file list text box. Instead we now keep two Tcl lists, one for each file list, holding the file names in sorted order. These lists can be searched with the native [lsearch -sorted] operation (which should be faster than our crude bsearch) or can be quickly accessed by index to return the file path. This should help make things safer should we ever be given a file name which contains an LF within it (as that would span two lines in the file list, not one). Signed-off-by: Shawn O. Pearce --- git-gui | 72 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/git-gui b/git-gui index 540d56397a..f36594eea0 100755 --- a/git-gui +++ b/git-gui @@ -150,7 +150,7 @@ proc repository_state {hdvar ctvar} { proc update_status {{final Ready.}} { global HEAD PARENT commit_type global ui_index ui_other ui_status_value ui_comm - global status_active file_states + global status_active file_states file_lists global cfg_trust_mtime if {$status_active || ![lock_index read]} return @@ -166,6 +166,7 @@ proc update_status {{final Ready.}} { } array unset file_states + array unset file_lists foreach w [list $ui_index $ui_other] { $w conf -state normal $w delete 0.0 end @@ -196,7 +197,7 @@ proc update_status {{final Ready.}} { proc update_status_stage2 {fd final} { global gitdir PARENT commit_type global ui_index ui_other ui_status_value ui_comm - global status_active file_states + global status_active global buf_rdi buf_rdf buf_rlo if {$fd != {}} { @@ -346,22 +347,40 @@ proc status_eof {fd buf final} { ## diff proc clear_diff {} { - global ui_diff ui_fname_value ui_fstatus_value + global ui_diff ui_fname_value ui_fstatus_value ui_index ui_other $ui_diff conf -state normal $ui_diff delete 0.0 end $ui_diff conf -state disabled + set ui_fname_value {} set ui_fstatus_value {} + + $ui_index tag remove in_diff 0.0 end + $ui_other tag remove in_diff 0.0 end } -proc show_diff {path} { - global file_states PARENT diff_3way diff_active +proc show_diff {path {w {}} {lno {}}} { + global file_states file_lists + global PARENT diff_3way diff_active global ui_diff ui_fname_value ui_fstatus_value ui_status_value if {$diff_active || ![lock_index read]} return clear_diff + if {$w == {} || $lno == {}} { + foreach w [array names file_lists] { + set lno [lsearch -sorted $file_lists($w) $path] + if {$lno >= 0} { + incr lno + break + } + } + } + if {$w != {} && $lno >= 1} { + $w tag add in_diff $lno.0 [expr $lno + 1].0 + } + set s $file_states($path) set m [lindex $s 0] set diff_3way 0 @@ -823,27 +842,6 @@ proc mapdesc {state path} { return $r } -proc bsearch {w path} { - set hi [expr [lindex [split [$w index end] .] 0] - 2] - if {$hi == 0} { - return -1 - } - set lo 0 - while {$lo < $hi} { - set mi [expr [expr $lo + $hi] / 2] - set ti [expr $mi + 1] - set cmp [string compare [$w get $ti.1 $ti.end] $path] - if {$cmp < 0} { - set lo $ti - } elseif {$cmp == 0} { - return $mi - } else { - set hi $mi - } - } - return -[expr $lo + 1] -} - set next_icon_id 0 proc merge_state {path new_state} { @@ -877,7 +875,8 @@ proc merge_state {path new_state} { } proc display_file {path state} { - global ui_index ui_other file_states status_active + global ui_index ui_other + global file_states file_lists status_active set old_m [merge_state $path $state] if {$status_active} return @@ -889,7 +888,7 @@ proc display_file {path state} { set new_icon [mapicon $new_m $path] if {$new_w != $old_w} { - set lno [bsearch $old_w $path] + set lno [lsearch -sorted $file_lists($old_w) $path] if {$lno >= 0} { incr lno $old_w conf -state normal @@ -897,7 +896,10 @@ proc display_file {path state} { $old_w conf -state disabled } - set lno [expr abs([bsearch $new_w $path] + 1) + 1] + lappend file_lists($new_w) $path + set file_lists($new_w) [lsort $file_lists($new_w)] + set lno [lsearch -sorted $file_lists($new_w) $path] + incr lno $new_w conf -state normal $new_w image create $lno.0 \ -align center -padx 5 -pady 1 \ @@ -913,7 +915,7 @@ proc display_file {path state} { } proc display_all_files {} { - global ui_index ui_other file_states + global ui_index ui_other file_states file_lists $ui_index conf -state normal $ui_other conf -state normal @@ -922,6 +924,7 @@ proc display_all_files {} { set s $file_states($path) set m [lindex $s 0] set w [mapcol $m $path] + lappend file_lists($w) $path $w image create end \ -align center -padx 5 -pady 1 \ -name [lindex $s 1] \ @@ -1484,19 +1487,16 @@ proc do_commit {} { # shift == 1: left click # 3: right click proc click {w x y shift wx wy} { - global ui_index ui_other + global ui_index ui_other file_lists set pos [split [$w index @$x,$y] .] set lno [lindex $pos 0] set col [lindex $pos 1] - set path [$w get $lno.1 $lno.end] + set path [lindex $file_lists($w) [expr $lno - 1]] if {$path == {}} return if {$col > 0 && $shift == 1} { - $ui_index tag remove in_diff 0.0 end - $ui_other tag remove in_diff 0.0 end - $w tag add in_diff $lno.0 [expr $lno + 1].0 - show_diff $path + show_diff $path $w $lno } } From 68e009dec44a023efd6f6fcd61c1583b23b04f32 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 17:59:34 -0500 Subject: [PATCH 043/548] git-gui: Correctly handle files containing LF in their name. If we are given a file whose path name contains an LF (\n) we now escape it by inserting the common escape string \n instead of the LF character whenever we display the name in the UI. This way the text fields don't start to span multiple lines just to display one file, and it keeps the line numbers correct within the file lists. Signed-off-by: Shawn O. Pearce --- git-gui | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/git-gui b/git-gui index f36594eea0..ab64684c91 100755 --- a/git-gui +++ b/git-gui @@ -385,9 +385,9 @@ proc show_diff {path {w {}} {lno {}}} { set m [lindex $s 0] set diff_3way 0 set diff_active 1 - set ui_fname_value $path + set ui_fname_value [escape_path $path] set ui_fstatus_value [mapdesc $m $path] - set ui_status_value "Loading diff of $path..." + set ui_status_value "Loading diff of [escape_path $path]..." set cmd [list | git diff-index -p $PARENT -- $path] switch $m { @@ -404,7 +404,7 @@ proc show_diff {path {w {}} {lno {}}} { } err ]} { set diff_active 0 unlock_index - set ui_status_value "Unable to display $path" + set ui_status_value "Unable to display [escape_path $path]" error_popup "Error loading file:\n$err" return } @@ -421,7 +421,7 @@ proc show_diff {path {w {}} {lno {}}} { if {[catch {set fd [open $cmd r]} err]} { set diff_active 0 unlock_index - set ui_status_value "Unable to display $path" + set ui_status_value "Unable to display [escape_path $path]" error_popup "Error loading diff:\n$err" return } @@ -569,7 +569,7 @@ before committing. U* { error_popup "Unmerged files cannot be committed. -File $path has merge conflicts. +File [escape_path $path] has merge conflicts. You must resolve them and include the file before committing. " unlock_index @@ -578,7 +578,7 @@ You must resolve them and include the file before committing. default { error_popup "Unknown file state [lindex $s 0] detected. -File $path cannot be committed by this program. +File [escape_path $path] cannot be committed by this program. " } } @@ -842,6 +842,11 @@ proc mapdesc {state path} { return $r } +proc escape_path {path} { + regsub -all "\n" $path "\\n" path + return $path +} + set next_icon_id 0 proc merge_state {path new_state} { @@ -905,7 +910,7 @@ proc display_file {path state} { -align center -padx 5 -pady 1 \ -name [lindex $s 1] \ -image $new_icon - $new_w insert $lno.1 "$path\n" + $new_w insert $lno.1 "[escape_path $path]\n" $new_w conf -state disabled } elseif {$new_icon != [mapicon $old_m $path]} { $new_w conf -state normal @@ -929,7 +934,7 @@ proc display_all_files {} { -align center -padx 5 -pady 1 \ -name [lindex $s 1] \ -image [mapicon $m $path] - $w insert end "$path\n" + $w insert end "[escape_path $path]\n" } $ui_index conf -state disabled From 7f1df79bb7bad696d1d8965da3937edeb1cfe9f6 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 18:38:00 -0500 Subject: [PATCH 044/548] git-gui: Efficiently update the UI after committing. When we commit we know that whatever was in the index went as part of the commit. Since we generally assume that the user does not update the index except through our user interface we can be reasonably certain that any file which was marked as A/M/D in the index will have had that A/M/D state changed to an _ (not different) by the commit. We can use this knowledge to update the user interface post commit by simply updating the index part of the file state of all files whose index state was A/M/D to _ and then removing any file memory any which wound up with a final state of __ (not different anywhere). Finally we redraw the file lists and update the diff view. Signed-off-by: Shawn O. Pearce --- git-gui | 85 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 29 deletions(-) diff --git a/git-gui b/git-gui index ab64684c91..2c4e97236b 100755 --- a/git-gui +++ b/git-gui @@ -150,7 +150,7 @@ proc repository_state {hdvar ctvar} { proc update_status {{final Ready.}} { global HEAD PARENT commit_type global ui_index ui_other ui_status_value ui_comm - global status_active file_states file_lists + global status_active file_states global cfg_trust_mtime if {$status_active || ![lock_index read]} return @@ -166,12 +166,6 @@ proc update_status {{final Ready.}} { } array unset file_states - array unset file_lists - foreach w [list $ui_index $ui_other] { - $w conf -state normal - $w delete 0.0 end - $w conf -state disabled - } if {![$ui_comm edit modified] || [string trim [$ui_comm get 0.0 end]] == {}} { @@ -319,25 +313,18 @@ proc read_ls_others {fd final} { } proc status_eof {fd buf final} { - global status_active $buf - global ui_fname_value ui_status_value file_states + global status_active ui_status_value + upvar $buf to_clear if {[eof $fd]} { - set $buf {} + set to_clear {} close $fd if {[incr status_active -1] == 0} { - unlock_index - display_all_files + unlock_index + reshow_diff set ui_status_value $final - - if {$ui_fname_value != {} && [array names file_states \ - -exact $ui_fname_value] != {}} { - show_diff $ui_fname_value - } else { - clear_diff - } } } } @@ -360,6 +347,17 @@ proc clear_diff {} { $ui_other tag remove in_diff 0.0 end } +proc reshow_diff {} { + global ui_fname_value ui_status_value file_states + + if {$ui_fname_value != {} && [array names file_states \ + -exact $ui_fname_value] != {}} { + show_diff $ui_fname_value + } else { + clear_diff + } +} + proc show_diff {path {w {}} {lno {}}} { global file_states file_lists global PARENT diff_3way diff_active @@ -562,11 +560,11 @@ before committing. foreach path [array names file_states] { set s $file_states($path) switch -glob -- [lindex $s 0] { - _* {continue} - A* - - D* - - M* {set files_ready 1; break} - U* { + _? {continue} + A? - + D? - + M? {set files_ready 1; break} + U? { error_popup "Unmerged files cannot be committed. File [escape_path $path] has merge conflicts. @@ -635,8 +633,9 @@ A good commit message has the following format: } proc commit_stage2 {fd_wt curHEAD msg} { - global single_commit gitdir PARENT commit_type + global single_commit gitdir HEAD PARENT commit_type global commit_active ui_status_value ui_comm + global file_states gets $fd_wt tree_id close $fd_wt @@ -663,7 +662,7 @@ proc commit_stage2 {fd_wt curHEAD msg} { } close $fd_mh } err]} { - error_popup "Loading MERGE_HEADs failed:\n$err" + error_popup "Loading MERGE_HEAD failed:\n$err" set commit_active 0 set ui_status_value {Commit failed.} unlock_index @@ -723,12 +722,34 @@ proc commit_stage2 {fd_wt curHEAD msg} { if {$single_commit} do_quit - set commit_type {} + # -- Update status without invoking any git commands. + # set commit_active 0 + set commit_type normal set HEAD $cmt_id set PARENT $cmt_id + + foreach path [array names file_states] { + set s $file_states($path) + set m [lindex $s 0] + switch -glob -- $m { + A? - + M? - + D? {set m _[string index $m 1]} + } + + if {$m == {__}} { + unset file_states($path) + } else { + lset file_states($path) 0 $m + } + } + + display_all_files unlock_index - update_status "Changes committed as [string range $cmt_id 0 7]." + reshow_diff + set ui_status_value \ + "Changes committed as [string range $cmt_id 0 7]." } ###################################################################### @@ -925,6 +946,10 @@ proc display_all_files {} { $ui_index conf -state normal $ui_other conf -state normal + $ui_index delete 0.0 end + $ui_other delete 0.0 end + + array unset file_lists foreach path [lsort [array names file_states]] { set s $file_states($path) set m [lindex $s 0] @@ -1506,10 +1531,12 @@ proc click {w x y shift wx wy} { } proc unclick {w x y} { + global file_lists + set pos [split [$w index @$x,$y] .] set lno [lindex $pos 0] set col [lindex $pos 1] - set path [$w get $lno.1 $lno.end] + set path [lindex $file_lists($w) [expr $lno - 1]] if {$path == {}} return if {$col == 0} { From 73ad179bbbba56c053045c555b7e5f1882380533 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 18:42:42 -0500 Subject: [PATCH 045/548] git-gui: Use catch rather than array names to check file. When we reshow the current diff file it can be faster to just fetch the value from the file_states array than it is to ask for all paths whose name exactly matches the one we want to show. This is because [array names -exact] is O(n) in the number of files. Signed-off-by: Shawn O. Pearce --- git-gui | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-gui b/git-gui index 2c4e97236b..e16fcf7346 100755 --- a/git-gui +++ b/git-gui @@ -350,11 +350,11 @@ proc clear_diff {} { proc reshow_diff {} { global ui_fname_value ui_status_value file_states - if {$ui_fname_value != {} && [array names file_states \ - -exact $ui_fname_value] != {}} { - show_diff $ui_fname_value - } else { + if {$ui_fname_value == {} + || [catch {set s $file_states($ui_fname_value)}]} { clear_diff + } else { + show_diff $ui_fname_value } } From 3963678da9e78e87fa9b57f2f3ad75ba1c70e589 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 18:46:52 -0500 Subject: [PATCH 046/548] git-gui: Rename difffont/mainfont variables. I found difffont to be a very awkward varible name, due to the use of three f's in a row. So use easier to read variable names. Signed-off-by: Shawn O. Pearce --- git-gui | 110 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/git-gui b/git-gui index e16fcf7346..e30a114439 100755 --- a/git-gui +++ b/git-gui @@ -1044,17 +1044,17 @@ proc load_all_remotes {} { } proc populate_remote_menu {m pfx op} { - global all_remotes mainfont + global all_remotes font_ui foreach remote $all_remotes { $m add command -label "$pfx $remote..." \ -command [list $op $remote] \ - -font $mainfont + -font $font_ui } } proc populate_pull_menu {m} { - global gitdir repo_config all_remotes mainfont disable_on_lock + global gitdir repo_config all_remotes font_ui disable_on_lock foreach remote $all_remotes { set rb {} @@ -1082,7 +1082,7 @@ proc populate_pull_menu {m} { $m add command \ -label "Branch $rb_short from $remote..." \ -command [list pull_remote $remote $rb] \ - -font $mainfont + -font $font_ui lappend disable_on_lock \ [list $m entryconf [$m index last] -state] } @@ -1212,13 +1212,13 @@ proc error_popup {msg} { } proc show_msg {w top msg} { - global gitdir appname mainfont + global gitdir appname font_ui message $w.m -text $msg -justify left -aspect 400 pack $w.m -side top -fill x -padx 5 -pady 10 button $w.ok -text OK \ -width 15 \ - -font $mainfont \ + -font $font_ui \ -command "destroy $top" pack $w.ok -side bottom bind $top "grab $top; focus $top" @@ -1230,7 +1230,7 @@ proc show_msg {w top msg} { } proc hook_failed_popup {hook msg} { - global gitdir mainfont difffont appname + global gitdir font_ui font_diff appname set w .hookfail toplevel $w @@ -1240,18 +1240,18 @@ proc hook_failed_popup {hook msg} { label $w.m.l1 -text "$hook hook failed:" \ -anchor w \ -justify left \ - -font [concat $mainfont bold] + -font [concat $font_ui bold] text $w.m.t \ -background white -borderwidth 1 \ -relief sunken \ -width 80 -height 10 \ - -font $difffont \ + -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 [concat $mainfont bold] + -font [concat $font_ui bold] 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 @@ -1264,7 +1264,7 @@ proc hook_failed_popup {hook msg} { button $w.ok -text OK \ -width 15 \ - -font $mainfont \ + -font $font_ui \ -command "destroy $w" pack $w.ok -side bottom @@ -1287,7 +1287,7 @@ proc new_console {short_title long_title} { proc console_init {w} { global console_cr console_data - global gitdir appname mainfont difffont + global gitdir appname font_ui font_diff set console_cr($w) 1.0 toplevel $w @@ -1295,17 +1295,17 @@ proc console_init {w} { label $w.m.l1 -text "[lindex $console_data($w) 1]:" \ -anchor w \ -justify left \ - -font [concat $mainfont bold] + -font [concat $font_ui bold] text $w.m.t \ -background white -borderwidth 1 \ -relief sunken \ -width 80 -height 10 \ - -font $difffont \ + -font $font_diff \ -state disabled \ -yscrollcommand [list $w.m.sby set] label $w.m.s -anchor w \ -justify left \ - -font [concat $mainfont bold] + -font [concat $font_ui bold] 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 @@ -1315,7 +1315,7 @@ proc console_init {w} { button $w.ok -text {Running...} \ -width 15 \ - -font $mainfont \ + -font $font_ui \ -state disabled \ -command "destroy $w" pack $w.ok -side bottom @@ -1548,8 +1548,8 @@ proc unclick {w x y} { ## ## ui init -set mainfont {Helvetica 10} -set difffont {Courier 10} +set font_ui {Helvetica 10} +set font_diff {Courier 10} set maincursor [. cget -cursor] switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" { @@ -1573,14 +1573,14 @@ menu .mbar -tearoff 0 menu .mbar.project .mbar.project add command -label Visualize \ -command do_gitk \ - -font $mainfont + -font $font_ui .mbar.project add command -label {Repack Database} \ -command do_repack \ - -font $mainfont + -font $font_ui .mbar.project add command -label Quit \ -command do_quit \ -accelerator $M1T-Q \ - -font $mainfont + -font $font_ui # -- Edit Menu # @@ -1588,61 +1588,61 @@ menu .mbar.edit .mbar.edit add command -label Undo \ -command {catch {[focus] edit undo}} \ -accelerator $M1T-Z \ - -font $mainfont + -font $font_ui .mbar.edit add command -label Redo \ -command {catch {[focus] edit redo}} \ -accelerator $M1T-Y \ - -font $mainfont + -font $font_ui .mbar.edit add separator .mbar.edit add command -label Cut \ -command {catch {tk_textCut [focus]}} \ -accelerator $M1T-X \ - -font $mainfont + -font $font_ui .mbar.edit add command -label Copy \ -command {catch {tk_textCopy [focus]}} \ -accelerator $M1T-C \ - -font $mainfont + -font $font_ui .mbar.edit add command -label Paste \ -command {catch {tk_textPaste [focus]; [focus] see insert}} \ -accelerator $M1T-V \ - -font $mainfont + -font $font_ui .mbar.edit add command -label Delete \ -command {catch {[focus] delete sel.first sel.last}} \ -accelerator Del \ - -font $mainfont + -font $font_ui .mbar.edit add separator .mbar.edit add command -label {Select All} \ -command {catch {[focus] tag add sel 0.0 end}} \ -accelerator $M1T-A \ - -font $mainfont + -font $font_ui # -- Commit Menu menu .mbar.commit .mbar.commit add command -label Rescan \ -command do_rescan \ -accelerator F5 \ - -font $mainfont + -font $font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Amend Last Commit} \ -command do_amend_last \ - -font $mainfont + -font $font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Include All Files} \ -command do_include_all \ -accelerator $M1T-I \ - -font $mainfont + -font $font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Sign Off} \ -command do_signoff \ -accelerator $M1T-S \ - -font $mainfont + -font $font_ui .mbar.commit add command -label Commit \ -command do_commit \ -accelerator $M1T-Return \ - -font $mainfont + -font $font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] @@ -1673,10 +1673,10 @@ pack .vpane -anchor n -side top -fill both -expand 1 frame .vpane.files.index -height 100 -width 400 label .vpane.files.index.title -text {Modified Files} \ -background green \ - -font $mainfont + -font $font_ui text $ui_index -background white -borderwidth 0 \ -width 40 -height 10 \ - -font $mainfont \ + -font $font_ui \ -yscrollcommand {.vpane.files.index.sb set} \ -cursor $maincursor \ -state disabled @@ -1690,10 +1690,10 @@ pack $ui_index -side left -fill both -expand 1 frame .vpane.files.other -height 100 -width 100 label .vpane.files.other.title -text {Untracked Files} \ -background red \ - -font $mainfont + -font $font_ui text $ui_other -background white -borderwidth 0 \ -width 40 -height 10 \ - -font $mainfont \ + -font $font_ui \ -yscrollcommand {.vpane.files.other.sb set} \ -cursor $maincursor \ -state disabled @@ -1703,8 +1703,8 @@ pack .vpane.files.other.sb -side right -fill y pack $ui_other -side left -fill both -expand 1 .vpane.files add .vpane.files.other -sticky nsew -$ui_index tag conf in_diff -font [concat $mainfont bold] -$ui_other tag conf in_diff -font [concat $mainfont bold] +$ui_index tag conf in_diff -font [concat $font_ui bold] +$ui_other tag conf in_diff -font [concat $font_ui bold] # -- Diff and Commit Area frame .vpane.lower -height 400 -width 400 @@ -1719,36 +1719,36 @@ frame .vpane.lower.commarea.buttons label .vpane.lower.commarea.buttons.l -text {} \ -anchor w \ -justify left \ - -font $mainfont + -font $font_ui 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 $mainfont + -font $font_ui 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.amend -text {Amend Last} \ -command do_amend_last \ - -font $mainfont + -font $font_ui pack .vpane.lower.commarea.buttons.amend -side top -fill x lappend disable_on_lock {.vpane.lower.commarea.buttons.amend conf -state} button .vpane.lower.commarea.buttons.incall -text {Include All} \ -command do_include_all \ - -font $mainfont + -font $font_ui 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 $mainfont + -font $font_ui pack .vpane.lower.commarea.buttons.signoff -side top -fill x button .vpane.lower.commarea.buttons.commit -text {Commit} \ -command do_commit \ - -font $mainfont + -font $font_ui pack .vpane.lower.commarea.buttons.commit -side top -fill x lappend disable_on_lock {.vpane.lower.commarea.buttons.commit conf -state} @@ -1759,7 +1759,7 @@ set ui_coml .vpane.lower.commarea.buffer.l label $ui_coml -text {Commit Message:} \ -anchor w \ -justify left \ - -font $mainfont + -font $font_ui trace add variable commit_type write {uplevel #0 { switch -glob $commit_type \ initial {$ui_coml conf -text {Initial Commit Message:}} \ @@ -1773,7 +1773,7 @@ text $ui_comm -background white -borderwidth 1 \ -autoseparators true \ -relief sunken \ -width 75 -height 9 -wrap none \ - -font $difffont \ + -font $font_diff \ -yscrollcommand {.vpane.lower.commarea.buffer.sby set} \ -cursor $maincursor scrollbar .vpane.lower.commarea.buffer.sby -command [list $ui_comm yview] @@ -1788,21 +1788,21 @@ set ui_fstatus_value {} frame .vpane.lower.diff.header -background orange label .vpane.lower.diff.header.l1 -text {File:} \ -background orange \ - -font $mainfont + -font $font_ui label .vpane.lower.diff.header.l2 -textvariable ui_fname_value \ -background orange \ -anchor w \ -justify left \ - -font $mainfont + -font $font_ui label .vpane.lower.diff.header.l3 -text {Status:} \ -background orange \ - -font $mainfont + -font $font_ui label .vpane.lower.diff.header.l4 -textvariable ui_fstatus_value \ -background orange \ -width $max_status_desc \ -anchor w \ -justify left \ - -font $mainfont + -font $font_ui pack .vpane.lower.diff.header.l1 -side left pack .vpane.lower.diff.header.l2 -side left -fill x pack .vpane.lower.diff.header.l4 -side right @@ -1813,7 +1813,7 @@ frame .vpane.lower.diff.body set ui_diff .vpane.lower.diff.body.t text $ui_diff -background white -borderwidth 0 \ -width 80 -height 15 -wrap none \ - -font $difffont \ + -font $font_diff \ -xscrollcommand {.vpane.lower.diff.body.sbx set} \ -yscrollcommand {.vpane.lower.diff.body.sby set} \ -cursor $maincursor \ @@ -1830,10 +1830,10 @@ pack .vpane.lower.diff.body -side bottom -fill both -expand 1 $ui_diff tag conf dm -foreground red $ui_diff tag conf dp -foreground blue -$ui_diff tag conf da -font [concat $difffont bold] +$ui_diff tag conf da -font [concat $font_diff bold] $ui_diff tag conf di -foreground "#00a000" $ui_diff tag conf dni -foreground "#a000a0" -$ui_diff tag conf bold -font [concat $difffont bold] +$ui_diff tag conf bold -font [concat $font_diff bold] # -- Status Bar set ui_status_value {Initializing...} @@ -1842,7 +1842,7 @@ label .status -textvariable ui_status_value \ -justify left \ -borderwidth 1 \ -relief sunken \ - -font $mainfont + -font $font_ui pack .status -anchor w -side bottom -fill x # -- Load geometry From da5239dcab0e8f899a4e6bfb34b574f6286b9dbb Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 19:03:06 -0500 Subject: [PATCH 047/548] git-gui: Use native tk_messageBox for errors. Rather than drawing our own toplevel for error messages we really should just use the the native tk_messageBox command to display any error messages. Major benefits for doing so are: - automatically centers over main window; - less code required on our part in git-gui; - includes a nifty error icon on most systems; - better fits the look-and-feel of the operating system. Signed-off-by: Shawn O. Pearce --- git-gui | 56 +++++++++++++++++++++++++------------------------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/git-gui b/git-gui index e30a114439..81fe38f006 100755 --- a/git-gui +++ b/git-gui @@ -7,6 +7,9 @@ exec wish "$0" -- "$@" # and distributed under the terms of the GNU General Public Licence, # either version 2, or (at your option) any later version. +set appname [lindex [file split $argv0] end] +set gitdir {} + ###################################################################### ## ## config @@ -59,16 +62,30 @@ proc save_my_config {} { } } +proc error_popup {msg} { + global gitdir appname + + set title $appname + if {$gitdir != {}} { + append title { (} + append title [lindex \ + [file split [file normalize [file dirname $gitdir]]] \ + end] + append title {)} + } + tk_messageBox -parent . \ + -icon error \ + -type ok \ + -title "$title: error" \ + -message $msg +} + ###################################################################### ## ## repository setup -set appname [lindex [file split $argv0] end] -set gitdir {} -set GIT_COMMITTER_IDENT {} - if {[catch {set cdup [exec git rev-parse --show-cdup]} err]} { - show_msg {} . "Cannot find the git directory: $err" + error_popup "Cannot find the git directory:\n$err" exit 1 } if {$cdup != ""} { @@ -77,7 +94,7 @@ if {$cdup != ""} { unset cdup if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} { - show_msg {} . "Cannot find the git directory: $err" + error_popup "Cannot find the git directory:\n$err" exit 1 } @@ -1204,31 +1221,6 @@ unset filemask i ## ## util -proc error_popup {msg} { - set w .error - toplevel $w - wm transient $w . - show_msg $w $w $msg -} - -proc show_msg {w top msg} { - global gitdir appname font_ui - - message $w.m -text $msg -justify left -aspect 400 - pack $w.m -side top -fill x -padx 5 -pady 10 - button $w.ok -text OK \ - -width 15 \ - -font $font_ui \ - -command "destroy $top" - pack $w.ok -side bottom - bind $top "grab $top; focus $top" - bind $top "destroy $top" - wm title $w "$appname ([lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end]): error" - tkwait window $top -} - proc hook_failed_popup {hook msg} { global gitdir font_ui font_diff appname @@ -1482,6 +1474,8 @@ proc do_include_all {} { } } +set GIT_COMMITTER_IDENT {} + proc do_signoff {} { global ui_comm GIT_COMMITTER_IDENT From 44be340e4d8d39475e86d3e00cec31fec0d0f6c1 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 19:10:10 -0500 Subject: [PATCH 048/548] git-gui: Cleaned up error message formatting. Added an extra blank line between the first line of each error message and the rest of the message, as usually the rest of the message is coming from Tcl or is the stderr output of a git command we tried to invoke. This makes it easier to read the output (if any). Signed-off-by: Shawn O. Pearce --- git-gui | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/git-gui b/git-gui index 81fe38f006..4449c7d67e 100755 --- a/git-gui +++ b/git-gui @@ -73,7 +73,8 @@ proc error_popup {msg} { end] append title {)} } - tk_messageBox -parent . \ + tk_messageBox \ + -parent . \ -icon error \ -type ok \ -title "$title: error" \ @@ -84,8 +85,10 @@ proc error_popup {msg} { ## ## repository setup -if {[catch {set cdup [exec git rev-parse --show-cdup]} err]} { - error_popup "Cannot find the git directory:\n$err" +if { [catch {set cdup [exec git rev-parse --show-cdup]} err] + || [catch {set gitdir [exec git rev-parse --git-dir]} err]} { + catch {wm withdraw .} + error_popup "Cannot find the git directory:\n\n$err" exit 1 } if {$cdup != ""} { @@ -93,11 +96,6 @@ if {$cdup != ""} { } unset cdup -if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} { - error_popup "Cannot find the git directory:\n$err" - exit 1 -} - if {$appname == {git-citool}} { set single_commit 1 } @@ -420,7 +418,7 @@ proc show_diff {path {w {}} {lno {}}} { set diff_active 0 unlock_index set ui_status_value "Unable to display [escape_path $path]" - error_popup "Error loading file:\n$err" + error_popup "Error loading file:\n\n$err" return } $ui_diff conf -state normal @@ -437,7 +435,7 @@ proc show_diff {path {w {}} {lno {}}} { set diff_active 0 unlock_index set ui_status_value "Unable to display [escape_path $path]" - error_popup "Error loading diff:\n$err" + error_popup "Error loading diff:\n\n$err" return } @@ -523,7 +521,7 @@ proc load_last_commit {} { set msg [string trim [read $fd]] close $fd } err]} { - error_popup "Error loading commit data for amend:\n$err" + error_popup "Error loading commit data for amend:\n\n$err" return } @@ -655,10 +653,8 @@ proc commit_stage2 {fd_wt curHEAD msg} { global file_states gets $fd_wt tree_id - close $fd_wt - - if {$tree_id == {}} { - error_popup "write-tree failed" + if {$tree_id == {} || [catch {close $fd_wt} err]} { + error_popup "write-tree failed:\n\n$err" set commit_active 0 set ui_status_value {Commit failed.} unlock_index @@ -679,7 +675,7 @@ proc commit_stage2 {fd_wt curHEAD msg} { } close $fd_mh } err]} { - error_popup "Loading MERGE_HEAD failed:\n$err" + error_popup "Loading MERGE_HEAD failed:\n\n$err" set commit_active 0 set ui_status_value {Commit failed.} unlock_index @@ -692,7 +688,7 @@ proc commit_stage2 {fd_wt curHEAD msg} { } lappend cmd << $msg if {[catch {set cmt_id [eval exec $cmd]} err]} { - error_popup "commit-tree failed:\n$err" + error_popup "commit-tree failed:\n\n$err" set commit_active 0 set ui_status_value {Commit failed.} unlock_index @@ -713,7 +709,7 @@ proc commit_stage2 {fd_wt curHEAD msg} { } set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD] if {[catch {eval exec $cmd} err]} { - error_popup "update-ref failed:\n$err" + error_popup "update-ref failed:\n\n$err" set commit_active 0 set ui_status_value {Commit failed.} unlock_index @@ -1481,12 +1477,12 @@ proc do_signoff {} { if {$GIT_COMMITTER_IDENT == {}} { if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} { - error_popup "Unable to obtain your identity:\n$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$me" + error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me" return } } From c4fe7728529fd9f3dc2c413ce889d359732cd3a4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 19:32:24 -0500 Subject: [PATCH 049/548] git-gui: Simplified format of geometry configuration. The gui.geometry config value was starting to contain the odd string \\{ as part of its value due to the way the Tcl lists were being supplied to git repo-config. Now we write out only three values: the overall window geomtry, the y position of the horizontal sash, and the x position of the vertical sash. All other data is skipped, which makes the gui.geometry value simpler. While debugging this I noticed that the save_my_config procedure was being invoked multiple times during exit due to do_quit getting invoked over and over again. So now we set a flag in do_quit and don't perform any of our "at exit" type of logic if we've already been through the do_quit procedure once. Signed-off-by: Shawn O. Pearce --- git-gui | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/git-gui b/git-gui index 4449c7d67e..ce49c38987 100755 --- a/git-gui +++ b/git-gui @@ -48,11 +48,9 @@ proc save_my_config {} { set repo_config(gui.trustmtime) [list $cfg_trust_mtime] } - set cfg_geometry [list \ - [wm geometry .] \ - [.vpane sash coord 0] \ - [.vpane.files sash coord 0] \ - ] + set cfg_geometry [wm geometry .] + append cfg_geometry " [lindex [.vpane sash coord 0] 1]" + append cfg_geometry " [lindex [.vpane.files sash coord 0] 0]" if {[catch {set rc_geometry $repo_config(gui.geometry)}]} { set rc_geometry [list [list]] } @@ -1422,8 +1420,13 @@ proc do_repack {} { console_exec $w $cmd } +set quitting 0 + proc do_quit {} { - global gitdir ui_comm + global gitdir ui_comm quitting + + if {$quitting} return + set quitting 1 set save [file join $gitdir GITGUI_MSG] set msg [string trim [$ui_comm get 0.0 end]] @@ -1837,10 +1840,16 @@ pack .status -anchor w -side bottom -fill x # -- Load geometry catch { -wm geometry . [lindex $repo_config(gui.geometry) 0 0] -eval .vpane sash place 0 [lindex $repo_config(gui.geometry) 0 1] -eval .vpane.files sash place 0 [lindex $repo_config(gui.geometry) 0 2] +set gm [lindex $repo_config(gui.geometry) 0] +wm geometry . [lindex $gm 0] +.vpane sash place 0 \ + [lindex [.vpane sash coord 0] 0] \ + [lindex $gm 1] +.vpane.files sash place 0 \ + [lindex $gm 2] \ + [lindex [.vpane.files sash coord 0] 1] } +unset gm # -- Key Bindings bind $ui_comm <$M1B-Key-Return> {do_commit;break} From 390adaeafbefc49a88d06c3a2ad68bc00fe0c614 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 19:40:33 -0500 Subject: [PATCH 050/548] git-gui: Misc. formatting cleanups. A number of lines were line wrapping in a rather ugly way when opened in vim with line numbers enabled, so I split most of these lines over two lines using a sensible wrapping policy. Signed-off-by: Shawn O. Pearce --- git-gui | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/git-gui b/git-gui index ce49c38987..f8c51590b0 100755 --- a/git-gui +++ b/git-gui @@ -197,7 +197,8 @@ proc update_status {{final Ready.}} { set ui_status_value {Refreshing file status...} set fd_rf [open "| git update-index -q --unmerged --refresh" r] fconfigure $fd_rf -blocking 0 -translation binary - fileevent $fd_rf readable [list update_status_stage2 $fd_rf $final] + fileevent $fd_rf readable \ + [list update_status_stage2 $fd_rf $final] } } @@ -402,8 +403,6 @@ proc show_diff {path {w {}} {lno {}}} { set cmd [list | git diff-index -p $PARENT -- $path] switch $m { - AM { - } MM { set cmd [list | git diff-index -p -c $PARENT $path] } @@ -828,7 +827,8 @@ proc post_pull_remote {remote branch success} { set PARENT $HEAD set $ui_status_value {Ready.} } else { - update_status "Conflicts detected while pulling $branch from $remote." + update_status \ + "Conflicts detected while pulling $branch from $remote." } } @@ -1548,7 +1548,7 @@ set maincursor [. cget -cursor] switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" { windows,* {set M1B Control; set M1T Ctrl} unix,Darwin {set M1B M1; set M1T Cmd} -default {set M1B M1; set M1T M1} +* {set M1B M1; set M1T M1} } # -- Menu Bar @@ -1720,19 +1720,22 @@ button .vpane.lower.commarea.buttons.rescan -text {Rescan} \ -command do_rescan \ -font $font_ui pack .vpane.lower.commarea.buttons.rescan -side top -fill x -lappend disable_on_lock {.vpane.lower.commarea.buttons.rescan conf -state} +lappend disable_on_lock \ + {.vpane.lower.commarea.buttons.rescan conf -state} button .vpane.lower.commarea.buttons.amend -text {Amend Last} \ -command do_amend_last \ -font $font_ui pack .vpane.lower.commarea.buttons.amend -side top -fill x -lappend disable_on_lock {.vpane.lower.commarea.buttons.amend conf -state} +lappend disable_on_lock \ + {.vpane.lower.commarea.buttons.amend conf -state} button .vpane.lower.commarea.buttons.incall -text {Include All} \ -command do_include_all \ -font $font_ui pack .vpane.lower.commarea.buttons.incall -side top -fill x -lappend disable_on_lock {.vpane.lower.commarea.buttons.incall conf -state} +lappend disable_on_lock \ + {.vpane.lower.commarea.buttons.incall conf -state} button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \ -command do_signoff \ @@ -1743,7 +1746,8 @@ button .vpane.lower.commarea.buttons.commit -text {Commit} \ -command do_commit \ -font $font_ui pack .vpane.lower.commarea.buttons.commit -side top -fill x -lappend disable_on_lock {.vpane.lower.commarea.buttons.commit conf -state} +lappend disable_on_lock \ + {.vpane.lower.commarea.buttons.commit conf -state} # -- Commit Message Buffer frame .vpane.lower.commarea.buffer @@ -1769,7 +1773,8 @@ text $ui_comm -background white -borderwidth 1 \ -font $font_diff \ -yscrollcommand {.vpane.lower.commarea.buffer.sby set} \ -cursor $maincursor -scrollbar .vpane.lower.commarea.buffer.sby -command [list $ui_comm yview] +scrollbar .vpane.lower.commarea.buffer.sby \ + -command [list $ui_comm yview] pack $ui_coml -side top -fill x pack .vpane.lower.commarea.buffer.sby -side right -fill y pack $ui_comm -side left -fill y @@ -1848,8 +1853,8 @@ wm geometry . [lindex $gm 0] .vpane.files sash place 0 \ [lindex $gm 2] \ [lindex [.vpane.files sash coord 0] 1] -} unset gm +} # -- Key Bindings bind $ui_comm <$M1B-Key-Return> {do_commit;break} From 62aac80b133de4fa004d90d27a97f574aec9d02d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 20:00:35 -0500 Subject: [PATCH 051/548] git-gui: Misc. bug fixes for mouse click crashes. Make sure the file_lists array has both elements set at all times, otherwise we get Tcl errors during mouse clicks in the file list areas due to the list not being defined. Also added M1-A as a keyboard binding within the console window text area. This lets users select all text easily and copy it to the clipboard. Signed-off-by: Shawn O. Pearce --- git-gui | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/git-gui b/git-gui index f8c51590b0..b82e6e629f 100755 --- a/git-gui +++ b/git-gui @@ -960,7 +960,9 @@ proc display_all_files {} { $ui_index delete 0.0 end $ui_other delete 0.0 end - array unset file_lists + set file_lists($ui_index) [list] + set file_lists($ui_other) [list] + foreach path [lsort [array names file_states]] { set s $file_states($path) set m [lindex $s 0] @@ -1273,7 +1275,7 @@ proc new_console {short_title long_title} { proc console_init {w} { global console_cr console_data - global gitdir appname font_ui font_diff + global gitdir appname font_ui font_diff M1B set console_cr($w) 1.0 toplevel $w @@ -1306,6 +1308,8 @@ proc console_init {w} { -command "destroy $w" pack $w.ok -side bottom + 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 ([lindex [file split \ [file normalize [file dirname $gitdir]]] \ @@ -1900,7 +1904,10 @@ foreach i [list $ui_index $ui_other] { bind $i {click %W %x %y 3 %X %Y; break} bind $i {unclick %W %x %y; break} } -unset i M1B M1T +unset i + +set file_lists($ui_index) [list] +set file_lists($ui_other) [list] wm title . "$appname ([file normalize [file dirname $gitdir]])" focus -force $ui_comm From 0e794311833fd4d6f0c3204a5449372284318bda Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 20:24:23 -0500 Subject: [PATCH 052/548] git-gui: Added context menus for consoles and commit message buffer. This change adds a context menu to the commit message buffer providing fast access to the contents of the Edit menu, and to the console text buffer, providing easy ways to copy selections of the buffer or the entire buffer. Signed-off-by: Shawn O. Pearce --- git-gui | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/git-gui b/git-gui index b82e6e629f..c2b5e56838 100755 --- a/git-gui +++ b/git-gui @@ -1301,6 +1301,21 @@ proc console_init {w} { 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" \ + -font $font_ui \ + -command "tk_textCopy $w.m.t" + $w.ctxm add command -label "Select All" \ + -font $font_ui \ + -command "$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 + $w.m.t tag remove sel 0.0 end + " + button $w.ok -text {Running...} \ -width 15 \ -font $font_ui \ @@ -1308,6 +1323,7 @@ proc console_init {w} { -command "destroy $w" pack $w.ok -side bottom + bind $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" @@ -1784,6 +1800,38 @@ pack .vpane.lower.commarea.buffer.sby -side right -fill y pack $ui_comm -side left -fill y pack .vpane.lower.commarea.buffer -side left -fill y +# -- Commit Message Buffer Context Menu +# +menu $ui_comm.ctxm -tearoff 0 +$ui_comm.ctxm add command -label "Cut" \ + -font $font_ui \ + -command "tk_textCut $ui_comm" +$ui_comm.ctxm add command -label "Copy" \ + -font $font_ui \ + -command "tk_textCopy $ui_comm" +$ui_comm.ctxm add command -label "Paste" \ + -font $font_ui \ + -command "tk_textPaste $ui_comm" +$ui_comm.ctxm add command -label "Delete" \ + -font $font_ui \ + -command "$ui_comm delete sel.first sel.last" +$ui_comm.ctxm add separator +$ui_comm.ctxm add command -label "Select All" \ + -font $font_ui \ + -command "$ui_comm tag add sel 0.0 end" +$ui_comm.ctxm add command -label "Copy All" \ + -font $font_ui \ + -command " + $ui_comm tag add sel 0.0 end + tk_textCopy $ui_comm + $ui_comm tag remove sel 0.0 end + " +$ui_comm.ctxm add separator +$ui_comm.ctxm add command -label "Sign Off" \ + -font $font_ui \ + -command do_signoff +bind $ui_comm "tk_popup $ui_comm.ctxm %X %Y" + # -- Diff Header set ui_fname_value {} set ui_fstatus_value {} @@ -1837,6 +1885,24 @@ $ui_diff tag conf di -foreground "#00a000" $ui_diff tag conf dni -foreground "#a000a0" $ui_diff tag conf bold -font [concat $font_diff bold] +# -- Diff Body Context Menu +# +menu $ui_diff.ctxm -tearoff 0 +$ui_diff.ctxm add command -label "Copy" \ + -font $font_ui \ + -command "tk_textCopy $ui_diff" +$ui_diff.ctxm add command -label "Select All" \ + -font $font_ui \ + -command "$ui_diff tag add sel 0.0 end" +$ui_diff.ctxm add command -label "Copy All" \ + -font $font_ui \ + -command " + $ui_diff tag add sel 0.0 end + tk_textCopy $ui_diff + $ui_diff tag remove sel 0.0 end + " +bind $ui_diff "tk_popup $ui_diff.ctxm %X %Y" + # -- Status Bar set ui_status_value {Initializing...} label .status -textvariable ui_status_value \ From 6c6dd01a041e628ce6efb583ea865d1a694ff7b2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 20:33:30 -0500 Subject: [PATCH 053/548] git-gui: Fix mouse cursor behavior when in widgets. The mouse cursor (at least on Windows) seemed to be picking up the cursor from the sash controls and then never resetting itself back to the standard text cursor (the I-beam) when it was over a text area that the user can edit (like the commit buffer) or over a text area the user can copy from (like the diff viewer). So now we always set the cursor to left_ptr (which according to the Tk documentation should be available everywhere) and only for the two text areas which we use to list file names, as the user clicks in these but is not permitted to select text. Signed-off-by: Shawn O. Pearce --- git-gui | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/git-gui b/git-gui index c2b5e56838..9a259c0dcb 100755 --- a/git-gui +++ b/git-gui @@ -1563,7 +1563,7 @@ proc unclick {w x y} { set font_ui {Helvetica 10} set font_diff {Courier 10} -set maincursor [. cget -cursor] +set cursor_ptr left_ptr switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" { windows,* {set M1B Control; set M1T Ctrl} @@ -1690,8 +1690,8 @@ label .vpane.files.index.title -text {Modified Files} \ text $ui_index -background white -borderwidth 0 \ -width 40 -height 10 \ -font $font_ui \ + -cursor $cursor_ptr \ -yscrollcommand {.vpane.files.index.sb set} \ - -cursor $maincursor \ -state disabled scrollbar .vpane.files.index.sb -command [list $ui_index yview] pack .vpane.files.index.title -side top -fill x @@ -1707,8 +1707,8 @@ label .vpane.files.other.title -text {Untracked Files} \ text $ui_other -background white -borderwidth 0 \ -width 40 -height 10 \ -font $font_ui \ + -cursor $cursor_ptr \ -yscrollcommand {.vpane.files.other.sb set} \ - -cursor $maincursor \ -state disabled scrollbar .vpane.files.other.sb -command [list $ui_other yview] pack .vpane.files.other.title -side top -fill x @@ -1791,8 +1791,7 @@ text $ui_comm -background white -borderwidth 1 \ -relief sunken \ -width 75 -height 9 -wrap none \ -font $font_diff \ - -yscrollcommand {.vpane.lower.commarea.buffer.sby set} \ - -cursor $maincursor + -yscrollcommand {.vpane.lower.commarea.buffer.sby set} scrollbar .vpane.lower.commarea.buffer.sby \ -command [list $ui_comm yview] pack $ui_coml -side top -fill x @@ -1866,7 +1865,6 @@ text $ui_diff -background white -borderwidth 0 \ -font $font_diff \ -xscrollcommand {.vpane.lower.diff.body.sbx set} \ -yscrollcommand {.vpane.lower.diff.body.sby set} \ - -cursor $maincursor \ -state disabled scrollbar .vpane.lower.diff.body.sbx -orient horizontal \ -command [list $ui_diff xview] From 1daf1d0c81c7c2623867f7884c8cbc5ff0a10d30 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 20:44:03 -0500 Subject: [PATCH 054/548] git-gui: Teach sign off to be more intelligent. When we sign off on a commit we want to add a blank line between whatever is in the commit buffer and the new Signed-off-by line, unless there already is a Signed-off-by (or Acked-by) tag at the end of the buffer already. This change makes us do the right thing more often. Signed-off-by: Shawn O. Pearce --- git-gui | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/git-gui b/git-gui index 9a259c0dcb..8a42c97cb1 100755 --- a/git-gui +++ b/git-gui @@ -1510,10 +1510,15 @@ proc do_signoff {} { } } - set str "Signed-off-by: $GIT_COMMITTER_IDENT" - if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} { + set sob "Signed-off-by: $GIT_COMMITTER_IDENT" + set last [$ui_comm get {end -1c linestart} {end -1c}] + if {$last != $sob} { $ui_comm edit separator - $ui_comm insert end "\n$str" + if {$last != {} + && ![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 } From 058803f400d8bbd72aa8b8584e9a6e93dbd17d54 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 20:45:52 -0500 Subject: [PATCH 055/548] git-gui: Corrected font used for options menu items. Signed-off-by: Shawn O. Pearce --- git-gui | 1 + 1 file changed, 1 insertion(+) diff --git a/git-gui b/git-gui index 8a42c97cb1..008eeb4460 100755 --- a/git-gui +++ b/git-gui @@ -1677,6 +1677,7 @@ menu .mbar.push menu .mbar.options .mbar.options add checkbutton \ -label {Trust File Modification Timestamps} \ + -font $font_ui \ -offvalue false \ -onvalue true \ -variable cfg_trust_mtime From f019c96add051aa9265c3dba8940c29f307aae81 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 21:02:37 -0500 Subject: [PATCH 056/548] git-gui: Honor system font and let user configure fonts. Rather than hardcoding our fonts to something that I thought was reasonable, guess font_ui off the font used by the system in the menu bar. This way the application conforms by default to whatever the user's desktop is setup to. We also now let the user supply font configuration through their repository configuration as gui.fontui (the overall UI font) and gui.fontdiff (the font used for the diff viewer). Signed-off-by: Shawn O. Pearce --- git-gui | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/git-gui b/git-gui index 008eeb4460..26df169c18 100755 --- a/git-gui +++ b/git-gui @@ -1566,9 +1566,16 @@ proc unclick {w x y} { ## ## ui init -set font_ui {Helvetica 10} -set font_diff {Courier 10} -set cursor_ptr left_ptr +set font_ui {} +set font_diff {} +set cursor_ptr {} +menu .mbar -tearoff 0 +catch {set font_ui [lindex $repo_config(gui.fontui) 0]} +catch {set font_diff [lindex $repo_config(gui.fontdiff) 0]} +if {$font_ui == {}} {catch {set font_ui [.mbar cget -font]}} +if {$font_ui == {}} {set font_ui {Helvetica 10}} +if {$font_diff == {}} {set font_diff {Courier 10}} +if {$cursor_ptr == {}} {set cursor_ptr left_ptr} switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" { windows,* {set M1B Control; set M1T Ctrl} @@ -1577,7 +1584,6 @@ unix,Darwin {set M1B M1; set M1T Cmd} } # -- Menu Bar -menu .mbar -tearoff 0 .mbar add cascade -label Project -menu .mbar.project .mbar add cascade -label Edit -menu .mbar.edit .mbar add cascade -label Commit -menu .mbar.commit From 2c26e6f5279286aa844a54363d877acffb4a4310 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 21:14:28 -0500 Subject: [PATCH 057/548] git-gui: Allow the user to change the diff viewer font size. Because the diff area is one of the most important areas to be able to read users should be able to increase or decrease the size of the font used within that area. Currently we save that back to the global configuration, even if it may have originated from the local repository configuration. This is probably going to be considered to be a bug by at least one user who wants some sort of different font within a given repository, but I'm just going to ignore the problem for now. Signed-off-by: Shawn O. Pearce --- git-gui | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/git-gui b/git-gui index 26df169c18..fdb1dce88d 100755 --- a/git-gui +++ b/git-gui @@ -39,6 +39,7 @@ proc load_repo_config {} { proc save_my_config {} { global repo_config global cfg_trust_mtime + global font_diff if {[catch {set rc_trustMTime $repo_config(gui.trustmtime)}]} { set rc_trustMTime [list false] @@ -48,6 +49,14 @@ proc save_my_config {} { set repo_config(gui.trustmtime) [list $cfg_trust_mtime] } + if {[catch {set rc_fontdiff $repo_config(gui.fontdiff)}]} { + set rc_fontdiff [list {Courier 10}] + } + if {$font_diff != [lindex $rc_fontdiff 0]} { + exec git repo-config --global gui.fontDiff $font_diff + set repo_config(gui.fontdiff) [list $font_diff] + } + set cfg_geometry [wm geometry .] append cfg_geometry " [lindex [.vpane sash coord 0] 1]" append cfg_geometry " [lindex [.vpane.files sash coord 0] 0]" @@ -1890,9 +1899,9 @@ pack .vpane.lower.diff.body -side bottom -fill both -expand 1 $ui_diff tag conf dm -foreground red $ui_diff tag conf dp -foreground blue -$ui_diff tag conf da -font [concat $font_diff bold] $ui_diff tag conf di -foreground "#00a000" $ui_diff tag conf dni -foreground "#a000a0" +$ui_diff tag conf da -font [concat $font_diff bold] $ui_diff tag conf bold -font [concat $font_diff bold] # -- Diff Body Context Menu @@ -1911,6 +1920,23 @@ $ui_diff.ctxm add command -label "Copy All" \ tk_textCopy $ui_diff $ui_diff tag remove sel 0.0 end " +$ui_diff.ctxm add separator +$ui_diff.ctxm add command -label "Decrease Font Size" \ + -font $font_ui \ + -command { + lset font_diff 1 [expr [lindex $font_diff 1] - 1] + $ui_diff configure -font $font_diff + $ui_diff tag conf da -font [concat $font_diff bold] + $ui_diff tag conf bold -font [concat $font_diff bold] + } +$ui_diff.ctxm add command -label "Increase Font Size" \ + -font $font_ui \ + -command { + lset font_diff 1 [expr [lindex $font_diff 1] + 1] + $ui_diff configure -font $font_diff + $ui_diff tag conf da -font [concat $font_diff bold] + $ui_diff tag conf bold -font [concat $font_diff bold] + } bind $ui_diff "tk_popup $ui_diff.ctxm %X %Y" # -- Status Bar From 16403d0b1f9d17bec6bce488356ec8bf84cace48 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 11 Nov 2006 21:52:06 -0500 Subject: [PATCH 058/548] git-gui: Refresh a file if it has an empty diff. When the user has enabled the Trust File Modification Timestamp option then we may display a file as being modified yet that file may not have a difference. When the user clicks on that file we wind up displaying an empty diff viewer, which makes no sense to the user. So instead if we get an empty diff and the user has this option enabled and the file's current state is _M (no change in index but the working file appears modified) then run a quick update-index on just that file and remove it from the list of modified files. We also give the user a quick dialog stating we are removing it, and why. Usually I don't run into this situation when I have the Trust File Modification Timestamp option enabled, so its not that annoying to have a dialog pop open to remind me why there are no differences. Signed-off-by: Shawn O. Pearce --- git-gui | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/git-gui b/git-gui index fdb1dce88d..ae87879bbb 100755 --- a/git-gui +++ b/git-gui @@ -88,6 +88,25 @@ proc error_popup {msg} { -message $msg } +proc info_popup {msg} { + global gitdir appname + + set title $appname + if {$gitdir != {}} { + append title { (} + append title [lindex \ + [file split [file normalize [file dirname $gitdir]]] \ + end] + append title {)} + } + tk_messageBox \ + -parent . \ + -icon error \ + -type ok \ + -title $title \ + -message $msg +} + ###################################################################### ## ## repository setup @@ -204,7 +223,12 @@ proc update_status {{final Ready.}} { } else { set status_active 1 set ui_status_value {Refreshing file status...} - set fd_rf [open "| git update-index -q --unmerged --refresh" r] + 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 update_status_stage2 $fd_rf $final] @@ -381,6 +405,42 @@ proc reshow_diff {} { } } +proc handle_empty_diff {} { + global ui_fname_value file_states file_lists + + set path $ui_fname_value + set s $file_states($path) + if {[lindex $s 0] != {_M}} return + + info_popup "No differences detected. + +[short_path $path] has no changes. + +The modification date of this file was updated by another +application and you currently have the Trust File Modification +Timestamps feature enabled, so Git did not automatically detect +that there are no content differences in this file. + +This file will now be removed from the modified files list, to +prevent possible confusion. +" + if {[catch {exec git update-index -- $path} err]} { + error_popup "Failed to refresh index:\n\n$err" + } + + clear_diff + set old_w [mapcol [lindex $file_states($path) 0] $path] + set lno [lsearch -sorted $file_lists($old_w) $path] + if {$lno >= 0} { + set file_lists($old_w) \ + [lreplace $file_lists($old_w) $lno $lno] + incr lno + $old_w conf -state normal + $old_w delete $lno.0 [expr $lno + 1].0 + $old_w conf -state disabled + } +} + proc show_diff {path {w {}} {lno {}}} { global file_states file_lists global PARENT diff_3way diff_active @@ -451,6 +511,7 @@ proc show_diff {path {w {}} {lno {}}} { proc read_diff {fd} { global ui_diff ui_status_value diff_3way diff_active + global cfg_trust_mtime while {[gets $fd line] >= 0} { if {[string match {diff --git *} $line]} continue @@ -497,6 +558,10 @@ proc read_diff {fd} { set diff_active 0 unlock_index set ui_status_value {Ready.} + + if {$cfg_trust_mtime && [$ui_diff index end] == {2.0}} { + handle_empty_diff + } } } @@ -588,7 +653,7 @@ before committing. U? { error_popup "Unmerged files cannot be committed. -File [escape_path $path] has merge conflicts. +File [short_path $path] has merge conflicts. You must resolve them and include the file before committing. " unlock_index @@ -597,7 +662,7 @@ You must resolve them and include the file before committing. default { error_popup "Unknown file state [lindex $s 0] detected. -File [escape_path $path] cannot be committed by this program. +File [short_path $path] cannot be committed by this program. " } } @@ -888,6 +953,10 @@ proc escape_path {path} { return $path } +proc short_path {path} { + return [escape_path [lindex [file split $path] end]] +} + set next_icon_id 0 proc merge_state {path new_state} { @@ -921,7 +990,6 @@ proc merge_state {path new_state} { } proc display_file {path state} { - global ui_index ui_other global file_states file_lists status_active set old_m [merge_state $path $state] From b4946930fa0f7aa538e33f1d799beffb7e10e7a9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 00:40:38 -0500 Subject: [PATCH 059/548] git-gui: Make use of the Tk font system rather than faking it. The native Tk font system is actually quite powerful and has the nice property that modifications to a named font are immediately reflected throughout all widgets currently displayed. This really saves us from needing to write all of the reconfigure code as part of our font display. I also fixed the way we detect and apply the system font on the main UI widgets as although it worked on a Windows 2000 system it does not work at all on my Mac OS 10.4 system. Signed-off-by: Shawn O. Pearce --- git-gui | 192 +++++++++++++++++++++++++++----------------------------- 1 file changed, 92 insertions(+), 100 deletions(-) diff --git a/git-gui b/git-gui index ae87879bbb..3cbe3e7f72 100755 --- a/git-gui +++ b/git-gui @@ -39,7 +39,6 @@ proc load_repo_config {} { proc save_my_config {} { global repo_config global cfg_trust_mtime - global font_diff if {[catch {set rc_trustMTime $repo_config(gui.trustmtime)}]} { set rc_trustMTime [list false] @@ -49,14 +48,6 @@ proc save_my_config {} { set repo_config(gui.trustmtime) [list $cfg_trust_mtime] } - if {[catch {set rc_fontdiff $repo_config(gui.fontdiff)}]} { - set rc_fontdiff [list {Courier 10}] - } - if {$font_diff != [lindex $rc_fontdiff 0]} { - exec git repo-config --global gui.fontDiff $font_diff - set repo_config(gui.fontdiff) [list $font_diff] - } - set cfg_geometry [wm geometry .] append cfg_geometry " [lindex [.vpane sash coord 0] 1]" append cfg_geometry " [lindex [.vpane.files sash coord 0] 0]" @@ -1134,17 +1125,17 @@ proc load_all_remotes {} { } proc populate_remote_menu {m pfx op} { - global all_remotes font_ui + global all_remotes foreach remote $all_remotes { $m add command -label "$pfx $remote..." \ -command [list $op $remote] \ - -font $font_ui + -font font_ui } } proc populate_pull_menu {m} { - global gitdir repo_config all_remotes font_ui disable_on_lock + global gitdir repo_config all_remotes disable_on_lock foreach remote $all_remotes { set rb {} @@ -1172,7 +1163,7 @@ proc populate_pull_menu {m} { $m add command \ -label "Branch $rb_short from $remote..." \ -command [list pull_remote $remote $rb] \ - -font $font_ui + -font font_ui lappend disable_on_lock \ [list $m entryconf [$m index last] -state] } @@ -1294,8 +1285,15 @@ unset filemask i ## ## util +proc incr_font_size {font {amt 1}} { + set sz [font configure $font -size] + incr sz $amt + font configure $font -size $sz + font configure ${font}bold -size $sz +} + proc hook_failed_popup {hook msg} { - global gitdir font_ui font_diff appname + global gitdir appname set w .hookfail toplevel $w @@ -1305,18 +1303,18 @@ proc hook_failed_popup {hook msg} { label $w.m.l1 -text "$hook hook failed:" \ -anchor w \ -justify left \ - -font [concat $font_ui bold] + -font font_uibold text $w.m.t \ -background white -borderwidth 1 \ -relief sunken \ -width 80 -height 10 \ - -font $font_diff \ + -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 [concat $font_ui bold] + -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 @@ -1329,7 +1327,7 @@ proc hook_failed_popup {hook msg} { button $w.ok -text OK \ -width 15 \ - -font $font_ui \ + -font font_ui \ -command "destroy $w" pack $w.ok -side bottom @@ -1352,7 +1350,7 @@ proc new_console {short_title long_title} { proc console_init {w} { global console_cr console_data - global gitdir appname font_ui font_diff M1B + global gitdir appname M1B set console_cr($w) 1.0 toplevel $w @@ -1360,17 +1358,17 @@ proc console_init {w} { label $w.m.l1 -text "[lindex $console_data($w) 1]:" \ -anchor w \ -justify left \ - -font [concat $font_ui bold] + -font font_uibold text $w.m.t \ -background white -borderwidth 1 \ -relief sunken \ -width 80 -height 10 \ - -font $font_diff \ + -font font_diff \ -state disabled \ -yscrollcommand [list $w.m.sby set] label $w.m.s -anchor w \ -justify left \ - -font [concat $font_ui bold] + -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 @@ -1380,13 +1378,13 @@ proc console_init {w} { menu $w.ctxm -tearoff 0 $w.ctxm add command -label "Copy" \ - -font $font_ui \ + -font font_ui \ -command "tk_textCopy $w.m.t" $w.ctxm add command -label "Select All" \ - -font $font_ui \ + -font font_ui \ -command "$w.m.t tag add sel 0.0 end" $w.ctxm add command -label "Copy All" \ - -font $font_ui \ + -font font_ui \ -command " $w.m.t tag add sel 0.0 end tk_textCopy $w.m.t @@ -1395,7 +1393,7 @@ proc console_init {w} { button $w.ok -text {Running...} \ -width 15 \ - -font $font_ui \ + -font font_ui \ -state disabled \ -command "destroy $w" pack $w.ok -side bottom @@ -1643,16 +1641,19 @@ proc unclick {w x y} { ## ## ui init -set font_ui {} -set font_diff {} -set cursor_ptr {} -menu .mbar -tearoff 0 -catch {set font_ui [lindex $repo_config(gui.fontui) 0]} -catch {set font_diff [lindex $repo_config(gui.fontdiff) 0]} -if {$font_ui == {}} {catch {set font_ui [.mbar cget -font]}} -if {$font_ui == {}} {set font_ui {Helvetica 10}} -if {$font_diff == {}} {set font_diff {Courier 10}} -if {$cursor_ptr == {}} {set cursor_ptr left_ptr} +set cursor_ptr left_ptr +font create font_diff -family Courier -size 10 +font create font_ui +catch { + label .dummy + eval font configure font_ui [font actual [.dummy cget -font]] + destroy .dummy +} + +eval font create font_uibold [font configure font_ui] +font configure font_uibold -weight bold +eval font create font_diffbold [font configure font_diff] +font configure font_diffbold -weight bold switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" { windows,* {set M1B Control; set M1T Ctrl} @@ -1661,6 +1662,7 @@ unix,Darwin {set M1B M1; set M1T Cmd} } # -- Menu Bar +menu .mbar -tearoff 0 .mbar add cascade -label Project -menu .mbar.project .mbar add cascade -label Edit -menu .mbar.edit .mbar add cascade -label Commit -menu .mbar.commit @@ -1674,14 +1676,14 @@ unix,Darwin {set M1B M1; set M1T Cmd} menu .mbar.project .mbar.project add command -label Visualize \ -command do_gitk \ - -font $font_ui + -font font_ui .mbar.project add command -label {Repack Database} \ -command do_repack \ - -font $font_ui + -font font_ui .mbar.project add command -label Quit \ -command do_quit \ -accelerator $M1T-Q \ - -font $font_ui + -font font_ui # -- Edit Menu # @@ -1689,61 +1691,61 @@ menu .mbar.edit .mbar.edit add command -label Undo \ -command {catch {[focus] edit undo}} \ -accelerator $M1T-Z \ - -font $font_ui + -font font_ui .mbar.edit add command -label Redo \ -command {catch {[focus] edit redo}} \ -accelerator $M1T-Y \ - -font $font_ui + -font font_ui .mbar.edit add separator .mbar.edit add command -label Cut \ -command {catch {tk_textCut [focus]}} \ -accelerator $M1T-X \ - -font $font_ui + -font font_ui .mbar.edit add command -label Copy \ -command {catch {tk_textCopy [focus]}} \ -accelerator $M1T-C \ - -font $font_ui + -font font_ui .mbar.edit add command -label Paste \ -command {catch {tk_textPaste [focus]; [focus] see insert}} \ -accelerator $M1T-V \ - -font $font_ui + -font font_ui .mbar.edit add command -label Delete \ -command {catch {[focus] delete sel.first sel.last}} \ -accelerator Del \ - -font $font_ui + -font font_ui .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 + -font font_ui # -- Commit Menu menu .mbar.commit .mbar.commit add command -label Rescan \ -command do_rescan \ -accelerator F5 \ - -font $font_ui + -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Amend Last Commit} \ -command do_amend_last \ - -font $font_ui + -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Include All Files} \ -command do_include_all \ -accelerator $M1T-I \ - -font $font_ui + -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Sign Off} \ -command do_signoff \ -accelerator $M1T-S \ - -font $font_ui + -font font_ui .mbar.commit add command -label Commit \ -command do_commit \ -accelerator $M1T-Return \ - -font $font_ui + -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] @@ -1760,7 +1762,7 @@ menu .mbar.push menu .mbar.options .mbar.options add checkbutton \ -label {Trust File Modification Timestamps} \ - -font $font_ui \ + -font font_ui \ -offvalue false \ -onvalue true \ -variable cfg_trust_mtime @@ -1775,10 +1777,10 @@ pack .vpane -anchor n -side top -fill both -expand 1 frame .vpane.files.index -height 100 -width 400 label .vpane.files.index.title -text {Modified Files} \ -background green \ - -font $font_ui + -font font_ui text $ui_index -background white -borderwidth 0 \ -width 40 -height 10 \ - -font $font_ui \ + -font font_ui \ -cursor $cursor_ptr \ -yscrollcommand {.vpane.files.index.sb set} \ -state disabled @@ -1792,10 +1794,10 @@ pack $ui_index -side left -fill both -expand 1 frame .vpane.files.other -height 100 -width 100 label .vpane.files.other.title -text {Untracked Files} \ -background red \ - -font $font_ui + -font font_ui text $ui_other -background white -borderwidth 0 \ -width 40 -height 10 \ - -font $font_ui \ + -font font_ui \ -cursor $cursor_ptr \ -yscrollcommand {.vpane.files.other.sb set} \ -state disabled @@ -1805,8 +1807,8 @@ pack .vpane.files.other.sb -side right -fill y pack $ui_other -side left -fill both -expand 1 .vpane.files add .vpane.files.other -sticky nsew -$ui_index tag conf in_diff -font [concat $font_ui bold] -$ui_other tag conf in_diff -font [concat $font_ui bold] +$ui_index tag conf in_diff -font font_uibold +$ui_other tag conf in_diff -font font_uibold # -- Diff and Commit Area frame .vpane.lower -height 400 -width 400 @@ -1821,39 +1823,39 @@ frame .vpane.lower.commarea.buttons label .vpane.lower.commarea.buttons.l -text {} \ -anchor w \ -justify left \ - -font $font_ui + -font font_ui 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 + -font font_ui 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.amend -text {Amend Last} \ -command do_amend_last \ - -font $font_ui + -font font_ui pack .vpane.lower.commarea.buttons.amend -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.amend conf -state} button .vpane.lower.commarea.buttons.incall -text {Include All} \ -command do_include_all \ - -font $font_ui + -font font_ui 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 + -font font_ui pack .vpane.lower.commarea.buttons.signoff -side top -fill x button .vpane.lower.commarea.buttons.commit -text {Commit} \ -command do_commit \ - -font $font_ui + -font font_ui pack .vpane.lower.commarea.buttons.commit -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.commit conf -state} @@ -1865,7 +1867,7 @@ set ui_coml .vpane.lower.commarea.buffer.l label $ui_coml -text {Commit Message:} \ -anchor w \ -justify left \ - -font $font_ui + -font font_ui trace add variable commit_type write {uplevel #0 { switch -glob $commit_type \ initial {$ui_coml conf -text {Initial Commit Message:}} \ @@ -1879,7 +1881,7 @@ text $ui_comm -background white -borderwidth 1 \ -autoseparators true \ -relief sunken \ -width 75 -height 9 -wrap none \ - -font $font_diff \ + -font font_diff \ -yscrollcommand {.vpane.lower.commarea.buffer.sby set} scrollbar .vpane.lower.commarea.buffer.sby \ -command [list $ui_comm yview] @@ -1892,23 +1894,23 @@ pack .vpane.lower.commarea.buffer -side left -fill y # menu $ui_comm.ctxm -tearoff 0 $ui_comm.ctxm add command -label "Cut" \ - -font $font_ui \ + -font font_ui \ -command "tk_textCut $ui_comm" $ui_comm.ctxm add command -label "Copy" \ - -font $font_ui \ + -font font_ui \ -command "tk_textCopy $ui_comm" $ui_comm.ctxm add command -label "Paste" \ - -font $font_ui \ + -font font_ui \ -command "tk_textPaste $ui_comm" $ui_comm.ctxm add command -label "Delete" \ - -font $font_ui \ + -font font_ui \ -command "$ui_comm delete sel.first sel.last" $ui_comm.ctxm add separator $ui_comm.ctxm add command -label "Select All" \ - -font $font_ui \ + -font font_ui \ -command "$ui_comm tag add sel 0.0 end" $ui_comm.ctxm add command -label "Copy All" \ - -font $font_ui \ + -font font_ui \ -command " $ui_comm tag add sel 0.0 end tk_textCopy $ui_comm @@ -1916,7 +1918,7 @@ $ui_comm.ctxm add command -label "Copy All" \ " $ui_comm.ctxm add separator $ui_comm.ctxm add command -label "Sign Off" \ - -font $font_ui \ + -font font_ui \ -command do_signoff bind $ui_comm "tk_popup $ui_comm.ctxm %X %Y" @@ -1926,21 +1928,21 @@ set ui_fstatus_value {} frame .vpane.lower.diff.header -background orange label .vpane.lower.diff.header.l1 -text {File:} \ -background orange \ - -font $font_ui + -font font_ui label .vpane.lower.diff.header.l2 -textvariable ui_fname_value \ -background orange \ -anchor w \ -justify left \ - -font $font_ui + -font font_ui label .vpane.lower.diff.header.l3 -text {Status:} \ -background orange \ - -font $font_ui + -font font_ui label .vpane.lower.diff.header.l4 -textvariable ui_fstatus_value \ -background orange \ -width $max_status_desc \ -anchor w \ -justify left \ - -font $font_ui + -font font_ui pack .vpane.lower.diff.header.l1 -side left pack .vpane.lower.diff.header.l2 -side left -fill x pack .vpane.lower.diff.header.l4 -side right @@ -1951,7 +1953,7 @@ frame .vpane.lower.diff.body set ui_diff .vpane.lower.diff.body.t text $ui_diff -background white -borderwidth 0 \ -width 80 -height 15 -wrap none \ - -font $font_diff \ + -font font_diff \ -xscrollcommand {.vpane.lower.diff.body.sbx set} \ -yscrollcommand {.vpane.lower.diff.body.sby set} \ -state disabled @@ -1967,22 +1969,22 @@ pack .vpane.lower.diff.body -side bottom -fill both -expand 1 $ui_diff tag conf dm -foreground red $ui_diff tag conf dp -foreground blue -$ui_diff tag conf di -foreground "#00a000" -$ui_diff tag conf dni -foreground "#a000a0" -$ui_diff tag conf da -font [concat $font_diff bold] -$ui_diff tag conf bold -font [concat $font_diff bold] +$ui_diff tag conf di -foreground {#00a000} +$ui_diff tag conf dni -foreground {#a000a0} +$ui_diff tag conf da -font font_diffbold +$ui_diff tag conf bold -font font_diffbold # -- Diff Body Context Menu # menu $ui_diff.ctxm -tearoff 0 $ui_diff.ctxm add command -label "Copy" \ - -font $font_ui \ + -font font_ui \ -command "tk_textCopy $ui_diff" $ui_diff.ctxm add command -label "Select All" \ - -font $font_ui \ + -font font_ui \ -command "$ui_diff tag add sel 0.0 end" $ui_diff.ctxm add command -label "Copy All" \ - -font $font_ui \ + -font font_ui \ -command " $ui_diff tag add sel 0.0 end tk_textCopy $ui_diff @@ -1990,21 +1992,11 @@ $ui_diff.ctxm add command -label "Copy All" \ " $ui_diff.ctxm add separator $ui_diff.ctxm add command -label "Decrease Font Size" \ - -font $font_ui \ - -command { - lset font_diff 1 [expr [lindex $font_diff 1] - 1] - $ui_diff configure -font $font_diff - $ui_diff tag conf da -font [concat $font_diff bold] - $ui_diff tag conf bold -font [concat $font_diff bold] - } + -font font_ui \ + -command {incr_font_size font_diff -1} $ui_diff.ctxm add command -label "Increase Font Size" \ - -font $font_ui \ - -command { - lset font_diff 1 [expr [lindex $font_diff 1] + 1] - $ui_diff configure -font $font_diff - $ui_diff tag conf da -font [concat $font_diff bold] - $ui_diff tag conf bold -font [concat $font_diff bold] - } + -font font_ui \ + -command {incr_font_size font_diff 1} bind $ui_diff "tk_popup $ui_diff.ctxm %X %Y" # -- Status Bar @@ -2014,7 +2006,7 @@ label .status -textvariable ui_status_value \ -justify left \ -borderwidth 1 \ -relief sunken \ - -font $font_ui + -font font_ui pack .status -anchor w -side bottom -fill x # -- Load geometry From 16fccd7a1111c1fca6ce973ddaff690188e742d0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 02:22:21 -0500 Subject: [PATCH 060/548] git-gui: Improve right click context menu binding on all platforms. Apparently doesn't work on my single button PowerBook mouse under Mac OS X. I'm guessing this is because Tk is stealing every event and doesn't realize that Control-Button-1 is actually supposed to invoke the context menu on this platform. So now we have a utility procedure is_MacOSX to guess if we are running on a Mac OS X system, and if so setup Control-Button-1 to also activate what Button-3 should have. This does mean that I need to stay away from using Control-Button-1 as a binding in any other context. Of course we should use $M1B for that, which is M1 (aka Command) on Mac OS X so that shouldn't prove to be a problem. Signed-off-by: Shawn O. Pearce --- git-gui | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/git-gui b/git-gui index 3cbe3e7f72..040581e829 100755 --- a/git-gui +++ b/git-gui @@ -1285,6 +1285,23 @@ unset filemask i ## ## util +proc is_MacOSX {} { + global tcl_platform tk_library + if {$tcl_platform(platform) == {unix} + && $tcl_platform(os) == {Darwin} + && [string match /Library/Frameworks/* $tk_library]} { + return 1 + } + return 0 +} + +proc bind_button3 {w cmd} { + bind $w $cmd + if {[is_MacOSX]} { + bind $w $cmd + } +} + proc incr_font_size {font {amt 1}} { set sz [font configure $font -size] incr sz $amt @@ -1398,7 +1415,7 @@ proc console_init {w} { -command "destroy $w" pack $w.ok -side bottom - bind $w.m.t "tk_popup $w.ctxm %X %Y" + 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" @@ -1655,10 +1672,14 @@ font configure font_uibold -weight bold eval font create font_diffbold [font configure font_diff] font configure font_diffbold -weight bold -switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" { -windows,* {set M1B Control; set M1T Ctrl} -unix,Darwin {set M1B M1; set M1T Cmd} -* {set M1B M1; set M1T M1} +set M1B M1 +set M1T M1 +if {$tcl_platform(platform) == {windows}} { + set M1B Control + set M1T Ctrl +} elseif {[is_MacOSX]} { + set M1B M1 + set M1T Cmd } # -- Menu Bar @@ -1920,7 +1941,7 @@ $ui_comm.ctxm add separator $ui_comm.ctxm add command -label "Sign Off" \ -font font_ui \ -command do_signoff -bind $ui_comm "tk_popup $ui_comm.ctxm %X %Y" +bind_button3 $ui_comm "tk_popup $ui_comm.ctxm %X %Y" # -- Diff Header set ui_fname_value {} @@ -1997,7 +2018,7 @@ $ui_diff.ctxm add command -label "Decrease Font Size" \ $ui_diff.ctxm add command -label "Increase Font Size" \ -font font_ui \ -command {incr_font_size font_diff 1} -bind $ui_diff "tk_popup $ui_diff.ctxm %X %Y" +bind_button3 $ui_diff "tk_popup $ui_diff.ctxm %X %Y" # -- Status Bar set ui_status_value {Initializing...} @@ -2063,8 +2084,8 @@ bind all <$M1B-Key-w> {destroy [winfo toplevel %W]} bind all <$M1B-Key-W> {destroy [winfo toplevel %W]} foreach i [list $ui_index $ui_other] { bind $i {click %W %x %y 1 %X %Y; break} - bind $i {click %W %x %y 3 %X %Y; break} bind $i {unclick %W %x %y; break} + bind_button3 $i {click %W %x %y 3 %X %Y; break} } unset i From b5834d70fe970ccf955378d4f6e60a51a94bbaaf Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 02:27:28 -0500 Subject: [PATCH 061/548] git-gui: Rename quitting global to is_quitting. This is a boolean value; naming it as such is a good thing. Signed-off-by: Shawn O. Pearce --- git-gui | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-gui b/git-gui index 040581e829..73227d594a 100755 --- a/git-gui +++ b/git-gui @@ -1532,13 +1532,13 @@ proc do_repack {} { console_exec $w $cmd } -set quitting 0 +set is_quitting 0 proc do_quit {} { - global gitdir ui_comm quitting + global gitdir ui_comm is_quitting - if {$quitting} return - set quitting 1 + if {$is_quitting} return + set is_quitting 1 set save [file join $gitdir GITGUI_MSG] set msg [string trim [$ui_comm get 0.0 end]] From 00f949fbd831bda29dc909baf4a21d00a7c2c119 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 02:30:02 -0500 Subject: [PATCH 062/548] git-gui: Use arrow cursor rather than left_ptr. Arrow is available on all Tk platforms and is mapped to the native system cursor on Windows and Mac OS X. Consequently its the better cursor choice as it should match whatever the system has configured for the standard pointing thingy. Signed-off-by: Shawn O. Pearce --- git-gui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-gui b/git-gui index 73227d594a..29877e4150 100755 --- a/git-gui +++ b/git-gui @@ -1658,7 +1658,7 @@ proc unclick {w x y} { ## ## ui init -set cursor_ptr left_ptr +set cursor_ptr arrow font create font_diff -family Courier -size 10 font create font_ui catch { From 51f4d16b1d8e2e709f28a4e52b951236950cb1d5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 03:47:00 -0500 Subject: [PATCH 063/548] git-gui: Refactor options menu into an options dialog. I decided that the options menu was going to turn into a mess after a while as I start to add additional features to git-gui. The better approach would be to create a dialog that lets the user edit the options, including their --global options. We also wisely let the user press Cancel (or destroy the window) to abort any sort of option editing session, without the options being changed. Signed-off-by: Shawn O. Pearce --- git-gui | 204 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 158 insertions(+), 46 deletions(-) diff --git a/git-gui b/git-gui index 29877e4150..520ec1efff 100755 --- a/git-gui +++ b/git-gui @@ -14,49 +14,90 @@ set gitdir {} ## ## config -proc load_repo_config {} { - global repo_config - global cfg_trust_mtime +set default_config(gui.trustmtime) false +proc is_many_config {name} { + switch -glob -- $name { + remote.*.fetch - + remote.*.push + {return 1} + * + {return 0} + } +} + +proc load_config {} { + global repo_config global_config default_config + + array unset global_config array unset repo_config + catch { + set fd_rc [open "| git repo-config --global --list" r] + while {[gets $fd_rc line] >= 0} { + if {[regexp {^([^=]+)=(.*)$} $line line name value]} { + if {[is_many_config $name]} { + lappend global_config($name) $value + } else { + set global_config($name) $value + } + } + } + close $fd_rc + } catch { set fd_rc [open "| git repo-config --list" r] while {[gets $fd_rc line] >= 0} { if {[regexp {^([^=]+)=(.*)$} $line line name value]} { - lappend repo_config($name) $value + if {[is_many_config $name]} { + lappend repo_config($name) $value + } else { + set repo_config($name) $value + } } } close $fd_rc } - if {[catch {set cfg_trust_mtime \ - [lindex $repo_config(gui.trustmtime) 0] - }]} { - set cfg_trust_mtime false + foreach name [array names default_config] { + if {[catch {set v $global_config($name)}]} { + set global_config($name) $default_config($name) + } + if {[catch {set v $repo_config($name)}]} { + set repo_config($name) $default_config($name) + } } } -proc save_my_config {} { - global repo_config - global cfg_trust_mtime +proc save_config {} { + global repo_config global_config default_config + global repo_config_new global_config_new - if {[catch {set rc_trustMTime $repo_config(gui.trustmtime)}]} { - set rc_trustMTime [list false] - } - if {$cfg_trust_mtime != [lindex $rc_trustMTime 0]} { - exec git repo-config gui.trustMTime $cfg_trust_mtime - set repo_config(gui.trustmtime) [list $cfg_trust_mtime] + foreach name [array names global_config_new] { + set value $global_config_new($name) + if {$value != $global_config($name)} { + if {$value == $default_config($name)} { + catch {exec git repo-config --global --unset $name} + } else { + catch {exec git repo-config --global $name $value} + } + set global_config($name) $value + if {$value == $repo_config($name)} { + catch {exec git repo-config --unset $name} + set repo_config($name) $value + } + } } - set cfg_geometry [wm geometry .] - append cfg_geometry " [lindex [.vpane sash coord 0] 1]" - append cfg_geometry " [lindex [.vpane.files sash coord 0] 0]" - if {[catch {set rc_geometry $repo_config(gui.geometry)}]} { - set rc_geometry [list [list]] - } - if {$cfg_geometry != [lindex $rc_geometry 0]} { - exec git repo-config gui.geometry $cfg_geometry - set repo_config(gui.geometry) [list $cfg_geometry] + foreach name [array names repo_config_new] { + set value $repo_config_new($name) + if {$value != $repo_config($name)} { + if {$value == $global_config($name)} { + catch {exec git repo-config --unset $name} + } else { + catch {exec git repo-config $name $value} + } + set repo_config($name) $value + } } } @@ -117,7 +158,7 @@ if {$appname == {git-citool}} { set single_commit 1 } -load_repo_config +load_config ###################################################################### ## @@ -183,7 +224,7 @@ proc update_status {{final Ready.}} { global HEAD PARENT commit_type global ui_index ui_other ui_status_value ui_comm global status_active file_states - global cfg_trust_mtime + global repo_config if {$status_active || ![lock_index read]} return @@ -209,7 +250,7 @@ proc update_status {{final Ready.}} { $ui_comm edit reset } - if {$cfg_trust_mtime == {true}} { + if {$repo_config(gui.trustmtime) == {true}} { update_status_stage2 {} $final } else { set status_active 1 @@ -409,7 +450,7 @@ proc handle_empty_diff {} { The modification date of this file was updated by another application and you currently have the Trust File Modification -Timestamps feature enabled, so Git did not automatically detect +Timestamps option enabled, so Git did not automatically detect that there are no content differences in this file. This file will now be removed from the modified files list, to @@ -502,7 +543,7 @@ proc show_diff {path {w {}} {lno {}}} { proc read_diff {fd} { global ui_diff ui_status_value diff_3way diff_active - global cfg_trust_mtime + global repo_config while {[gets $fd line] >= 0} { if {[string match {diff --git *} $line]} continue @@ -550,7 +591,8 @@ proc read_diff {fd} { unlock_index set ui_status_value {Ready.} - if {$cfg_trust_mtime && [$ui_diff index end] == {2.0}} { + if {$repo_config(gui.trustmtime) == {true} + && [$ui_diff index end] == {2.0}} { handle_empty_diff } } @@ -1314,7 +1356,6 @@ proc hook_failed_popup {hook msg} { set w .hookfail toplevel $w - wm transient $w . frame $w.m label $w.m.l1 -text "$hook hook failed:" \ @@ -1535,11 +1576,13 @@ proc do_repack {} { set is_quitting 0 proc do_quit {} { - global gitdir ui_comm is_quitting + global gitdir ui_comm is_quitting repo_config if {$is_quitting} return set is_quitting 1 + # -- Stash our current commit buffer. + # set save [file join $gitdir GITGUI_MSG] set msg [string trim [$ui_comm get 0.0 end]] if {[$ui_comm edit modified] && $msg != {}} { @@ -1552,7 +1595,19 @@ proc do_quit {} { file delete $save } - save_my_config + # -- Stash our current window geometry into this repository. + # + set cfg_geometry [list] + lappend cfg_geometry [wm geometry .] + lappend cfg_geometry [lindex [.vpane sash coord 0] 1] + lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0] + if {[catch {set rc_geometry $repo_config(gui.geometry)}]} { + set rc_geometry {} + } + if {$cfg_geometry != $rc_geometry} { + catch {exec git repo-config gui.geometry $cfg_geometry} + } + destroy . } @@ -1624,6 +1679,69 @@ proc do_commit {} { commit_tree } +proc do_options {} { + global appname gitdir + global repo_config global_config + global repo_config_new global_config_new + + load_config + array unset repo_config_new + array unset global_config_new + foreach name [array names repo_config] { + 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 + + label $w.header -text "$appname Options" \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.save -text Save \ + -font font_ui \ + -command "save_config; destroy $w" + pack $w.buttons.save -side right + button $w.buttons.cancel -text {Cancel} \ + -font font_ui \ + -command "destroy $w" + pack $w.buttons.cancel -side right + pack $w.buttons -side bottom -anchor e -pady 10 -padx 10 + + labelframe $w.repo -text {This Repository} \ + -relief raised -borderwidth 2 + labelframe $w.global -text {Global (All Repositories)} \ + -relief raised -borderwidth 2 + 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 + + foreach option { + {trustmtime {Trust File Modification Timestamps}} + } { + set name [lindex $option 0] + set text [lindex $option 1] + foreach f {repo global} { + checkbutton $w.$f.$name -text $text \ + -variable ${f}_config_new(gui.$name) \ + -onvalue true \ + -offvalue false \ + -font font_ui + pack $w.$f.$name -side top -anchor w + } + } + + bind $w "grab $w; focus $w" + bind $w "destroy $w" + wm title $w "$appname ([lindex [file split \ + [file normalize [file dirname $gitdir]]] \ + end]): Options" + tkwait window $w +} + # shift == 1: left click # 3: right click proc click {w x y shift wx wy} { @@ -1690,7 +1808,6 @@ menu .mbar -tearoff 0 .mbar add cascade -label Fetch -menu .mbar.fetch .mbar add cascade -label Pull -menu .mbar.pull .mbar add cascade -label Push -menu .mbar.push -.mbar add cascade -label Options -menu .mbar.options . configure -menu .mbar # -- Project Menu @@ -1739,6 +1856,10 @@ menu .mbar.edit -command {catch {[focus] tag add sel 0.0 end}} \ -accelerator $M1T-A \ -font font_ui +.mbar.edit add separator +.mbar.edit add command -label {Options...} \ + -command do_options \ + -font font_ui # -- Commit Menu menu .mbar.commit @@ -1779,15 +1900,6 @@ menu .mbar.pull # -- Push Menu menu .mbar.push -# -- Options Menu -menu .mbar.options -.mbar.options add checkbutton \ - -label {Trust File Modification Timestamps} \ - -font font_ui \ - -offvalue false \ - -onvalue true \ - -variable cfg_trust_mtime - # -- Main Window Layout panedwindow .vpane -orient vertical panedwindow .vpane.files -orient horizontal @@ -2032,7 +2144,7 @@ pack .status -anchor w -side bottom -fill x # -- Load geometry catch { -set gm [lindex $repo_config(gui.geometry) 0] +set gm $repo_config(gui.geometry) wm geometry . [lindex $gm 0] .vpane sash place 0 \ [lindex [.vpane sash coord 0] 0] \ From 92148d8091d148e219b88e4d555a386ffa78f575 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 05:27:00 -0500 Subject: [PATCH 064/548] git-gui: Allow the user to manipulate the fonts from the options panel. This turned out to take a lot more time than I thought it would take; but now users can edit the main UI font and the diff/fixed with font by changing both the family name and/or the point size of the text. We save the complete Tk font specification to the user's ~/.gitconfig file upon saving options. This is probably more verbose than it needs to be as there are many useless options recorded (e.g. -overstrike 0) that a user won't really want to use in this application. Signed-off-by: Shawn O. Pearce --- git-gui | 140 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 124 insertions(+), 16 deletions(-) diff --git a/git-gui b/git-gui index 520ec1efff..931a959307 100755 --- a/git-gui +++ b/git-gui @@ -14,8 +14,6 @@ set gitdir {} ## ## config -set default_config(gui.trustmtime) false - proc is_many_config {name} { switch -glob -- $name { remote.*.fetch - @@ -69,10 +67,25 @@ proc load_config {} { } proc save_config {} { - global repo_config global_config default_config + global default_config font_descs + global repo_config global_config global repo_config_new global_config_new - foreach name [array names 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 != $global_config($name)} { if {$value == $default_config($name)} { @@ -88,7 +101,7 @@ proc save_config {} { } } - foreach name [array names repo_config_new] { + foreach name [array names default_config] { set value $repo_config_new($name) if {$value != $repo_config($name)} { if {$value == $global_config($name)} { @@ -158,8 +171,6 @@ if {$appname == {git-citool}} { set single_commit 1 } -load_config - ###################################################################### ## ## task management @@ -1680,7 +1691,7 @@ proc do_commit {} { } proc do_options {} { - global appname gitdir + global appname gitdir font_descs global repo_config global_config global repo_config_new global_config_new @@ -1702,19 +1713,25 @@ proc do_options {} { pack $w.header -side top -fill x frame $w.buttons + button $w.buttons.restore -text {Restore Defaults} \ + -font font_ui \ + -command do_restore_defaults + pack $w.buttons.restore -side left button $w.buttons.save -text Save \ -font font_ui \ - -command "save_config; destroy $w" + -command [list do_save_config $w] pack $w.buttons.save -side right button $w.buttons.cancel -text {Cancel} \ -font font_ui \ - -command "destroy $w" + -command [list destroy $w] pack $w.buttons.cancel -side right - pack $w.buttons -side bottom -anchor e -pady 10 -padx 10 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 labelframe $w.repo -text {This Repository} \ + -font font_ui \ -relief raised -borderwidth 2 labelframe $w.global -text {Global (All Repositories)} \ + -font font_ui \ -relief raised -borderwidth 2 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 @@ -1734,6 +1751,33 @@ proc do_options {} { } } + 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:" -font font_ui + 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 \ + -font font_ui + 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" bind $w "destroy $w" wm title $w "$appname ([lindex [file split \ @@ -1742,6 +1786,38 @@ proc do_options {} { tkwait window $w } +proc do_restore_defaults {} { + global font_descs default_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($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" + } + destroy $w +} + # shift == 1: left click # 3: right click proc click {w x y shift wx wy} { @@ -1774,7 +1850,7 @@ proc unclick {w x y} { ###################################################################### ## -## ui init +## config defaults set cursor_ptr arrow font create font_diff -family Courier -size 10 @@ -1785,10 +1861,8 @@ catch { destroy .dummy } -eval font create font_uibold [font configure font_ui] -font configure font_uibold -weight bold -eval font create font_diffbold [font configure font_diff] -font configure font_diffbold -weight bold +font create font_uibold +font create font_diffbold set M1B M1 set M1T M1 @@ -1800,6 +1874,40 @@ if {$tcl_platform(platform) == {windows}} { set M1T Cmd } +proc apply_config {} { + global repo_config font_descs + + foreach option $font_descs { + set name [lindex $option 0] + set font [lindex $option 1] + if {[catch { + foreach {cn cv} $repo_config(gui.$name) { + font configure $font $cn $cv + } + } err]} { + error_popup "Invalid font specified in gui.$name:\n\n$err" + } + foreach {cn cv} [font configure $font] { + font configure ${font}bold $cn $cv + } + font configure ${font}bold -weight bold + } +} + +set default_config(gui.trustmtime) false +set default_config(gui.fontui) [font configure font_ui] +set default_config(gui.fontdiff) [font configure font_diff] +set font_descs { + {fontui font_ui {Main Font}} + {fontdiff font_diff {Diff/Console Font}} +} +load_config +apply_config + +###################################################################### +## +## ui construction + # -- Menu Bar menu .mbar -tearoff 0 .mbar add cascade -label Project -menu .mbar.project From 74e6b12f5831a8c93a5d3c90118a73e908d0680a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 06:35:14 -0500 Subject: [PATCH 065/548] git-gui: Supply progress feedback when running update-index. The git-update-index process can take a while to process a large number of files; for example my laptop would probably need almost an hour to chug through 20,000 modified files. In these incredibly large cases the user should be given at least some feedback to let them know the application is still working on their behalf, even if it won't them do anything else (as the index is locked). Signed-off-by: Shawn O. Pearce --- git-gui | 139 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 77 insertions(+), 62 deletions(-) diff --git a/git-gui b/git-gui index 931a959307..456f53fdc0 100755 --- a/git-gui +++ b/git-gui @@ -178,9 +178,7 @@ if {$appname == {git-citool}} { set single_commit 0 set status_active 0 set diff_active 0 -set update_active 0 set commit_active 0 -set update_index_fd {} set disable_on_lock [list] set index_lock_type none @@ -1100,55 +1098,73 @@ proc display_all_files {} { $ui_other conf -state disabled } -proc with_update_index {body} { - global update_index_fd +proc update_index {pathList} { + global update_index_cp ui_status_value - if {$update_index_fd == {}} { - if {![lock_index update]} return - set update_index_fd [open \ - "| git update-index --add --remove -z --stdin" \ - w] - fconfigure $update_index_fd -translation binary - uplevel 1 $body - close $update_index_fd - set update_index_fd {} - unlock_index - } else { - uplevel 1 $body - } + if {![lock_index update]} return + + set update_index_cp 0 + set totalCnt [llength $pathList] + set batch [expr {int($totalCnt * .01) + 1}] + if {$batch > 25} {set batch 25} + + set ui_status_value "Including files ... 0/$totalCnt 0%" + set ui_status_value [format \ + "Including files ... %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 -translation binary + fileevent $fd writable [list \ + write_update_index \ + $fd \ + $pathList \ + $totalCnt \ + $batch \ + ] } -proc update_index {path} { - global update_index_fd - - if {$update_index_fd == {}} { - error {not in with_update_index} - } else { - puts -nonewline $update_index_fd "$path\0" - } -} - -proc toggle_mode {path} { +proc write_update_index {fd pathList totalCnt batch} { + global update_index_cp ui_status_value global file_states ui_fname_value - set s $file_states($path) - set m [lindex $s 0] - - switch -- $m { - AM - - _O {set new A*} - _M - - MM {set new M*} - AD - - _D {set new D*} - default {return} + if {$update_index_cp >= $totalCnt} { + close $fd + unlock_index + set ui_status_value {Ready.} + return } - with_update_index {update_index $path} - display_file $path $new - if {$ui_fname_value == $path} { - show_diff $path + 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 -- [lindex $file_states($path) 0] { + AM - + _O {set new A*} + _M - + MM {set new M*} + AD - + _D {set new D*} + default {continue} + } + + puts -nonewline $fd $path + puts -nonewline $fd "\0" + display_file $path $new + if {$ui_fname_value == $path} { + show_diff $path + } } + + set ui_status_value [format \ + "Including files ... %i/%i files (%.2f%%)" \ + $update_index_cp \ + $totalCnt \ + [expr {100.0 * $update_index_cp / $totalCnt}]] } ###################################################################### @@ -1627,27 +1643,25 @@ proc do_rescan {} { } proc do_include_all {} { - global update_active ui_status_value + global file_states - if {$update_active || ![lock_index begin-update]} return + if {![lock_index begin-update]} return - set update_active 1 - set ui_status_value {Including all modified files...} - after 1 { - with_update_index { - foreach path [array names file_states] { - set s $file_states($path) - set m [lindex $s 0] - switch -- $m { - AM - - MM - - _M - - _D {toggle_mode $path} - } - } + set pathList [list] + foreach path [array names file_states] { + set s $file_states($path) + set m [lindex $s 0] + switch -- $m { + AM - + MM - + _M - + _D {lappend pathList $path} } - set update_active 0 - set ui_status_value {Ready.} + } + if {$pathList == {}} { + unlock_index + } else { + update_index $pathList } } @@ -1844,7 +1858,7 @@ proc unclick {w x y} { if {$path == {}} return if {$col == 0} { - toggle_mode $path + update_index [list $path] } } @@ -2318,4 +2332,5 @@ load_all_remotes populate_remote_menu .mbar.fetch From fetch_from populate_remote_menu .mbar.push To push_to populate_pull_menu .mbar.pull +tkwait visibility . update_status From e01b42211cad31b1262d152b7e2561cb8bf218ed Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 06:46:26 -0500 Subject: [PATCH 066/548] git-gui: Minor options dialog UI cleanups. Display the name of "this" repository rather than the quite ambiguous string "This". The idea is that seeing the name of the directory the repository is stored in should help jog the user's memory about what they are setting options for. Also place the options dialog immediately over the git-gui main window when it gets opened. This way the user isn't scrolling very far away to gain access to the window. At least on my Mac OS X system not doing this makes the options dialog open rather far away, thus requiring lots of mouse activity to reach it. Signed-off-by: Shawn O. Pearce --- git-gui | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/git-gui b/git-gui index 456f53fdc0..580110e629 100755 --- a/git-gui +++ b/git-gui @@ -1718,9 +1718,13 @@ proc do_options {} { foreach name [array names global_config] { set global_config_new($name) $global_config($name) } + set reponame [lindex [file split \ + [file normalize [file dirname $gitdir]]] \ + end] set w .options_editor toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" label $w.header -text "$appname Options" \ -font font_uibold @@ -1741,7 +1745,7 @@ proc do_options {} { pack $w.buttons.cancel -side right pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.repo -text {This Repository} \ + labelframe $w.repo -text "$reponame Repository" \ -font font_ui \ -relief raised -borderwidth 2 labelframe $w.global -text {Global (All Repositories)} \ @@ -1794,9 +1798,7 @@ proc do_options {} { bind $w "grab $w; focus $w" bind $w "destroy $w" - wm title $w "$appname ([lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end]): Options" + wm title $w "$appname ($reponame): Options" tkwait window $w } From 8009dcdc8d9bee0b5aab1f6e860a834ffbb0b08f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 06:53:56 -0500 Subject: [PATCH 067/548] git-gui: Added Options... menu item to end of diff context menu. Since the font name can only be chosen from within the options dialog giving the user fast access to this dialog from within a context menu that already talks about increasing and decreasing the font size may help users to locate the font name setting as well. Signed-off-by: Shawn O. Pearce --- git-gui | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/git-gui b/git-gui index 580110e629..f959cb6706 100755 --- a/git-gui +++ b/git-gui @@ -2068,7 +2068,7 @@ $ui_index tag conf in_diff -font font_uibold $ui_other tag conf in_diff -font font_uibold # -- Diff and Commit Area -frame .vpane.lower -height 400 -width 400 +frame .vpane.lower -height 300 -width 400 frame .vpane.lower.commarea frame .vpane.lower.diff -relief sunken -borderwidth 1 pack .vpane.lower.commarea -side top -fill x @@ -2254,6 +2254,9 @@ $ui_diff.ctxm add command -label "Decrease Font Size" \ $ui_diff.ctxm add command -label "Increase Font Size" \ -font font_ui \ -command {incr_font_size font_diff 1} +$ui_diff.ctxm add command -label {Options...} \ + -font font_ui \ + -command do_options bind_button3 $ui_diff "tk_popup $ui_diff.ctxm %X %Y" # -- Status Bar From 4af2c384eaae62300765e205c705c7741dd7dd31 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 15:04:15 -0500 Subject: [PATCH 068/548] git-gui: Use 'after 1' to post UI rather than tkwait. The tkwait visibility command and Windows doesn't seem to realize the window is visible, consequently we are never finishing our initialization by calling update_status. Signed-off-by: Shawn O. Pearce --- git-gui | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/git-gui b/git-gui index f959cb6706..2138d2d2e1 100755 --- a/git-gui +++ b/git-gui @@ -2337,5 +2337,4 @@ load_all_remotes populate_remote_menu .mbar.fetch From fetch_from populate_remote_menu .mbar.push To push_to populate_pull_menu .mbar.pull -tkwait visibility . -update_status +after 1 update_status From 7b64d0b7d62ec0eb6e8b37f0be2e62f2a719de16 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 15:45:35 -0500 Subject: [PATCH 069/548] git-gui: Correct bugs in font config handling. Apparently Tcl is being helpful on Windows during exec and is throwing a \ in front of every { it finds in the string. I'm guessing they think the value might be read by another Tcl program? Anyway, Git faithfully stores the \{ sequence and sends it back that way to Tcl, at which point Tcl parses the list wrong and starts to break it in the middle of any element which contains spaces. Therefore a list such as: -family {Times New Roman} gets broken up into the pairs: {-family \{Times} {New Roman} which is very incorrect. So now we replace all { and } with "", at which point Tcl doesn't throw \ in front of the " on the way out to Git yet it reads it correctly as a list on the way back in. I also found and fixed a bug in the way we restored the fonts when the user presses Restore Defaults in the options dialog. Signed-off-by: Shawn O. Pearce --- git-gui | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/git-gui b/git-gui index 2138d2d2e1..e59d225cb6 100755 --- a/git-gui +++ b/git-gui @@ -91,7 +91,8 @@ proc save_config {} { if {$value == $default_config($name)} { catch {exec git repo-config --global --unset $name} } else { - catch {exec git repo-config --global $name $value} + regsub -all "\[{}\]" $value {"} value + exec git repo-config --global $name $value } set global_config($name) $value if {$value == $repo_config($name)} { @@ -107,7 +108,8 @@ proc save_config {} { if {$value == $global_config($name)} { catch {exec git repo-config --unset $name} } else { - catch {exec git repo-config $name $value} + regsub -all "\[{}\]" $value {"} value + exec git repo-config $name $value } set repo_config($name) $value } @@ -1803,7 +1805,7 @@ proc do_options {} { } proc do_restore_defaults {} { - global font_descs default_config + global font_descs default_config repo_config global repo_config_new global_config_new foreach name [array names default_config] { @@ -1813,7 +1815,7 @@ proc do_restore_defaults {} { foreach option $font_descs { set name [lindex $option 0] - set repo_config($name) $default_config(gui.$name) + set repo_config(gui.$name) $default_config(gui.$name) } apply_config From 4ccdab028263dfdeb0adf9764466bb3a43265395 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 16:20:36 -0500 Subject: [PATCH 070/548] git-gui: Hide non-commit related commands when invoked as git-citool. If the user is invoking us as git-citool then they want to perform a single commit and exit quickly. Since we are about to be a very short lived process we should do what we can to avoid spending CPU time setting up menus which the user will never use, like the fetch/push/pull menus. Signed-off-by: Shawn O. Pearce --- git-gui | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/git-gui b/git-gui index e59d225cb6..249b2c894a 100755 --- a/git-gui +++ b/git-gui @@ -169,6 +169,7 @@ if {$cdup != ""} { } unset cdup +set single_commit 0 if {$appname == {git-citool}} { set single_commit 1 } @@ -177,7 +178,6 @@ if {$appname == {git-citool}} { ## ## task management -set single_commit 0 set status_active 0 set diff_active 0 set commit_active 0 @@ -1931,9 +1931,11 @@ menu .mbar -tearoff 0 .mbar add cascade -label Project -menu .mbar.project .mbar add cascade -label Edit -menu .mbar.edit .mbar add cascade -label Commit -menu .mbar.commit -.mbar add cascade -label Fetch -menu .mbar.fetch -.mbar add cascade -label Pull -menu .mbar.pull -.mbar add cascade -label Push -menu .mbar.push +if {!$single_commit} { + .mbar add cascade -label Fetch -menu .mbar.fetch + .mbar add cascade -label Pull -menu .mbar.pull + .mbar add cascade -label Push -menu .mbar.push +} . configure -menu .mbar # -- Project Menu @@ -1941,9 +1943,11 @@ menu .mbar.project .mbar.project add command -label Visualize \ -command do_gitk \ -font font_ui -.mbar.project add command -label {Repack Database} \ - -command do_repack \ - -font font_ui +if {!$single_commit} { + .mbar.project add command -label {Repack Database} \ + -command do_repack \ + -font font_ui +} .mbar.project add command -label Quit \ -command do_quit \ -accelerator $M1T-Q \ @@ -2017,14 +2021,16 @@ lappend disable_on_lock \ lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] -# -- Fetch Menu -menu .mbar.fetch +if {!$single_commit} { + # -- Fetch Menu + menu .mbar.fetch -# -- Pull Menu -menu .mbar.pull + # -- Pull Menu + menu .mbar.pull -# -- Push Menu -menu .mbar.push + # -- Push Menu + menu .mbar.push +} # -- Main Window Layout panedwindow .vpane -orient vertical @@ -2335,8 +2341,10 @@ set file_lists($ui_other) [list] wm title . "$appname ([file normalize [file dirname $gitdir]])" focus -force $ui_comm -load_all_remotes -populate_remote_menu .mbar.fetch From fetch_from -populate_remote_menu .mbar.push To push_to -populate_pull_menu .mbar.pull +if {!$single_commit} { + load_all_remotes + populate_remote_menu .mbar.fetch From fetch_from + populate_remote_menu .mbar.push To push_to + populate_pull_menu .mbar.pull +} after 1 update_status From 6bbd1cb95aede2991d0748d2a54088f2c1291602 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 16:24:52 -0500 Subject: [PATCH 071/548] git-gui: Don't load the global options unless necessary. Since git-repo-config will supply us a union of both the global and the local repository configuration data when we invoke it during startup there is no reason to go get the global configuration with an extra call to repo-config unless the user is trying to view & edit all options in the options dialog. Since skipping this extra repo-config invocation save us a little bit of time its nice to be able to avoid it when we are invoked as git-citool and won't be running very long. Signed-off-by: Shawn O. Pearce --- git-gui | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/git-gui b/git-gui index 249b2c894a..fbb3090ed1 100755 --- a/git-gui +++ b/git-gui @@ -24,24 +24,27 @@ proc is_many_config {name} { } } -proc load_config {} { +proc load_config {include_global} { global repo_config global_config default_config array unset global_config - array unset repo_config - catch { - set fd_rc [open "| git repo-config --global --list" r] - while {[gets $fd_rc line] >= 0} { - if {[regexp {^([^=]+)=(.*)$} $line line name value]} { - if {[is_many_config $name]} { - lappend global_config($name) $value - } else { - set global_config($name) $value + if {$include_global} { + catch { + set fd_rc [open "| git repo-config --global --list" r] + while {[gets $fd_rc line] >= 0} { + if {[regexp {^([^=]+)=(.*)$} $line line name value]} { + if {[is_many_config $name]} { + lappend global_config($name) $value + } else { + set global_config($name) $value + } } } + close $fd_rc } - close $fd_rc } + + array unset repo_config catch { set fd_rc [open "| git repo-config --list" r] while {[gets $fd_rc line] >= 0} { @@ -1711,7 +1714,7 @@ proc do_options {} { global repo_config global_config global repo_config_new global_config_new - load_config + load_config 1 array unset repo_config_new array unset global_config_new foreach name [array names repo_config] { @@ -1919,7 +1922,7 @@ set font_descs { {fontui font_ui {Main Font}} {fontdiff font_diff {Diff/Console Font}} } -load_config +load_config 0 apply_config ###################################################################### From ebf336b9422302ec89bce14b83017060d7ccd3e6 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 16:53:19 -0500 Subject: [PATCH 072/548] git-gui: Allow the user to disable diff stat summary during pull. Because the pull diffstat summary can take as long as the pull itself some users may just choose to disable the summary and save themselves an extra few seconds during each pull. This is especially true if the user really doesn't care about the other files being modified, as due to their project organizational structure they aren't really responsible for their content. This adds an option to the options panel which lets the user disable the diffstat summary (and thus we pass --no-summary to git-pull) but there does appear to be a bug in the config saving code where we did not set the local repo config differently from the global config. Signed-off-by: Shawn O. Pearce --- git-gui | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/git-gui b/git-gui index fbb3090ed1..761ce7551f 100755 --- a/git-gui +++ b/git-gui @@ -896,8 +896,7 @@ proc fetch_from {remote} { } proc pull_remote {remote branch} { - global HEAD commit_type - global file_states + global HEAD commit_type file_states repo_config if {![lock_index update]} return @@ -933,6 +932,9 @@ Commit or throw away all changes before starting a pull operation. set w [new_console "pull $remote $branch" \ "Pulling new changes from branch $branch in $remote"] set cmd [list git pull] + if {$repo_config(gui.pullsummary) == {false}} { + lappend cmd --no-summary + } lappend cmd $remote lappend cmd $branch console_exec $w $cmd [list post_pull_remote $remote $branch] @@ -1760,6 +1762,7 @@ proc do_options {} { pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 foreach option { + {pullsummary {Show Pull Summary}} {trustmtime {Trust File Modification Timestamps}} } { set name [lindex $option 0] @@ -1916,6 +1919,7 @@ proc apply_config {} { } set default_config(gui.trustmtime) false +set default_config(gui.pullsummary) true set default_config(gui.fontui) [font configure font_ui] set default_config(gui.fontdiff) [font configure font_diff] set font_descs { From 4658b56fce4e8b7c4489ae7b0fe9ecf1e236b70b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 17:58:08 -0500 Subject: [PATCH 073/548] git-gui: Run the pre-commit hook in the background. I started to notice on Windows that commits took a lot longer to get going than on my Mac OS X system. The real reason is the repositories that I'm testing with on Windows all enabled the standard pre-commit hook while my test repository on Mac OS X doesn't have it executable (so its not running). So the Windows repositories are spending this lag time running that hook. Now we run the pre-commit hook in the background, allowing the UI to update and tell the user we are busy doing things. Signed-off-by: Shawn O. Pearce --- git-gui | 55 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/git-gui b/git-gui index 761ce7551f..bee17de4dd 100755 --- a/git-gui +++ b/git-gui @@ -664,8 +664,8 @@ proc load_last_commit {} { proc commit_tree {} { global tcl_platform HEAD gitdir commit_type file_states - global commit_active ui_status_value - global ui_comm + global commit_active pch_error + global ui_status_value ui_comm if {$commit_active || ![lock_index update]} return @@ -739,33 +739,64 @@ A good commit message has the following format: return } + set commit_active 1 + # -- Ask the pre-commit hook for the go-ahead. # set pchook [file join $gitdir hooks pre-commit] if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} { - set pchook [list sh -c \ - "if test -x \"$pchook\"; then exec \"$pchook\"; fi"] + set pchook [list sh -c [concat \ + "if test -x \"$pchook\";" \ + "then exec \"$pchook\" 2>&1;" \ + "fi"]] } elseif {[file executable $pchook]} { - set pchook [list $pchook] + set pchook [list $pchook |& cat] } else { set pchook {} } - if {$pchook != {} && [catch {eval exec $pchook} err]} { - hook_failed_popup pre-commit $err - unlock_index + if {$pchook != {}} { + 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_stage1 $fd_ph $curHEAD $msg] + } else { + commit_stage2 $curHEAD $msg + } +} + +proc commit_stage1 {fd_ph curHEAD msg} { + global commit_active 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 + set commit_active 0 + set pch_error {} + return + } + commit_stage2 $curHEAD $msg return } + fconfigure $fd_ph -blocking 0 +} + +proc commit_stage2 {curHEAD msg} { + global ui_status_value # -- Write the tree in the background. # - set commit_active 1 set ui_status_value {Committing changes...} - set fd_wt [open "| git write-tree" r] - fileevent $fd_wt readable [list commit_stage2 $fd_wt $curHEAD $msg] + fileevent $fd_wt readable [list commit_stage3 $fd_wt $curHEAD $msg] } -proc commit_stage2 {fd_wt curHEAD msg} { +proc commit_stage3 {fd_wt curHEAD msg} { global single_commit gitdir HEAD PARENT commit_type global commit_active ui_status_value ui_comm global file_states From 333b0c74b33fe54f0489813950345edbeb62c0b5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 18:03:19 -0500 Subject: [PATCH 074/548] git-gui: Remove the commit_active global variable. We were originally trying to use $commit_active to tell us if there was a commit currently in progress, just so we didn't attempt to start a second (parallel) one by mistake. But really the index lock handles this for us as it won't let us lock the index if it is already locked for update. So this can't happen. Signed-off-by: Shawn O. Pearce --- git-gui | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/git-gui b/git-gui index bee17de4dd..3ccf0a41de 100755 --- a/git-gui +++ b/git-gui @@ -183,7 +183,6 @@ if {$appname == {git-citool}} { set status_active 0 set diff_active 0 -set commit_active 0 set disable_on_lock [list] set index_lock_type none @@ -664,10 +663,10 @@ proc load_last_commit {} { proc commit_tree {} { global tcl_platform HEAD gitdir commit_type file_states - global commit_active pch_error + global pch_error global ui_status_value ui_comm - if {$commit_active || ![lock_index update]} return + if {![lock_index update]} return # -- Our in memory state should match the repository. # @@ -739,8 +738,6 @@ A good commit message has the following format: return } - set commit_active 1 - # -- Ask the pre-commit hook for the go-ahead. # set pchook [file join $gitdir hooks pre-commit] @@ -767,7 +764,7 @@ A good commit message has the following format: } proc commit_stage1 {fd_ph curHEAD msg} { - global commit_active pch_error ui_status_value + global pch_error ui_status_value append pch_error [read $fd_ph] fconfigure $fd_ph -blocking 1 @@ -776,14 +773,13 @@ proc commit_stage1 {fd_ph curHEAD msg} { set ui_status_value {Commit declined by pre-commit hook.} hook_failed_popup pre-commit $pch_error unlock_index - set commit_active 0 - set pch_error {} - return + } else { + commit_stage2 $curHEAD $msg } - commit_stage2 $curHEAD $msg - return + set pch_error {} + } else { + fconfigure $fd_ph -blocking 0 } - fconfigure $fd_ph -blocking 0 } proc commit_stage2 {curHEAD msg} { @@ -798,13 +794,12 @@ proc commit_stage2 {curHEAD msg} { proc commit_stage3 {fd_wt curHEAD msg} { global single_commit gitdir HEAD PARENT commit_type - global commit_active ui_status_value ui_comm + global ui_status_value ui_comm global file_states gets $fd_wt tree_id if {$tree_id == {} || [catch {close $fd_wt} err]} { error_popup "write-tree failed:\n\n$err" - set commit_active 0 set ui_status_value {Commit failed.} unlock_index return @@ -825,7 +820,6 @@ proc commit_stage3 {fd_wt curHEAD msg} { close $fd_mh } err]} { error_popup "Loading MERGE_HEAD failed:\n\n$err" - set commit_active 0 set ui_status_value {Commit failed.} unlock_index return @@ -838,7 +832,6 @@ proc commit_stage3 {fd_wt curHEAD msg} { lappend cmd << $msg if {[catch {set cmt_id [eval exec $cmd]} err]} { error_popup "commit-tree failed:\n\n$err" - set commit_active 0 set ui_status_value {Commit failed.} unlock_index return @@ -859,7 +852,6 @@ proc commit_stage3 {fd_wt curHEAD msg} { 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" - set commit_active 0 set ui_status_value {Commit failed.} unlock_index return @@ -886,7 +878,6 @@ proc commit_stage3 {fd_wt curHEAD msg} { # -- Update status without invoking any git commands. # - set commit_active 0 set commit_type normal set HEAD $cmt_id set PARENT $cmt_id From c8ebafd84537473bb8a53880a6a6740d723b83bc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 18:08:10 -0500 Subject: [PATCH 075/548] git-gui: Added post-commit invocation after the commit is done. Since git-commit.sh invokes hooks/post-commit after running git rerere we should do the same if its available and executable. Signed-off-by: Shawn O. Pearce --- git-gui | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/git-gui b/git-gui index 3ccf0a41de..7eeff9061c 100755 --- a/git-gui +++ b/git-gui @@ -793,7 +793,7 @@ proc commit_stage2 {curHEAD msg} { } proc commit_stage3 {fd_wt curHEAD msg} { - global single_commit gitdir HEAD PARENT commit_type + global single_commit gitdir HEAD PARENT commit_type tcl_platform global ui_status_value ui_comm global file_states @@ -870,6 +870,21 @@ proc commit_stage3 {fd_wt curHEAD msg} { catch {exec git rerere} } + # -- Run the post-commit hook. + # + set pchook [file join $gitdir hooks post-commit] + if {$tcl_platform(platform) == {windows} && [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 != {}} { + catch {exec $pchook &} + } + $ui_comm delete 0.0 end $ui_comm edit modified false $ui_comm edit reset From 043f701116c30067c2a7096135b6e419dd1f7b47 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 18:16:45 -0500 Subject: [PATCH 076/548] git-gui: Always use eq/ne for string comparsions. This is one of those stupid Tcl mistakes that an experienced Tcl programmer just wouldn't make. We should always use eq and ne to compare string values (and never == or !=) as when we use ==/!= Tcl will attempt to convert either side to numeric if one of the two sides looks like a numeric. This could cause some trouble if a file named "1" exists and a different file named "1.0" also exists; their paths are equal according to == but not according to eq. Signed-off-by: Shawn O. Pearce --- git-gui | 140 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/git-gui b/git-gui index 7eeff9061c..05ef8e2e33 100755 --- a/git-gui +++ b/git-gui @@ -90,15 +90,15 @@ proc save_config {} { foreach name [array names default_config] { set value $global_config_new($name) - if {$value != $global_config($name)} { - if {$value == $default_config($name)} { + if {$value ne $global_config($name)} { + if {$value eq $default_config($name)} { catch {exec git repo-config --global --unset $name} } else { regsub -all "\[{}\]" $value {"} value exec git repo-config --global $name $value } set global_config($name) $value - if {$value == $repo_config($name)} { + if {$value eq $repo_config($name)} { catch {exec git repo-config --unset $name} set repo_config($name) $value } @@ -107,8 +107,8 @@ proc save_config {} { foreach name [array names default_config] { set value $repo_config_new($name) - if {$value != $repo_config($name)} { - if {$value == $global_config($name)} { + if {$value ne $repo_config($name)} { + if {$value eq $global_config($name)} { catch {exec git repo-config --unset $name} } else { regsub -all "\[{}\]" $value {"} value @@ -123,7 +123,7 @@ proc error_popup {msg} { global gitdir appname set title $appname - if {$gitdir != {}} { + if {$gitdir ne {}} { append title { (} append title [lindex \ [file split [file normalize [file dirname $gitdir]]] \ @@ -142,7 +142,7 @@ proc info_popup {msg} { global gitdir appname set title $appname - if {$gitdir != {}} { + if {$gitdir ne {}} { append title { (} append title [lindex \ [file split [file normalize [file dirname $gitdir]]] \ @@ -167,13 +167,13 @@ if { [catch {set cdup [exec git rev-parse --show-cdup]} err] error_popup "Cannot find the git directory:\n\n$err" exit 1 } -if {$cdup != ""} { +if {$cdup ne ""} { cd $cdup } unset cdup set single_commit 0 -if {$appname == {git-citool}} { +if {$appname eq {git-citool}} { set single_commit 1 } @@ -194,13 +194,13 @@ set commit_type {} proc lock_index {type} { global index_lock_type disable_on_lock - if {$index_lock_type == {none}} { + if {$index_lock_type eq {none}} { set index_lock_type $type foreach w $disable_on_lock { uplevel #0 $w disabled } return 1 - } elseif {$index_lock_type == {begin-update} && $type == {update}} { + } elseif {$index_lock_type eq {begin-update} && $type eq {update}} { set index_lock_type $type return 1 } @@ -242,9 +242,9 @@ proc update_status {{final Ready.}} { if {$status_active || ![lock_index read]} return repository_state new_HEAD new_type - if {$commit_type == {amend} - && $new_type == {normal} - && $new_HEAD == $HEAD} { + if {$commit_type eq {amend} + && $new_type eq {normal} + && $new_HEAD eq $HEAD} { } else { set HEAD $new_HEAD set PARENT $new_HEAD @@ -254,7 +254,7 @@ proc update_status {{final Ready.}} { array unset file_states if {![$ui_comm edit modified] - || [string trim [$ui_comm get 0.0 end]] == {}} { + || [string trim [$ui_comm get 0.0 end]] eq {}} { if {[load_message GITGUI_MSG]} { } elseif {[load_message MERGE_MSG]} { } elseif {[load_message SQUASH_MSG]} { @@ -263,7 +263,7 @@ proc update_status {{final Ready.}} { $ui_comm edit reset } - if {$repo_config(gui.trustmtime) == {true}} { + if {$repo_config(gui.trustmtime) eq {true}} { update_status_stage2 {} $final } else { set status_active 1 @@ -286,7 +286,7 @@ proc update_status_stage2 {fd final} { global status_active global buf_rdi buf_rdf buf_rlo - if {$fd != {}} { + if {$fd ne {}} { read $fd if {![eof $fd]} return close $fd @@ -442,7 +442,7 @@ proc clear_diff {} { proc reshow_diff {} { global ui_fname_value ui_status_value file_states - if {$ui_fname_value == {} + if {$ui_fname_value eq {} || [catch {set s $file_states($ui_fname_value)}]} { clear_diff } else { @@ -455,7 +455,7 @@ proc handle_empty_diff {} { set path $ui_fname_value set s $file_states($path) - if {[lindex $s 0] != {_M}} return + if {[lindex $s 0] ne {_M}} return info_popup "No differences detected. @@ -494,7 +494,7 @@ proc show_diff {path {w {}} {lno {}}} { if {$diff_active || ![lock_index read]} return clear_diff - if {$w == {} || $lno == {}} { + if {$w eq {} || $lno == {}} { foreach w [array names file_lists] { set lno [lsearch -sorted $file_lists($w) $path] if {$lno >= 0} { @@ -503,7 +503,7 @@ proc show_diff {path {w {}} {lno {}}} { } } } - if {$w != {} && $lno >= 1} { + if {$w ne {} && $lno >= 1} { $w tag add in_diff $lno.0 [expr $lno + 1].0 } @@ -604,8 +604,8 @@ proc read_diff {fd} { unlock_index set ui_status_value {Ready.} - if {$repo_config(gui.trustmtime) == {true} - && [$ui_diff index end] == {2.0}} { + if {$repo_config(gui.trustmtime) eq {true} + && [$ui_diff index end] eq {2.0}} { handle_empty_diff } } @@ -618,8 +618,8 @@ proc read_diff {fd} { proc load_last_commit {} { global HEAD PARENT commit_type ui_comm - if {$commit_type == {amend}} return - if {$commit_type != {normal}} { + if {$commit_type eq {amend}} return + if {$commit_type ne {normal}} { error_popup "Can't amend a $commit_type commit." return } @@ -671,10 +671,10 @@ proc commit_tree {} { # -- Our in memory state should match the repository. # repository_state curHEAD cur_type - if {$commit_type == {amend} - && $cur_type == {normal} - && $curHEAD == $HEAD} { - } elseif {$commit_type != $cur_type || $HEAD != $curHEAD} { + if {$commit_type eq {amend} + && $cur_type eq {normal} + && $curHEAD eq $HEAD} { + } elseif {$commit_type ne $cur_type || $HEAD ne $curHEAD} { error_popup {Last scanned state does not match repository state. Its highly likely that another Git program modified the @@ -725,7 +725,7 @@ You must include at least 1 file before you can commit. # -- A message is required. # set msg [string trim [$ui_comm get 1.0 end]] - if {$msg == {}} { + if {$msg eq {}} { error_popup {Please supply a commit message. A good commit message has the following format: @@ -741,7 +741,7 @@ A good commit message has the following format: # -- Ask the pre-commit hook for the go-ahead. # set pchook [file join $gitdir hooks pre-commit] - if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} { + if {$tcl_platform(platform) eq {windows} && [file isfile $pchook]} { set pchook [list sh -c [concat \ "if test -x \"$pchook\";" \ "then exec \"$pchook\" 2>&1;" \ @@ -751,7 +751,7 @@ A good commit message has the following format: } else { set pchook {} } - if {$pchook != {}} { + if {$pchook ne {}} { set ui_status_value {Calling pre-commit hook...} set pch_error {} set fd_ph [open "| $pchook" r] @@ -798,7 +798,7 @@ proc commit_stage3 {fd_wt curHEAD msg} { global file_states gets $fd_wt tree_id - if {$tree_id == {} || [catch {close $fd_wt} err]} { + 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 @@ -808,10 +808,10 @@ proc commit_stage3 {fd_wt curHEAD msg} { # -- Create the commit. # set cmd [list git commit-tree $tree_id] - if {$PARENT != {}} { + if {$PARENT ne {}} { lappend cmd -p $PARENT } - if {$commit_type == {merge}} { + if {$commit_type eq {merge}} { if {[catch { set fd_mh [open [file join $gitdir MERGE_HEAD] r] while {[gets $fd_mh merge_head] >= 0} { @@ -825,7 +825,7 @@ proc commit_stage3 {fd_wt curHEAD msg} { return } } - if {$PARENT == {}} { + if {$PARENT eq {}} { # git commit-tree writes to stderr during initial commit. lappend cmd 2>/dev/null } @@ -840,7 +840,7 @@ proc commit_stage3 {fd_wt curHEAD msg} { # -- Update the HEAD ref. # set reflogm commit - if {$commit_type != {normal}} { + if {$commit_type ne {normal}} { append reflogm " ($commit_type)" } set i [string first "\n" $msg] @@ -873,7 +873,7 @@ proc commit_stage3 {fd_wt curHEAD msg} { # -- Run the post-commit hook. # set pchook [file join $gitdir hooks post-commit] - if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} { + if {$tcl_platform(platform) eq {windows} && [file isfile $pchook]} { set pchook [list sh -c [concat \ "if test -x \"$pchook\";" \ "then exec \"$pchook\";" \ @@ -881,7 +881,7 @@ proc commit_stage3 {fd_wt curHEAD msg} { } elseif {![file executable $pchook]} { set pchook {} } - if {$pchook != {}} { + if {$pchook ne {}} { catch {exec $pchook &} } @@ -906,7 +906,7 @@ proc commit_stage3 {fd_wt curHEAD msg} { D? {set m _[string index $m 1]} } - if {$m == {__}} { + if {$m eq {__}} { unset file_states($path) } else { lset file_states($path) 0 $m @@ -940,7 +940,7 @@ proc pull_remote {remote branch} { # -- Our in memory state should match the repository. # repository_state curHEAD cur_type - if {$commit_type != $cur_type || $HEAD != $curHEAD} { + if {$commit_type ne $cur_type || $HEAD ne $curHEAD} { error_popup {Last scanned state does not match repository state. Its highly likely that another Git program modified the @@ -969,7 +969,7 @@ Commit or throw away all changes before starting a pull operation. set w [new_console "pull $remote $branch" \ "Pulling new changes from branch $branch in $remote"] set cmd [list git pull] - if {$repo_config(gui.pullsummary) == {false}} { + if {$repo_config(gui.pullsummary) eq {false}} { lappend cmd --no-summary } lappend cmd $remote @@ -1059,15 +1059,15 @@ proc merge_state {path new_state} { set icon [lindex $info 1] } - if {$s0 == {_}} { + if {$s0 eq {_}} { set s0 [string index $state 0] - } elseif {$s0 == {*}} { + } elseif {$s0 eq {*}} { set s0 _ } - if {$s1 == {_}} { + if {$s1 eq {_}} { set s1 [string index $state 1] - } elseif {$s1 == {*}} { + } elseif {$s1 eq {*}} { set s1 _ } @@ -1087,7 +1087,7 @@ proc display_file {path state} { set old_w [mapcol $old_m $path] set new_icon [mapicon $new_m $path] - if {$new_w != $old_w} { + if {$new_w ne $old_w} { set lno [lsearch -sorted $file_lists($old_w) $path] if {$lno >= 0} { incr lno @@ -1107,7 +1107,7 @@ proc display_file {path state} { -image $new_icon $new_w insert $lno.1 "[escape_path $path]\n" $new_w conf -state disabled - } elseif {$new_icon != [mapicon $old_m $path]} { + } elseif {$new_icon ne [mapicon $old_m $path]} { $new_w conf -state normal $new_w image conf [lindex $s 1] -image $new_icon $new_w conf -state disabled @@ -1199,7 +1199,7 @@ proc write_update_index {fd pathList totalCnt batch} { puts -nonewline $fd $path puts -nonewline $fd "\0" display_file $path $new - if {$ui_fname_value == $path} { + if {$ui_fname_value eq $path} { show_diff $path } } @@ -1252,8 +1252,8 @@ proc populate_pull_menu {m} { foreach remote $all_remotes { set rb {} - if {[array get repo_config remote.$remote.url] != {}} { - if {[array get repo_config remote.$remote.fetch] != {}} { + if {[array get repo_config remote.$remote.url] ne {}} { + if {[array get repo_config remote.$remote.fetch] ne {}} { regexp {^([^:]+):} \ [lindex $repo_config(remote.$remote.fetch) 0] \ line rb @@ -1272,7 +1272,7 @@ proc populate_pull_menu {m} { set rb_short $rb regsub ^refs/heads/ $rb {} rb_short - if {$rb_short != {}} { + if {$rb_short ne {}} { $m add command \ -label "Branch $rb_short from $remote..." \ -command [list pull_remote $remote $rb] \ @@ -1384,7 +1384,7 @@ foreach i { if {$max_status_desc < [string length [lindex $i 3]]} { set max_status_desc [string length [lindex $i 3]] } - if {[lindex $i 1] == {i}} { + if {[lindex $i 1] eq {i}} { set all_cols([lindex $i 0]) $ui_index } else { set all_cols([lindex $i 0]) $ui_other @@ -1400,8 +1400,8 @@ unset filemask i proc is_MacOSX {} { global tcl_platform tk_library - if {$tcl_platform(platform) == {unix} - && $tcl_platform(os) == {Darwin} + if {$tcl_platform(platform) eq {unix} + && $tcl_platform(os) eq {Darwin} && [string match /Library/Frameworks/* $tk_library]} { return 1 } @@ -1543,7 +1543,7 @@ proc console_exec {w cmd {after {}}} { # -- Windows tosses the enviroment when we exec our child. # But most users need that so we have to relogin. :-( # - if {$tcl_platform(platform) == {windows}} { + if {$tcl_platform(platform) eq {windows}} { set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"] } @@ -1561,7 +1561,7 @@ proc console_read {w fd after} { global console_cr console_data set buf [read $fd] - if {$buf != {}} { + if {$buf ne {}} { if {![winfo exists $w]} {console_init $w} $w.m.t conf -state normal set c 0 @@ -1605,7 +1605,7 @@ proc console_read {w fd after} { } array unset console_cr $w array unset console_data $w - if {$after != {}} { + if {$after ne {}} { uplevel #0 $after $ok } return @@ -1624,12 +1624,12 @@ proc do_gitk {} { set ui_status_value $starting_gitk_msg after 10000 { - if {$ui_status_value == $starting_gitk_msg} { + if {$ui_status_value eq $starting_gitk_msg} { set ui_status_value {Ready.} } } - if {$tcl_platform(platform) == {windows}} { + if {$tcl_platform(platform) eq {windows}} { exec sh -c gitk & } else { exec gitk & @@ -1656,13 +1656,13 @@ proc do_quit {} { # set save [file join $gitdir GITGUI_MSG] set msg [string trim [$ui_comm get 0.0 end]] - if {[$ui_comm edit modified] && $msg != {}} { + if {[$ui_comm edit modified] && $msg ne {}} { catch { set fd [open $save w] puts $fd [string trim [$ui_comm get 0.0 end]] close $fd } - } elseif {$msg == {} && [file exists $save]} { + } elseif {$msg eq {} && [file exists $save]} { file delete $save } @@ -1675,7 +1675,7 @@ proc do_quit {} { if {[catch {set rc_geometry $repo_config(gui.geometry)}]} { set rc_geometry {} } - if {$cfg_geometry != $rc_geometry} { + if {$cfg_geometry ne $rc_geometry} { catch {exec git repo-config gui.geometry $cfg_geometry} } @@ -1702,7 +1702,7 @@ proc do_include_all {} { _D {lappend pathList $path} } } - if {$pathList == {}} { + if {$pathList eq {}} { unlock_index } else { update_index $pathList @@ -1714,7 +1714,7 @@ set GIT_COMMITTER_IDENT {} proc do_signoff {} { global ui_comm GIT_COMMITTER_IDENT - if {$GIT_COMMITTER_IDENT == {}} { + if {$GIT_COMMITTER_IDENT eq {}} { if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} { error_popup "Unable to obtain your identity:\n\n$err" return @@ -1728,9 +1728,9 @@ proc do_signoff {} { set sob "Signed-off-by: $GIT_COMMITTER_IDENT" set last [$ui_comm get {end -1c linestart} {end -1c}] - if {$last != $sob} { + if {$last ne $sob} { $ui_comm edit separator - if {$last != {} + if {$last ne {} && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} { $ui_comm insert end "\n" } @@ -1888,7 +1888,7 @@ proc click {w x y shift wx wy} { set lno [lindex $pos 0] set col [lindex $pos 1] set path [lindex $file_lists($w) [expr $lno - 1]] - if {$path == {}} return + if {$path eq {}} return if {$col > 0 && $shift == 1} { show_diff $path $w $lno @@ -1902,7 +1902,7 @@ proc unclick {w x y} { set lno [lindex $pos 0] set col [lindex $pos 1] set path [lindex $file_lists($w) [expr $lno - 1]] - if {$path == {}} return + if {$path eq {}} return if {$col == 0} { update_index [list $path] @@ -1927,7 +1927,7 @@ font create font_diffbold set M1B M1 set M1T M1 -if {$tcl_platform(platform) == {windows}} { +if {$tcl_platform(platform) eq {windows}} { set M1B Control set M1T Ctrl } elseif {[is_MacOSX]} { From 2cbe5577a0e83ae7368eb2202ffe62c1d607b0bb Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 18:22:59 -0500 Subject: [PATCH 077/548] git-gui: Reshow diff if we sent the file to update-index. We can't ask the diff viewer to recompute the diff until after our update-index child process terminates, as the diff programs need to be able to read the updated index in order to generate the correct diff. This is actually why we prevent diffs from being generated while there is an update lock on the index, which is why we ignored our own show_diff invocation in the middle of the write_update_index event handler. So now we mark a flag if we identify that the file currently in the diff viewer was also sent to update-index; then later when the update-index process has terminated we update the diff viewer if the flag is true. Signed-off-by: Shawn O. Pearce --- git-gui | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/git-gui b/git-gui index 05ef8e2e33..fdb82812bd 100755 --- a/git-gui +++ b/git-gui @@ -1143,11 +1143,12 @@ proc display_all_files {} { } proc update_index {pathList} { - global update_index_cp ui_status_value + global update_index_cp update_index_rsd ui_status_value if {![lock_index update]} return set update_index_cp 0 + set update_index_rsd 0 set totalCnt [llength $pathList] set batch [expr {int($totalCnt * .01) + 1}] if {$batch > 25} {set batch 25} @@ -1170,13 +1171,17 @@ proc update_index {pathList} { } proc write_update_index {fd pathList totalCnt batch} { - global update_index_cp ui_status_value + global update_index_cp update_index_rsd ui_status_value global file_states ui_fname_value if {$update_index_cp >= $totalCnt} { close $fd unlock_index - set ui_status_value {Ready.} + if {$update_index_rsd} { + show_diff $ui_fname_value + } else { + set ui_status_value {Ready.} + } return } @@ -1200,7 +1205,7 @@ proc write_update_index {fd pathList totalCnt batch} { puts -nonewline $fd "\0" display_file $path $new if {$ui_fname_value eq $path} { - show_diff $path + set update_index_rsd 1 } } From fd2656fdfe575b55424b672f8bc274bf86526b9e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 18:51:38 -0500 Subject: [PATCH 078/548] git-gui: Cleanup diff construction code to prepare for more options. I'd like to allow the user to have more control over how we format the diff in the diff viewer; to that end we need to add additional options to the diff-index command line as we construct the command for execution. So cleanup the command handling code now to use lappend so we can come back and add in our additional options. Signed-off-by: Shawn O. Pearce --- git-gui | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/git-gui b/git-gui index fdb82812bd..ea71526e60 100755 --- a/git-gui +++ b/git-gui @@ -488,7 +488,7 @@ prevent possible confusion. proc show_diff {path {w {}} {lno {}}} { global file_states file_lists - global PARENT diff_3way diff_active + global PARENT diff_3way diff_active repo_config global ui_diff ui_fname_value ui_fstatus_value ui_status_value if {$diff_active || ![lock_index read]} return @@ -515,10 +515,13 @@ proc show_diff {path {w {}} {lno {}}} { set ui_fstatus_value [mapdesc $m $path] set ui_status_value "Loading diff of [escape_path $path]..." - set cmd [list | git diff-index -p $PARENT -- $path] + set cmd [list | git diff-index] + lappend cmd --no-color + lappend cmd -p + switch $m { MM { - set cmd [list | git diff-index -p -c $PARENT $path] + lappend cmd -c } _O { if {[catch { @@ -542,6 +545,10 @@ proc show_diff {path {w {}} {lno {}}} { } } + lappend cmd $PARENT + lappend cmd -- + lappend cmd $path + if {[catch {set fd [open $cmd r]} err]} { set diff_active 0 unlock_index @@ -1178,7 +1185,7 @@ proc write_update_index {fd pathList totalCnt batch} { close $fd unlock_index if {$update_index_rsd} { - show_diff $ui_fname_value + reshow_diff } else { set ui_status_value {Ready.} } From 358d8de8f3b9d09c9c4d7d43c03d33a4f60ba1da Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 19:20:02 -0500 Subject: [PATCH 079/548] git-gui: Allow the user to control the number of context lines in a diff. When displaying a diff the Git default of 3 line of context may not be enough for a user to see what has actually changed. Consequently we set our own program default to 5 lines of context and then allow the user to adjust this on a per-repository and global level through our options dialog. Signed-off-by: Shawn O. Pearce --- git-gui | 66 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/git-gui b/git-gui index ea71526e60..3f74fbb076 100755 --- a/git-gui +++ b/git-gui @@ -517,6 +517,9 @@ proc show_diff {path {w {}} {lno {}}} { set cmd [list | git diff-index] lappend cmd --no-color + if {$repo_config(gui.diffcontext) > 0} { + lappend cmd "-U$repo_config(gui.diffcontext)" + } lappend cmd -p switch $m { @@ -1765,12 +1768,18 @@ proc do_options {} { global repo_config global_config global repo_config_new global_config_new - load_config 1 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) } @@ -1811,18 +1820,36 @@ proc do_options {} { pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 foreach option { - {pullsummary {Show Pull Summary}} - {trustmtime {Trust File Modification Timestamps}} + {b pullsummary {Show Pull Summary}} + {b trustmtime {Trust File Modification Timestamps}} + {i diffcontext {Number of Diff Context Lines}} } { - set name [lindex $option 0] - set text [lindex $option 1] + set type [lindex $option 0] + set name [lindex $option 1] + set text [lindex $option 2] foreach f {repo global} { - checkbutton $w.$f.$name -text $text \ - -variable ${f}_config_new(gui.$name) \ - -onvalue true \ - -offvalue false \ - -font font_ui - pack $w.$f.$name -side top -anchor w + switch $type { + b { + checkbutton $w.$f.$name -text $text \ + -variable ${f}_config_new(gui.$name) \ + -onvalue true \ + -offvalue false \ + -font font_ui + pack $w.$f.$name -side top -anchor w + } + i { + frame $w.$f.$name + label $w.$f.$name.l -text "$text:" -font font_ui + pack $w.$f.$name.l -side left -anchor w -fill x + spinbox $w.$f.$name.v \ + -textvariable ${f}_config_new(gui.$name) \ + -from 1 -to 99 -increment 1 \ + -width 3 \ + -font font_ui + pack $w.$f.$name.v -side right -anchor e + pack $w.$f.$name -side top -anchor w -fill x + } + } } } @@ -1888,6 +1915,7 @@ proc do_save_config {w} { if {[catch {save_config} err]} { error_popup "Failed to completely save options:\n\n$err" } + reshow_diff destroy $w } @@ -1969,6 +1997,7 @@ proc apply_config {} { set default_config(gui.trustmtime) false set default_config(gui.pullsummary) true +set default_config(gui.diffcontext) 5 set default_config(gui.fontui) [font configure font_ui] set default_config(gui.fontdiff) [font configure font_diff] set font_descs { @@ -2318,6 +2347,21 @@ $ui_diff.ctxm add command -label "Decrease Font Size" \ $ui_diff.ctxm add command -label "Increase Font Size" \ -font font_ui \ -command {incr_font_size font_diff 1} +$ui_diff.ctxm add separator +$ui_diff.ctxm add command -label "Show Less Context" \ + -font font_ui \ + -command {if {$ui_fname_value ne {} + && $repo_config(gui.diffcontext) >= 2} { + incr repo_config(gui.diffcontext) -1 + reshow_diff + }} +$ui_diff.ctxm add command -label "Show More Context" \ + -font font_ui \ + -command {if {$ui_fname_value ne {}} { + incr repo_config(gui.diffcontext) + reshow_diff + }} +$ui_diff.ctxm add separator $ui_diff.ctxm add command -label {Options...} \ -font font_ui \ -command do_options From aaf1085a03a53eacff1b26459d0281a133f573d5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 19:29:04 -0500 Subject: [PATCH 080/548] git-gui: Sort the list of paths being updated in the index. Its a little surprising to see the UI update the icons for files in random order, due to the fact that the files are updating in the order they appear within the array (which is based on a hash function and not order). So sort the list of files before we send any to update-index so the order of operation is means something to the user. Signed-off-by: Shawn O. Pearce --- git-gui | 1 + 1 file changed, 1 insertion(+) diff --git a/git-gui b/git-gui index 3f74fbb076..b1a90e6661 100755 --- a/git-gui +++ b/git-gui @@ -1159,6 +1159,7 @@ proc update_index {pathList} { set update_index_cp 0 set update_index_rsd 0 + set pathList [lsort $pathList] set totalCnt [llength $pathList] set batch [expr {int($totalCnt * .01) + 1}] if {$batch > 25} {set batch 25} From 7f09cfafa8acf507f3a1358e05002e566f41783f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 19:33:33 -0500 Subject: [PATCH 081/548] git-gui: Use a smaller pipe buffer for update-index. When we shove a large number of files at update-index and they have very short path names we are likely going to fit a large number of them into the pipe buffer very early; thereby seeing a huge progress update followed by lots of waiting between progress updates due to the latency of update-index. Using a smaller buffer should help smooth out the progress updates as we are better able to keep tabs on the update-index process' progress through our list of paths. Signed-off-by: Shawn O. Pearce --- git-gui | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/git-gui b/git-gui index b1a90e6661..7e28328cf0 100755 --- a/git-gui +++ b/git-gui @@ -1171,7 +1171,11 @@ proc update_index {pathList} { $totalCnt \ 0.0] set fd [open "| git update-index --add --remove -z --stdin" w] - fconfigure $fd -blocking 0 -translation binary + fconfigure $fd \ + -blocking 0 \ + -buffering full \ + -buffersize 512 \ + -translation binary fileevent $fd writable [list \ write_update_index \ $fd \ From c11b5f20d3bc2e8bc4fcd965b16e1575b87c0d5f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 21:11:12 -0500 Subject: [PATCH 082/548] git-gui: Allow the user to copy name of the file in the diff viewer. There's a lot of reasons why the user might need to obtain the complete (or just part of) path of a file which they are currently viewing in the diff viewer pane. So now we allow selection on this widget by using a text widget instead of a label. We also offer a context menu which has actions for copying the selection or the entire value onto the clipboard. Signed-off-by: Shawn O. Pearce --- git-gui | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/git-gui b/git-gui index 7e28328cf0..1e5b0d049d 100755 --- a/git-gui +++ b/git-gui @@ -511,7 +511,7 @@ proc show_diff {path {w {}} {lno {}}} { set m [lindex $s 0] set diff_3way 0 set diff_active 1 - set ui_fname_value [escape_path $path] + set ui_fname_value $path set ui_fstatus_value [mapdesc $m $path] set ui_status_value "Loading diff of [escape_path $path]..." @@ -2284,11 +2284,33 @@ frame .vpane.lower.diff.header -background orange label .vpane.lower.diff.header.l1 -text {File:} \ -background orange \ -font font_ui -label .vpane.lower.diff.header.l2 -textvariable ui_fname_value \ +set ui_fname .vpane.lower.diff.header.l2 +text $ui_fname \ -background orange \ - -anchor w \ - -justify left \ + -height 1 \ + -relief flat \ + -state disabled \ -font font_ui +menu $ui_fname.ctxm -tearoff 0 +$ui_fname.ctxm add command -label "Copy Only Selection" \ + -font font_ui \ + -command "tk_textCopy $ui_fname" +$ui_fname.ctxm add command -label "Copy Complete Name" \ + -font font_ui \ + -command " + $ui_fname tag add sel 0.0 {end -1c} + tk_textCopy $ui_fname + $ui_fname tag remove sel 0.0 end + " +bind_button3 $ui_fname "tk_popup $ui_fname.ctxm %X %Y" +trace add variable ui_fname_value write $ui_fname.update +proc $ui_fname.update {varname args} { + global ui_fname ui_fname_value + $ui_fname configure -state normal + $ui_fname delete 0.0 end + $ui_fname insert end [escape_path $ui_fname_value] + $ui_fname configure -state disabled +} label .vpane.lower.diff.header.l3 -text {Status:} \ -background orange \ -font font_ui From 135f76ed996b6a0478831c561e1cddd249b7e19d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 21:49:49 -0500 Subject: [PATCH 083/548] git-gui: Correct language for M_/A_ status codes. When I changed from 'check in' to 'include' I missed the human friendly status displayed in the right side of the diff viewer heading. It was still reporting 'Checked in' for a fully included file, which is not what we wanted it to say. Signed-off-by: Shawn O. Pearce --- git-gui | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-gui b/git-gui index 1e5b0d049d..907db0e9dc 100755 --- a/git-gui +++ b/git-gui @@ -1385,17 +1385,17 @@ set max_status_desc 0 foreach i { {__ i plain "Unmodified"} {_M i mod "Modified"} - {M_ i fulltick "Checked in"} + {M_ i fulltick "Included in commit"} {MM i parttick "Partially included"} {_O o plain "Untracked"} - {A_ o fulltick "Added"} + {A_ o fulltick "Added by commit"} {AM o parttick "Partially added"} {AD o question "Added (but now gone)"} {_D i question "Missing"} - {D_ i removed "Removed"} - {DD i removed "Removed"} + {D_ i removed "Removed by commit"} + {DD i removed "Removed by commit"} {DO i removed "Removed (still exists)"} {UM i merge "Merge conflicts"} From 3e7b0e1d0ae509a54ca61c2b4c4990c8e6f0b2c0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 22:06:37 -0500 Subject: [PATCH 084/548] git-gui: Display status on left in diff header. Because the Tk pack layout manager gives all space to the right/bottom most widget during expand/contract of the frame we were adding and removing all space from the status area of the bar and not from the file name, which is what we actually wanted. A simple enough fix is to just put the status of the given file on the left side of the diff viewer header rather than on the right. Signed-off-by: Shawn O. Pearce --- git-gui | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/git-gui b/git-gui index 907db0e9dc..00b6cdf281 100755 --- a/git-gui +++ b/git-gui @@ -2281,6 +2281,13 @@ bind_button3 $ui_comm "tk_popup $ui_comm.ctxm %X %Y" set ui_fname_value {} set ui_fstatus_value {} frame .vpane.lower.diff.header -background orange +label .vpane.lower.diff.header.l4 \ + -textvariable ui_fstatus_value \ + -background orange \ + -width $max_status_desc \ + -anchor w \ + -justify left \ + -font font_ui label .vpane.lower.diff.header.l1 -text {File:} \ -background orange \ -font font_ui @@ -2288,6 +2295,7 @@ set ui_fname .vpane.lower.diff.header.l2 text $ui_fname \ -background orange \ -height 1 \ + -wrap none \ -relief flat \ -state disabled \ -font font_ui @@ -2311,19 +2319,9 @@ proc $ui_fname.update {varname args} { $ui_fname insert end [escape_path $ui_fname_value] $ui_fname configure -state disabled } -label .vpane.lower.diff.header.l3 -text {Status:} \ - -background orange \ - -font font_ui -label .vpane.lower.diff.header.l4 -textvariable ui_fstatus_value \ - -background orange \ - -width $max_status_desc \ - -anchor w \ - -justify left \ - -font font_ui +pack .vpane.lower.diff.header.l4 -side left pack .vpane.lower.diff.header.l1 -side left -pack .vpane.lower.diff.header.l2 -side left -fill x -pack .vpane.lower.diff.header.l4 -side right -pack .vpane.lower.diff.header.l3 -side right +pack $ui_fname -fill x # -- Diff Body frame .vpane.lower.diff.body From 1e5c18fb431c2d2493996e24ea68408e59ef6c16 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 12 Nov 2006 22:41:34 -0500 Subject: [PATCH 085/548] git-gui: Minor UI layout improvements for console windows. Moved the Close button over to the lower right corner where our Cancel/Save buttons are in the options dialog. This should fit better with our own look and feel as well as that of most apps on Mac OS X and Windows. Also set the lower status bar in a console window to indicate the process is working and that the user should wait for it to finish. Signed-off-by: Shawn O. Pearce --- git-gui | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/git-gui b/git-gui index 00b6cdf281..fbbc0caaac 100755 --- a/git-gui +++ b/git-gui @@ -1478,7 +1478,7 @@ proc hook_failed_popup {hook msg} { -width 15 \ -font font_ui \ -command "destroy $w" - pack $w.ok -side bottom + pack $w.ok -side bottom -anchor e -pady 10 -padx 10 bind $w "grab $w; focus $w" bind $w "destroy $w" @@ -1515,7 +1515,8 @@ proc console_init {w} { -font font_diff \ -state disabled \ -yscrollcommand [list $w.m.sby set] - label $w.m.s -anchor w \ + 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] @@ -1540,12 +1541,11 @@ proc console_init {w} { $w.m.t tag remove sel 0.0 end " - button $w.ok -text {Running...} \ - -width 15 \ + button $w.ok -text {Close} \ -font font_ui \ -state disabled \ -command "destroy $w" - pack $w.ok -side bottom + 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" @@ -1614,12 +1614,10 @@ proc console_read {w fd after} { if {[catch {close $fd}]} { if {![winfo exists $w]} {console_init $w} $w.m.s conf -background red -text {Error: Command Failed} - $w.ok conf -text Close $w.ok conf -state normal set ok 0 } elseif {[winfo exists $w]} { $w.m.s conf -background green -text {Success} - $w.ok conf -text Close $w.ok conf -state normal set ok 1 } From fce89e466ae75961018ab88fec7000568f981d46 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 13 Nov 2006 00:48:44 -0500 Subject: [PATCH 086/548] git-gui: Reverted file name text field to a label. So although a text field with a flat relief looks like a label on Windows it doesn't on Mac OS X. The Aqua version of Tk is still drawing a border around the text field and that makes the diff pane header look pretty ugly. Earlier I had made the file name area into a text widget so the user could highlight parts of it and copy them onto the clipboard; but with the context menu being present this isn't quite as necessary as the user can copy the file name to the clipboard using that instead. So although this is a small loss in functionality for non-Mac OS X systems I think it is still reasonable. Signed-off-by: Shawn O. Pearce --- git-gui | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/git-gui b/git-gui index fbbc0caaac..ca7f8dbc41 100755 --- a/git-gui +++ b/git-gui @@ -2290,33 +2290,23 @@ label .vpane.lower.diff.header.l1 -text {File:} \ -background orange \ -font font_ui set ui_fname .vpane.lower.diff.header.l2 -text $ui_fname \ +label $ui_fname \ + -textvariable ui_fname_value \ -background orange \ - -height 1 \ - -wrap none \ - -relief flat \ - -state disabled \ + -anchor w \ + -justify left \ -font font_ui menu $ui_fname.ctxm -tearoff 0 -$ui_fname.ctxm add command -label "Copy Only Selection" \ +$ui_fname.ctxm add command -label "Copy" \ -font font_ui \ - -command "tk_textCopy $ui_fname" -$ui_fname.ctxm add command -label "Copy Complete Name" \ - -font font_ui \ - -command " - $ui_fname tag add sel 0.0 {end -1c} - tk_textCopy $ui_fname - $ui_fname tag remove sel 0.0 end - " + -command { + clipboard clear + clipboard append \ + -format STRING \ + -type STRING \ + -- $ui_fname_value + } bind_button3 $ui_fname "tk_popup $ui_fname.ctxm %X %Y" -trace add variable ui_fname_value write $ui_fname.update -proc $ui_fname.update {varname args} { - global ui_fname ui_fname_value - $ui_fname configure -state normal - $ui_fname delete 0.0 end - $ui_fname insert end [escape_path $ui_fname_value] - $ui_fname configure -state disabled -} pack .vpane.lower.diff.header.l4 -side left pack .vpane.lower.diff.header.l1 -side left pack $ui_fname -fill x From f7f8d32226595c22cb4f28f8e9e139cf42e1e640 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 13 Nov 2006 04:22:42 -0500 Subject: [PATCH 087/548] git-gui: By default don't allow partially included files. The concept of the Git index is confusing for many users, especially those who are newer to Git. Since git-gui is (at least partially) intended to be used by newer users who don't need the complexity of the index to be put in front of them early on, we should hide it by making any partially included file fully included as soon as we identify it. To do this we just run a quick update_index pass on any file which differs both in the index and the working directory, as these files have already been at least partially included by the user. A new option has been added in the options dialog (gui.partialinclude) which lets the user enable accessing the index from git-gui. This just disables the automatic update_index pass on partially included files. Signed-off-by: Shawn O. Pearce --- git-gui | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/git-gui b/git-gui index ca7f8dbc41..3e3a535326 100755 --- a/git-gui +++ b/git-gui @@ -406,19 +406,33 @@ proc read_ls_others {fd final} { proc status_eof {fd buf final} { global status_active ui_status_value + global file_states repo_config upvar $buf to_clear - if {[eof $fd]} { - set to_clear {} - close $fd + if {![eof $fd]} return + set to_clear {} + close $fd + if {[incr status_active -1] > 0} return - if {[incr status_active -1] == 0} { - display_all_files - unlock_index - reshow_diff - set ui_status_value $final + unlock_index + display_all_files + + if {$repo_config(gui.partialinclude) ne {true}} { + set pathList [list] + foreach path [array names file_states] { + switch -- [lindex $file_states($path) 0] { + AM - + MM {lappend pathList $path} + } + } + if {$pathList ne {}} { + update_index $pathList + return } } + + reshow_diff + set ui_status_value $final } ###################################################################### @@ -1164,7 +1178,6 @@ proc update_index {pathList} { set batch [expr {int($totalCnt * .01) + 1}] if {$batch > 25} {set batch 25} - set ui_status_value "Including files ... 0/$totalCnt 0%" set ui_status_value [format \ "Including files ... %i/%i files (%.2f%%)" \ $update_index_cp \ @@ -1192,10 +1205,9 @@ proc write_update_index {fd pathList totalCnt batch} { if {$update_index_cp >= $totalCnt} { close $fd unlock_index + set ui_status_value {Ready.} if {$update_index_rsd} { reshow_diff - } else { - set ui_status_value {Ready.} } return } @@ -1823,6 +1835,7 @@ proc do_options {} { pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 foreach option { + {b partialinclude {Allow Partially Included Files}} {b pullsummary {Show Pull Summary}} {b trustmtime {Trust File Modification Timestamps}} {i diffcontext {Number of Diff Context Lines}} @@ -2000,6 +2013,7 @@ proc apply_config {} { set default_config(gui.trustmtime) false set default_config(gui.pullsummary) true +set default_config(gui.partialinclude) false set default_config(gui.diffcontext) 5 set default_config(gui.fontui) [font configure font_ui] set default_config(gui.fontdiff) [font configure font_diff] From 7d0d289e457eb643b8638b6385a0ce056f1f5a97 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 13 Nov 2006 14:25:53 -0500 Subject: [PATCH 088/548] git-gui: Refactor mouse clicking on file names/icons. I'm not a huge fan of putting the left and right mouse actions into the same procedure. Originally this is how Paul had implemented the logic in gitool and I had carried some of that over into git-gui, but now that I'm getting ready to implement right mouse click features to act on files I really should split this apart. Signed-off-by: Shawn O. Pearce --- git-gui | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/git-gui b/git-gui index 3e3a535326..b8ebe31777 100755 --- a/git-gui +++ b/git-gui @@ -1935,10 +1935,8 @@ proc do_save_config {w} { destroy $w } -# shift == 1: left click -# 3: right click -proc click {w x y shift wx wy} { - global ui_index ui_other file_lists +proc file_left_click {w x y} { + global file_lists set pos [split [$w index @$x,$y] .] set lno [lindex $pos 0] @@ -1946,12 +1944,12 @@ proc click {w x y shift wx wy} { set path [lindex $file_lists($w) [expr $lno - 1]] if {$path eq {}} return - if {$col > 0 && $shift == 1} { + if {$col > 0} { show_diff $path $w $lno } } -proc unclick {w x y} { +proc file_left_unclick {w x y} { global file_lists set pos [split [$w index @$x,$y] .] @@ -2457,9 +2455,8 @@ 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]} foreach i [list $ui_index $ui_other] { - bind $i {click %W %x %y 1 %X %Y; break} - bind $i {unclick %W %x %y; break} - bind_button3 $i {click %W %x %y 3 %X %Y; break} + bind $i {file_left_click %W %x %y; break} + bind $i {file_left_unclick %W %x %y; break} } unset i From a37eee4406ff965fda4d234759aa58526657cbe3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 13 Nov 2006 14:37:41 -0500 Subject: [PATCH 089/548] git-gui: Narrow the no differences information message. On Mac OS X the no differences informational message was linewrapped at the wrong points due to the limited width of the system dialog, yet the LFs embedded in the message (where I linewrapped it manually) were also being honored. This resulted in a very difficult to read paragraph of text. So this narrows the text down by another 10 columns or so, making it more readable. Signed-off-by: Shawn O. Pearce --- git-gui | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/git-gui b/git-gui index b8ebe31777..6b8d25e9aa 100755 --- a/git-gui +++ b/git-gui @@ -475,13 +475,15 @@ proc handle_empty_diff {} { [short_path $path] has no changes. -The modification date of this file was updated by another -application and you currently have the Trust File Modification -Timestamps option enabled, so Git did not automatically detect -that there are no content differences in this file. +The modification date of this file was updated +by another application and you currently have +the Trust File Modification Timestamps option +enabled, so Git did not automatically detect +that there are no content differences in this +file. -This file will now be removed from the modified files list, to -prevent possible confusion. +This file will now be removed from the modified +files list, to prevent possible confusion. " if {[catch {exec git update-index -- $path} err]} { error_popup "Failed to refresh index:\n\n$err" From 24263b77165edfd438045ed3c25ec6669b3e76d4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 13 Nov 2006 16:06:38 -0500 Subject: [PATCH 090/548] git-gui: Implemented multiple selection in file lists. Because I want to let users apply actions to more than one file at a time we really needed a concept of "the current selection" from the two file lists. Since I'm abusing a Tk text widget for the file displays I can't really use the Tk selection to track which files are picked and which aren't. So instead we keep this in an array to tell us which paths are currently selected and we use an inverse fg/bg for the selected file display. This is common most operating systems as a selection indicator. The selection works like most users would expect; single click will clear the selection and pick only that file, M1-click (aka Ctrl-click or Cmd-click) will toggle the one file in/out of the selection, and Shift-click will select the range between the last clicked file and the currently clicked file. Signed-off-by: Shawn O. Pearce --- git-gui | 138 +++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 112 insertions(+), 26 deletions(-) diff --git a/git-gui b/git-gui index 6b8d25e9aa..a60bf1c8a3 100755 --- a/git-gui +++ b/git-gui @@ -183,6 +183,7 @@ if {$appname eq {git-citool}} { set status_active 0 set diff_active 0 +set last_clicked {} set disable_on_lock [list] set index_lock_type none @@ -351,7 +352,7 @@ proc read_diff_index {fd final} { incr z2 -1 display_file \ [string range $buf_rdi $z1 $z2] \ - [string index $buf_rdi [expr $z1 - 2]]_ + [string index $buf_rdi [expr {$z1 - 2}]]_ incr c } if {$c < $n} { @@ -380,7 +381,7 @@ proc read_diff_files {fd final} { incr z2 -1 display_file \ [string range $buf_rdf $z1 $z2] \ - _[string index $buf_rdf [expr $z1 - 2]] + _[string index $buf_rdf [expr {$z1 - 2}]] incr c } if {$c < $n} { @@ -414,6 +415,7 @@ proc status_eof {fd buf final} { close $fd if {[incr status_active -1] > 0} return + prune_selection unlock_index display_all_files @@ -435,6 +437,16 @@ proc status_eof {fd buf final} { set ui_status_value $final } +proc prune_selection {} { + global file_states selected_paths + + foreach path [array names selected_paths] { + if {[catch {set still_here $file_states($path)}]} { + unset selected_paths($path) + } + } +} + ###################################################################### ## ## diff @@ -497,7 +509,7 @@ files list, to prevent possible confusion. [lreplace $file_lists($old_w) $lno $lno] incr lno $old_w conf -state normal - $old_w delete $lno.0 [expr $lno + 1].0 + $old_w delete $lno.0 [expr {$lno + 1}].0 $old_w conf -state disabled } } @@ -520,7 +532,7 @@ proc show_diff {path {w {}} {lno {}}} { } } if {$w ne {} && $lno >= 1} { - $w tag add in_diff $lno.0 [expr $lno + 1].0 + $w tag add in_diff $lno.0 [expr {$lno + 1}].0 } set s $file_states($path) @@ -821,7 +833,7 @@ proc commit_stage2 {curHEAD msg} { proc commit_stage3 {fd_wt curHEAD msg} { global single_commit gitdir HEAD PARENT commit_type tcl_platform global ui_status_value ui_comm - global file_states + global file_states selected_paths gets $fd_wt tree_id if {$tree_id eq {} || [catch {close $fd_wt} err]} { @@ -871,7 +883,7 @@ proc commit_stage3 {fd_wt curHEAD msg} { } set i [string first "\n" $msg] if {$i >= 0} { - append reflogm {: } [string range $msg 0 [expr $i - 1]] + append reflogm {: } [string range $msg 0 [expr {$i - 1}]] } else { append reflogm {: } $msg } @@ -934,6 +946,7 @@ proc commit_stage3 {fd_wt curHEAD msg} { if {$m eq {__}} { unset file_states($path) + catch {unset selected_paths($path)} } else { lset file_states($path) 0 $m } @@ -1102,7 +1115,7 @@ proc merge_state {path new_state} { } proc display_file {path state} { - global file_states file_lists status_active + global file_states file_lists selected_paths status_active set old_m [merge_state $path $state] if {$status_active} return @@ -1118,7 +1131,7 @@ proc display_file {path state} { if {$lno >= 0} { incr lno $old_w conf -state normal - $old_w delete $lno.0 [expr $lno + 1].0 + $old_w delete $lno.0 [expr {$lno + 1}].0 $old_w conf -state disabled } @@ -1132,6 +1145,12 @@ proc display_file {path state} { -name [lindex $s 1] \ -image $new_icon $new_w insert $lno.1 "[escape_path $path]\n" + if {[catch {set in_sel $selected_paths($path)}]} { + set in_sel 0 + } + if {$in_sel} { + $new_w tag add in_sel $lno.0 [expr {$lno + 1}].0 + } $new_w conf -state disabled } elseif {$new_icon ne [mapicon $old_m $path]} { $new_w conf -state normal @@ -1141,13 +1160,16 @@ proc display_file {path state} { } proc display_all_files {} { - global ui_index ui_other file_states file_lists + global ui_index ui_other + global file_states file_lists + global last_clicked selected_paths $ui_index conf -state normal $ui_other conf -state normal $ui_index delete 0.0 end $ui_other delete 0.0 end + set last_clicked {} set file_lists($ui_index) [list] set file_lists($ui_other) [list] @@ -1157,11 +1179,18 @@ proc display_all_files {} { set m [lindex $s 0] set w [mapcol $m $path] lappend file_lists($w) $path + set lno [expr {[lindex [split [$w index end] .] 0] - 1}] $w image create end \ -align center -padx 5 -pady 1 \ -name [lindex $s 1] \ -image [mapicon $m $path] $w insert end "[escape_path $path]\n" + if {[catch {set in_sel $selected_paths($path)}]} { + set in_sel 0 + } + if {$in_sel} { + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + } } $ui_index conf -state disabled @@ -1603,8 +1632,8 @@ proc console_read {w fd after} { 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 {$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] @@ -1937,32 +1966,83 @@ proc do_save_config {w} { destroy $w } -proc file_left_click {w x y} { - global file_lists +proc toggle_or_diff {w x y} { + global file_lists ui_index ui_other + global last_clicked selected_paths set pos [split [$w index @$x,$y] .] set lno [lindex $pos 0] set col [lindex $pos 1] - set path [lindex $file_lists($w) [expr $lno - 1]] - if {$path eq {}} return + set path [lindex $file_lists($w) [expr {$lno - 1}]] + if {$path eq {}} { + set last_clicked {} + return + } - if {$col > 0} { + set last_clicked [list $w $lno] + array unset selected_paths + $ui_index tag remove in_sel 0.0 end + $ui_other tag remove in_sel 0.0 end + + if {$col == 0} { + update_index [list $path] + } else { show_diff $path $w $lno } } -proc file_left_unclick {w x y} { +proc add_one_to_selection {w x y} { global file_lists + global last_clicked selected_paths set pos [split [$w index @$x,$y] .] set lno [lindex $pos 0] set col [lindex $pos 1] - set path [lindex $file_lists($w) [expr $lno - 1]] - if {$path eq {}} return - - if {$col == 0} { - update_index [list $path] + set path [lindex $file_lists($w) [expr {$lno - 1}]] + if {$path eq {}} { + set last_clicked {} + return } + + set last_clicked [list $w $lno] + if {[catch {set in_sel $selected_paths($path)}]} { + set in_sel 0 + } + if {$in_sel} { + unset selected_paths($path) + $w tag remove in_sel $lno.0 [expr {$lno + 1}].0 + } else { + set selected_paths($path) 1 + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + } +} + +proc add_range_to_selection {w x y} { + global file_lists + global last_clicked selected_paths + + if {[lindex $last_clicked 0] ne $w} { + toggle_or_diff $w $x $y + return + } + + set pos [split [$w index @$x,$y] .] + set lno [lindex $pos 0] + set lc [lindex $last_clicked 1] + if {$lc < $lno} { + set begin $lc + set end $lno + } else { + set begin $lno + set end $lc + } + + foreach path [lrange $file_lists($w) \ + [expr {$begin - 1}] \ + [expr {$end - 1}]] { + set selected_paths($path) 1 + } + $w tag add in_sel $begin.0 [expr {$end + 1}].0 } ###################################################################### @@ -2174,8 +2254,13 @@ pack .vpane.files.other.sb -side right -fill y pack $ui_other -side left -fill both -expand 1 .vpane.files add .vpane.files.other -sticky nsew -$ui_index tag conf in_diff -font font_uibold -$ui_other tag conf in_diff -font font_uibold +foreach i [list $ui_index $ui_other] { + $i tag conf in_diff -font font_uibold + $i tag conf in_sel \ + -background [$i cget -foreground] \ + -foreground [$i cget -background] +} +unset i # -- Diff and Commit Area frame .vpane.lower -height 300 -width 400 @@ -2457,8 +2542,9 @@ 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]} foreach i [list $ui_index $ui_other] { - bind $i {file_left_click %W %x %y; break} - bind $i {file_left_unclick %W %x %y; break} + bind $i "toggle_or_diff $i %x %y; break" + bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break" + bind $i "add_range_to_selection $i %x %y; break" } unset i From 99058720df7981aaaaf64e9a0d2c658d90b82340 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 14 Nov 2006 01:19:03 -0500 Subject: [PATCH 091/548] git-gui: Refactor update_status -> rescan. Since we refer to the act of updating our memory structures with index and working directory differences as a rescan in the UI its probably a good idea to make the related procedures have the same name. Signed-off-by: Shawn O. Pearce --- git-gui | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/git-gui b/git-gui index a60bf1c8a3..a1266fe7a7 100755 --- a/git-gui +++ b/git-gui @@ -234,7 +234,7 @@ proc repository_state {hdvar ctvar} { } } -proc update_status {{final Ready.}} { +proc rescan {{final Ready.}} { global HEAD PARENT commit_type global ui_index ui_other ui_status_value ui_comm global status_active file_states @@ -265,7 +265,7 @@ proc update_status {{final Ready.}} { } if {$repo_config(gui.trustmtime) eq {true}} { - update_status_stage2 {} $final + rescan_stage2 {} $final } else { set status_active 1 set ui_status_value {Refreshing file status...} @@ -277,11 +277,11 @@ proc update_status {{final Ready.}} { set fd_rf [open "| $cmd" r] fconfigure $fd_rf -blocking 0 -translation binary fileevent $fd_rf readable \ - [list update_status_stage2 $fd_rf $final] + [list rescan_stage2 $fd_rf $final] } } -proc update_status_stage2 {fd final} { +proc rescan_stage2 {fd final} { global gitdir PARENT commit_type global ui_index ui_other ui_status_value ui_comm global status_active @@ -684,7 +684,7 @@ proc load_last_commit {} { set commit_type amend set HEAD {} set PARENT {} - update_status + rescan } elseif {$parent_count == 1} { set commit_type amend set PARENT $parent @@ -692,7 +692,7 @@ proc load_last_commit {} { $ui_comm insert end $msg $ui_comm edit modified false $ui_comm edit reset - update_status + rescan } else { error_popup {You can't amend a merge commit.} return @@ -720,7 +720,7 @@ repository since our last scan. A rescan is required before committing. } unlock_index - update_status + rescan return } @@ -987,7 +987,7 @@ repository since our last scan. A rescan is required before a pull can be started. } unlock_index - update_status + rescan return } @@ -1026,7 +1026,7 @@ proc post_pull_remote {remote branch success} { set PARENT $HEAD set $ui_status_value {Ready.} } else { - update_status \ + rescan \ "Conflicts detected while pulling $branch from $remote." } } @@ -1744,7 +1744,7 @@ proc do_quit {} { } proc do_rescan {} { - update_status + rescan } proc do_include_all {} { @@ -2559,4 +2559,4 @@ if {!$single_commit} { populate_remote_menu .mbar.push To push_to populate_pull_menu .mbar.pull } -after 1 update_status +after 1 rescan From 8f52548a9ed078d581379ad526a4259920f80a88 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 14 Nov 2006 01:29:32 -0500 Subject: [PATCH 092/548] git-gui: Provide an after-rescan script to rescan. There are some situations where we need to run rescan and have it do more than just updating the status in the UI when its complete. To help with that this changes the rescan procedure to take a script which it will run at the global level as soon as the rescan is done and the UI has finished updating with the results. This is useful for example if we performed a rescan as part of a commit operation; we can go back to the commit where we left off when the rescan got initiated. Signed-off-by: Shawn O. Pearce --- git-gui | 68 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/git-gui b/git-gui index a1266fe7a7..2c8501eebf 100755 --- a/git-gui +++ b/git-gui @@ -181,7 +181,7 @@ if {$appname eq {git-citool}} { ## ## task management -set status_active 0 +set rescan_active 0 set diff_active 0 set last_clicked {} @@ -234,13 +234,13 @@ proc repository_state {hdvar ctvar} { } } -proc rescan {{final Ready.}} { +proc rescan {after} { global HEAD PARENT commit_type global ui_index ui_other ui_status_value ui_comm - global status_active file_states + global rescan_active file_states global repo_config - if {$status_active || ![lock_index read]} return + if {$rescan_active > 0 || ![lock_index read]} return repository_state new_HEAD new_type if {$commit_type eq {amend} @@ -265,9 +265,9 @@ proc rescan {{final Ready.}} { } if {$repo_config(gui.trustmtime) eq {true}} { - rescan_stage2 {} $final + rescan_stage2 {} $after } else { - set status_active 1 + set rescan_active 1 set ui_status_value {Refreshing file status...} set cmd [list git update-index] lappend cmd -q @@ -277,14 +277,14 @@ proc rescan {{final Ready.}} { set fd_rf [open "| $cmd" r] fconfigure $fd_rf -blocking 0 -translation binary fileevent $fd_rf readable \ - [list rescan_stage2 $fd_rf $final] + [list rescan_stage2 $fd_rf $after] } } -proc rescan_stage2 {fd final} { +proc rescan_stage2 {fd after} { global gitdir PARENT commit_type global ui_index ui_other ui_status_value ui_comm - global status_active + global rescan_active global buf_rdi buf_rdf buf_rlo if {$fd ne {}} { @@ -304,7 +304,7 @@ proc rescan_stage2 {fd final} { set buf_rdf {} set buf_rlo {} - set status_active 3 + set rescan_active 3 set ui_status_value {Scanning for modified files ...} set fd_di [open "| git diff-index --cached -z $PARENT" r] set fd_df [open "| git diff-files -z" r] @@ -313,9 +313,9 @@ proc rescan_stage2 {fd final} { fconfigure $fd_di -blocking 0 -translation binary fconfigure $fd_df -blocking 0 -translation binary fconfigure $fd_lo -blocking 0 -translation binary - fileevent $fd_di readable [list read_diff_index $fd_di $final] - fileevent $fd_df readable [list read_diff_files $fd_df $final] - fileevent $fd_lo readable [list read_ls_others $fd_lo $final] + fileevent $fd_di readable [list read_diff_index $fd_di $after] + fileevent $fd_df readable [list read_diff_files $fd_df $after] + fileevent $fd_lo readable [list read_ls_others $fd_lo $after] } proc load_message {file} { @@ -335,7 +335,7 @@ proc load_message {file} { return 0 } -proc read_diff_index {fd final} { +proc read_diff_index {fd after} { global buf_rdi append buf_rdi [read $fd] @@ -361,10 +361,10 @@ proc read_diff_index {fd final} { set buf_rdi {} } - status_eof $fd buf_rdi $final + rescan_done $fd buf_rdi $after } -proc read_diff_files {fd final} { +proc read_diff_files {fd after} { global buf_rdf append buf_rdf [read $fd] @@ -390,10 +390,10 @@ proc read_diff_files {fd final} { set buf_rdf {} } - status_eof $fd buf_rdf $final + rescan_done $fd buf_rdf $after } -proc read_ls_others {fd final} { +proc read_ls_others {fd after} { global buf_rlo append buf_rlo [read $fd] @@ -402,18 +402,18 @@ proc read_ls_others {fd final} { foreach p [lrange $pck 0 end-1] { display_file $p _O } - status_eof $fd buf_rlo $final + rescan_done $fd buf_rlo $after } -proc status_eof {fd buf final} { - global status_active ui_status_value +proc rescan_done {fd buf after} { + global rescan_active global file_states repo_config upvar $buf to_clear if {![eof $fd]} return set to_clear {} close $fd - if {[incr status_active -1] > 0} return + if {[incr rescan_active -1] > 0} return prune_selection unlock_index @@ -434,7 +434,7 @@ proc status_eof {fd buf final} { } reshow_diff - set ui_status_value $final + uplevel #0 $after } proc prune_selection {} { @@ -684,7 +684,7 @@ proc load_last_commit {} { set commit_type amend set HEAD {} set PARENT {} - rescan + rescan {set ui_status_value {Ready.}} } elseif {$parent_count == 1} { set commit_type amend set PARENT $parent @@ -692,7 +692,7 @@ proc load_last_commit {} { $ui_comm insert end $msg $ui_comm edit modified false $ui_comm edit reset - rescan + rescan {set ui_status_value {Ready.}} } else { error_popup {You can't amend a merge commit.} return @@ -720,7 +720,7 @@ repository since our last scan. A rescan is required before committing. } unlock_index - rescan + rescan {set ui_status_value {Ready.}} return } @@ -987,7 +987,7 @@ repository since our last scan. A rescan is required before a pull can be started. } unlock_index - rescan + rescan {set ui_status_value {Ready.}} return } @@ -1024,10 +1024,10 @@ proc post_pull_remote {remote branch success} { if {$success} { repository_state HEAD commit_type set PARENT $HEAD - set $ui_status_value {Ready.} + set $ui_status_value "Pulling $branch from $remote complete." } else { - rescan \ - "Conflicts detected while pulling $branch from $remote." + set m "Conflicts detected while pulling $branch from $remote." + rescan "set ui_status_value {$m}" } } @@ -1115,10 +1115,10 @@ proc merge_state {path new_state} { } proc display_file {path state} { - global file_states file_lists selected_paths status_active + global file_states file_lists selected_paths rescan_active set old_m [merge_state $path $state] - if {$status_active} return + if {$rescan_active > 0} return set s $file_states($path) set new_m [lindex $s 0] @@ -1744,7 +1744,7 @@ proc do_quit {} { } proc do_rescan {} { - rescan + rescan {set ui_status_value {Ready.}} } proc do_include_all {} { @@ -2559,4 +2559,4 @@ if {!$single_commit} { populate_remote_menu .mbar.push To push_to populate_pull_menu .mbar.pull } -after 1 rescan +after 1 do_rescan From 04b393824ff57b5fcb881a00466e513cd4ad2a7f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 14 Nov 2006 01:42:32 -0500 Subject: [PATCH 093/548] git-gui: Allow update_index to also run a script when it completes. Like rescan we also have cases where we need to perform a script after we have finished updating a number of files in the index. By changing the parameter structure of update_index we can easily pass through any script we need to run afterwards, such as picking up in the middle of a commit, or finishing what is left of a rescan. Signed-off-by: Shawn O. Pearce --- git-gui | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/git-gui b/git-gui index 2c8501eebf..be42b91067 100755 --- a/git-gui +++ b/git-gui @@ -428,7 +428,10 @@ proc rescan_done {fd buf after} { } } if {$pathList ne {}} { - update_index $pathList + update_index \ + "Updating included files" \ + $pathList \ + [concat {reshow_diff;} $after] return } } @@ -1197,7 +1200,7 @@ proc display_all_files {} { $ui_other conf -state disabled } -proc update_index {pathList} { +proc update_index {msg pathList after} { global update_index_cp update_index_rsd ui_status_value if {![lock_index update]} return @@ -1210,7 +1213,7 @@ proc update_index {pathList} { if {$batch > 25} {set batch 25} set ui_status_value [format \ - "Including files ... %i/%i files (%.2f%%)" \ + "$msg... %i/%i files (%.2f%%)" \ $update_index_cp \ $totalCnt \ 0.0] @@ -1226,20 +1229,20 @@ proc update_index {pathList} { $pathList \ $totalCnt \ $batch \ + $msg \ + $after \ ] } -proc write_update_index {fd pathList totalCnt batch} { +proc write_update_index {fd pathList totalCnt batch msg after} { global update_index_cp update_index_rsd ui_status_value global file_states ui_fname_value if {$update_index_cp >= $totalCnt} { close $fd unlock_index - set ui_status_value {Ready.} - if {$update_index_rsd} { - reshow_diff - } + if {$update_index_rsd} reshow_diff + uplevel #0 $after return } @@ -1268,7 +1271,7 @@ proc write_update_index {fd pathList totalCnt batch} { } set ui_status_value [format \ - "Including files ... %i/%i files (%.2f%%)" \ + "$msg... %i/%i files (%.2f%%)" \ $update_index_cp \ $totalCnt \ [expr {100.0 * $update_index_cp / $totalCnt}]] @@ -1766,7 +1769,10 @@ proc do_include_all {} { if {$pathList eq {}} { unlock_index } else { - update_index $pathList + update_index \ + "Including all modified files" \ + $pathList \ + {set ui_status_value {Ready to commit.}} } } @@ -1985,7 +1991,10 @@ proc toggle_or_diff {w x y} { $ui_other tag remove in_sel 0.0 end if {$col == 0} { - update_index [list $path] + update_index \ + "Including [short_path $path]" \ + [list $path] \ + {set ui_status_value {Ready.}} } else { show_diff $path $w $lno } From bbe3b3b9b93763ff4eff4fbb638b1c6a4bed8c95 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 15 Nov 2006 18:06:29 -0500 Subject: [PATCH 094/548] git-gui: Automatically update-index all included files before commit. If the user has "Allow Partially Included Files" disabled (and most probably will as its the default setting) we should run update-index on every included file before commit to make sure that any changes made by the user since the last rescan will still be part of this commit. If we don't update-index every modified file the user will likely become confused when part of their changes were committed and other parts weren't; and those other parts won't show up until a later rescan occurs. Since we don't rescan immediately after a commit this may be a while. Signed-off-by: Shawn O. Pearce --- git-gui | 101 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 35 deletions(-) diff --git a/git-gui b/git-gui index be42b91067..9a6953e970 100755 --- a/git-gui +++ b/git-gui @@ -420,7 +420,7 @@ proc rescan_done {fd buf after} { display_all_files if {$repo_config(gui.partialinclude) ne {true}} { - set pathList [list] + set pathList [list] foreach path [array names file_states] { switch -- [lindex $file_states($path) 0] { AM - @@ -703,9 +703,7 @@ proc load_last_commit {} { } proc commit_tree {} { - global tcl_platform HEAD gitdir commit_type file_states - global pch_error - global ui_status_value ui_comm + global HEAD commit_type file_states ui_comm repo_config if {![lock_index update]} return @@ -719,8 +717,10 @@ proc commit_tree {} { error_popup {Last scanned state does not match repository state. Its highly likely that another Git program modified the -repository since our last scan. A rescan is required +repository since the last scan. A rescan is required before committing. + +A rescan will be automatically started now. } unlock_index rescan {set ui_status_value {Ready.}} @@ -731,8 +731,7 @@ before committing. # set files_ready 0 foreach path [array names file_states] { - set s $file_states($path) - switch -glob -- [lindex $s 0] { + switch -glob -- [lindex $file_states($path) 0] { _? {continue} A? - D? - @@ -779,10 +778,39 @@ A good commit message has the following format: return } - # -- Ask the pre-commit hook for the go-ahead. + # -- Update included files if partialincludes are off. # + if {$repo_config(gui.partialinclude) ne {true}} { + set pathList [list] + foreach path [array names file_states] { + switch -glob -- [lindex $file_states($path) 0] { + A? - + M? {lappend pathList $path} + } + } + if {$pathList ne {}} { + unlock_index + update_index \ + "Updating included files" \ + $pathList \ + [concat {lock_index update;} \ + [list commit_prehook $curHEAD $msg]] + return + } + } + + commit_prehook $curHEAD $msg +} + +proc commit_prehook {curHEAD msg} { + global tcl_platform gitdir ui_status_value pch_error + + # On Cygwin [file executable] might lie so we need to ask + # the shell if the hook is executable. Yes that's annoying. + set pchook [file join $gitdir hooks pre-commit] - if {$tcl_platform(platform) eq {windows} && [file isfile $pchook]} { + if {$tcl_platform(platform) eq {windows} + && [file isfile $pchook]} { set pchook [list sh -c [concat \ "if test -x \"$pchook\";" \ "then exec \"$pchook\" 2>&1;" \ @@ -790,21 +818,19 @@ A good commit message has the following format: } elseif {[file executable $pchook]} { set pchook [list $pchook |& cat] } else { - set pchook {} - } - if {$pchook ne {}} { - 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_stage1 $fd_ph $curHEAD $msg] - } else { - commit_stage2 $curHEAD $msg + 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_stage1 {fd_ph curHEAD msg} { +proc commit_prehook_wait {fd_ph curHEAD msg} { global pch_error ui_status_value append pch_error [read $fd_ph] @@ -815,25 +841,24 @@ proc commit_stage1 {fd_ph curHEAD msg} { hook_failed_popup pre-commit $pch_error unlock_index } else { - commit_stage2 $curHEAD $msg + commit_writetree $curHEAD $msg } set pch_error {} - } else { - fconfigure $fd_ph -blocking 0 + return } + fconfigure $fd_ph -blocking 0 } -proc commit_stage2 {curHEAD msg} { +proc commit_writetree {curHEAD msg} { global ui_status_value - # -- Write the tree in the background. - # set ui_status_value {Committing changes...} set fd_wt [open "| git write-tree" r] - fileevent $fd_wt readable [list commit_stage3 $fd_wt $curHEAD $msg] + fileevent $fd_wt readable \ + [list commit_committree $fd_wt $curHEAD $msg] } -proc commit_stage3 {fd_wt curHEAD msg} { +proc commit_committree {fd_wt curHEAD msg} { global single_commit gitdir HEAD PARENT commit_type tcl_platform global ui_status_value ui_comm global file_states selected_paths @@ -1252,14 +1277,20 @@ proc write_update_index {fd pathList totalCnt batch msg after} { set path [lindex $pathList $update_index_cp] incr update_index_cp - switch -- [lindex $file_states($path) 0] { - AM - - _O {set new A*} - _M - - MM {set new M*} + switch -glob -- [lindex $file_states($path) 0] { AD - + MD - _D {set new D*} - default {continue} + + _M - + MM - + M_ {set new M*} + + _O - + AM - + A_ {set new A*} + + ?? {continue} } puts -nonewline $fd $path From e8ab64461968392f126b0a0e2fef4ce8bdcca623 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 15 Nov 2006 18:55:05 -0500 Subject: [PATCH 095/548] git-gui: Disable diff actions when no diff is active. There is no reason why the user should be able to operate on the diff buffer if there is no currently selected diff; likewise the "File:" label text appears rather silly looking all by itself when no diff is being shown in the diff buffer. So now we only enable widgets (like menu items) if there is a diff currently showing, and we disable them when a diff isn't showing. Signed-off-by: Shawn O. Pearce --- git-gui | 190 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 120 insertions(+), 70 deletions(-) diff --git a/git-gui b/git-gui index 9a6953e970..8449664538 100755 --- a/git-gui +++ b/git-gui @@ -455,34 +455,33 @@ proc prune_selection {} { ## diff proc clear_diff {} { - global ui_diff ui_fname_value ui_fstatus_value ui_index ui_other + global ui_diff current_diff ui_index ui_other $ui_diff conf -state normal $ui_diff delete 0.0 end $ui_diff conf -state disabled - set ui_fname_value {} - set ui_fstatus_value {} + set current_diff {} $ui_index tag remove in_diff 0.0 end $ui_other tag remove in_diff 0.0 end } proc reshow_diff {} { - global ui_fname_value ui_status_value file_states + global current_diff ui_status_value file_states - if {$ui_fname_value eq {} - || [catch {set s $file_states($ui_fname_value)}]} { + if {$current_diff eq {} + || [catch {set s $file_states($current_diff)}]} { clear_diff } else { - show_diff $ui_fname_value + show_diff $current_diff } } proc handle_empty_diff {} { - global ui_fname_value file_states file_lists + global current_diff file_states file_lists - set path $ui_fname_value + set path $current_diff set s $file_states($path) if {[lindex $s 0] ne {_M}} return @@ -520,7 +519,7 @@ files list, to prevent possible confusion. proc show_diff {path {w {}} {lno {}}} { global file_states file_lists global PARENT diff_3way diff_active repo_config - global ui_diff ui_fname_value ui_fstatus_value ui_status_value + global ui_diff current_diff ui_status_value if {$diff_active || ![lock_index read]} return @@ -542,8 +541,7 @@ proc show_diff {path {w {}} {lno {}}} { set m [lindex $s 0] set diff_3way 0 set diff_active 1 - set ui_fname_value $path - set ui_fstatus_value [mapdesc $m $path] + set current_diff $path set ui_status_value "Loading diff of [escape_path $path]..." set cmd [list | git diff-index] @@ -1261,7 +1259,7 @@ proc update_index {msg pathList after} { proc write_update_index {fd pathList totalCnt batch msg after} { global update_index_cp update_index_rsd ui_status_value - global file_states ui_fname_value + global file_states current_diff if {$update_index_cp >= $totalCnt} { close $fd @@ -1296,7 +1294,7 @@ proc write_update_index {fd pathList totalCnt batch msg after} { puts -nonewline $fd $path puts -nonewline $fd "\0" display_file $path $new - if {$ui_fname_value eq $path} { + if {$current_diff eq $path} { set update_index_rsd 1 } } @@ -2384,71 +2382,105 @@ pack .vpane.lower.commarea.buffer -side left -fill y # -- Commit Message Buffer Context Menu # -menu $ui_comm.ctxm -tearoff 0 -$ui_comm.ctxm add command -label "Cut" \ +set ctxm .vpane.lower.commarea.buffer.ctxm +menu $ctxm -tearoff 0 +$ctxm add command \ + -label {Cut} \ -font font_ui \ - -command "tk_textCut $ui_comm" -$ui_comm.ctxm add command -label "Copy" \ + -command {tk_textCut $ui_comm} +$ctxm add command \ + -label {Copy} \ -font font_ui \ - -command "tk_textCopy $ui_comm" -$ui_comm.ctxm add command -label "Paste" \ + -command {tk_textCopy $ui_comm} +$ctxm add command \ + -label {Paste} \ -font font_ui \ - -command "tk_textPaste $ui_comm" -$ui_comm.ctxm add command -label "Delete" \ + -command {tk_textPaste $ui_comm} +$ctxm add command \ + -label {Delete} \ -font font_ui \ - -command "$ui_comm delete sel.first sel.last" -$ui_comm.ctxm add separator -$ui_comm.ctxm add command -label "Select All" \ + -command {$ui_comm delete sel.first sel.last} +$ctxm add separator +$ctxm add command \ + -label {Select All} \ -font font_ui \ - -command "$ui_comm tag add sel 0.0 end" -$ui_comm.ctxm add command -label "Copy All" \ + -command {$ui_comm tag add sel 0.0 end} +$ctxm add command \ + -label {Copy All} \ -font font_ui \ - -command " + -command { $ui_comm tag add sel 0.0 end tk_textCopy $ui_comm $ui_comm tag remove sel 0.0 end - " -$ui_comm.ctxm add separator -$ui_comm.ctxm add command -label "Sign Off" \ + } +$ctxm add separator +$ctxm add command \ + -label {Sign Off} \ -font font_ui \ -command do_signoff -bind_button3 $ui_comm "tk_popup $ui_comm.ctxm %X %Y" +bind_button3 $ui_comm "tk_popup $ctxm %X %Y" # -- Diff Header -set ui_fname_value {} -set ui_fstatus_value {} +set current_diff {} +set diff_actions [list] +proc current_diff_trace {varname args} { + global current_diff diff_actions file_states + if {$current_diff eq {}} { + set s {} + set f {} + set p {} + set o disabled + } else { + set p $current_diff + set s [mapdesc [lindex $file_states($p) 0] $p] + set f {File:} + set p [escape_path $p] + set o normal + } + + .vpane.lower.diff.header.status configure -text $s + .vpane.lower.diff.header.file configure -text $f + .vpane.lower.diff.header.path configure -text $p + foreach w $diff_actions { + uplevel #0 $w $o + } +} +trace add variable current_diff write current_diff_trace + frame .vpane.lower.diff.header -background orange -label .vpane.lower.diff.header.l4 \ - -textvariable ui_fstatus_value \ +label .vpane.lower.diff.header.status \ -background orange \ -width $max_status_desc \ -anchor w \ -justify left \ -font font_ui -label .vpane.lower.diff.header.l1 -text {File:} \ - -background orange \ - -font font_ui -set ui_fname .vpane.lower.diff.header.l2 -label $ui_fname \ - -textvariable ui_fname_value \ +label .vpane.lower.diff.header.file \ -background orange \ -anchor w \ -justify left \ -font font_ui -menu $ui_fname.ctxm -tearoff 0 -$ui_fname.ctxm add command -label "Copy" \ +label .vpane.lower.diff.header.path \ + -background orange \ + -anchor w \ + -justify left \ + -font font_ui +pack .vpane.lower.diff.header.status -side left +pack .vpane.lower.diff.header.file -side left +pack .vpane.lower.diff.header.path -fill x +set ctxm .vpane.lower.diff.header.ctxm +menu $ctxm -tearoff 0 +$ctxm add command \ + -label {Copy} \ -font font_ui \ -command { clipboard clear clipboard append \ -format STRING \ -type STRING \ - -- $ui_fname_value + -- $current_diff } -bind_button3 $ui_fname "tk_popup $ui_fname.ctxm %X %Y" -pack .vpane.lower.diff.header.l4 -side left -pack .vpane.lower.diff.header.l1 -side left -pack $ui_fname -fill x +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y" # -- Diff Body frame .vpane.lower.diff.body @@ -2478,48 +2510,63 @@ $ui_diff tag conf bold -font font_diffbold # -- Diff Body Context Menu # -menu $ui_diff.ctxm -tearoff 0 -$ui_diff.ctxm add command -label "Copy" \ +set ctxm .vpane.lower.diff.body.ctxm +menu $ctxm -tearoff 0 +$ctxm add command \ + -label {Copy} \ -font font_ui \ - -command "tk_textCopy $ui_diff" -$ui_diff.ctxm add command -label "Select All" \ + -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 "$ui_diff tag add sel 0.0 end" -$ui_diff.ctxm add command -label "Copy All" \ + -command {$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 " + -command { $ui_diff tag add sel 0.0 end tk_textCopy $ui_diff $ui_diff tag remove sel 0.0 end - " -$ui_diff.ctxm add separator -$ui_diff.ctxm add command -label "Decrease Font Size" \ + } +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add separator +$ctxm add command \ + -label {Decrease Font Size} \ -font font_ui \ -command {incr_font_size font_diff -1} -$ui_diff.ctxm add command -label "Increase Font Size" \ +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} -$ui_diff.ctxm add separator -$ui_diff.ctxm add command -label "Show Less Context" \ +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 {$ui_fname_value ne {} - && $repo_config(gui.diffcontext) >= 2} { + -command {if {$repo_config(gui.diffcontext) >= 2} { incr repo_config(gui.diffcontext) -1 reshow_diff }} -$ui_diff.ctxm add command -label "Show More Context" \ +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add command \ + -label {Show More Context} \ -font font_ui \ - -command {if {$ui_fname_value ne {}} { + -command { incr repo_config(gui.diffcontext) reshow_diff - }} -$ui_diff.ctxm add separator -$ui_diff.ctxm add command -label {Options...} \ + } +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 "tk_popup $ui_diff.ctxm %X %Y" +bind_button3 $ui_diff "tk_popup $ctxm %X %Y" # -- Status Bar +# set ui_status_value {Initializing...} label .status -textvariable ui_status_value \ -anchor w \ @@ -2530,6 +2577,7 @@ label .status -textvariable ui_status_value \ pack .status -anchor w -side bottom -fill x # -- Load geometry +# catch { set gm $repo_config(gui.geometry) wm geometry . [lindex $gm 0] @@ -2543,6 +2591,7 @@ unset gm } # -- Key Bindings +# bind $ui_comm <$M1B-Key-Return> {do_commit;break} bind $ui_comm <$M1B-Key-i> {do_include_all;break} bind $ui_comm <$M1B-Key-I> {do_include_all;break} @@ -2590,6 +2639,7 @@ unset i set file_lists($ui_index) [list] set file_lists($ui_other) [list] +set current_diff {} wm title . "$appname ([file normalize [file dirname $gitdir]])" focus -force $ui_comm From b3678bacbcd2dc0e1c61fd91f4146d0c037e626f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 15 Nov 2006 20:04:17 -0500 Subject: [PATCH 096/548] git-gui: Created makefile to install the program. Since we want to be installed in gitexecdir so that "git gui" works we can guess where that directory is by asking the git wrapper executable and locating ourselves at the same location using the same install rules as core git. Signed-off-by: Shawn O. Pearce --- Makefile | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..e3e871f7c4 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +all: git-gui + +gitexecdir := $(shell git --exec-path) +INSTALL = install + +DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) +gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) + +GITGUI_BUILTIN = git-citool + +install: all + $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)' + $(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)' + $(foreach p,$(GITGUI_BUILTIN), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;) From fbee8500a5b64a1c0c6103232879bcecc30f64b4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 15 Nov 2006 22:13:45 -0500 Subject: [PATCH 097/548] git-gui: Correctly handle GIT_DIR environment variable. Some users may want to start us by running "git --git-dir=... gui" rather than trying to cd into the directory first. This is especially true if they want to just make a shortcut to our executable on Windows and always have that associated with a certain repository. Since Tcl on Windows throws away our environment and doesn't pass it down to the child process correctly we cannot call git-rev-parse to get the GIT_DIR environment variable. So instead we ask for it specifically ourselves; if its not defined then we ask rev-parse. This should actually reduce startup by 1 fork/exec if we were started as "git gui" as GIT_DIR will be set for us. Signed-off-by: Shawn O. Pearce --- git-gui | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/git-gui b/git-gui index 8449664538..7c2f803fec 100755 --- a/git-gui +++ b/git-gui @@ -161,16 +161,17 @@ proc info_popup {msg} { ## ## repository setup -if { [catch {set cdup [exec git rev-parse --show-cdup]} err] - || [catch {set gitdir [exec git rev-parse --git-dir]} err]} { +if { [catch {set gitdir $env(GIT_DIR)}] + && [catch {set gitdir [exec git rev-parse --git-dir]} err]} { catch {wm withdraw .} error_popup "Cannot find the git directory:\n\n$err" exit 1 } -if {$cdup ne ""} { - cd $cdup +if {[catch {cd [file dirname $gitdir]} err]} { + catch {wm withdraw .} + error_popup "No working directory [file dirname $gitdir]:\n\n$err" + exit 1 } -unset cdup set single_commit 0 if {$appname eq {git-citool}} { From 4aca740b3915b35d7fa1707be79b268b0cc94123 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 15 Nov 2006 22:35:26 -0500 Subject: [PATCH 098/548] git-gui: Create Windows shortcut icons for git-gui. If we are running on Windows we now offer a 'Create Desktop Icon' menu item under the Project menu. This pops up a save dialog box letting the user create a .bat file on their desktop (or somewhere else). The .bat script will startup Cygwin with a login shell then launch git-gui in the current working directory. This is very useful for Windows users who have little to no desire to start a command window just to run a git-gui session. Signed-off-by: Shawn O. Pearce --- git-gui | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/git-gui b/git-gui index 7c2f803fec..def6a5e070 100755 --- a/git-gui +++ b/git-gui @@ -2002,6 +2002,54 @@ proc do_save_config {w} { destroy $w } +proc do_windows_shortcut {} { + global gitdir appname argv0 + + set reponame [lindex [file split \ + [file normalize [file dirname $gitdir]]] \ + end] + + 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 \ + --long-name \ + /bin/sh] + set me [exec cygpath \ + --unix \ + --absolute \ + $argv0] + set gd [exec cygpath \ + --unix \ + --absolute \ + $gitdir] + 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 toggle_or_diff {w x y} { global file_lists ui_index ui_other global last_clicked selected_paths @@ -2168,6 +2216,13 @@ if {!$single_commit} { .mbar.project add command -label {Repack Database} \ -command do_repack \ -font font_ui + + if {$tcl_platform(platform) eq {windows}} { + .mbar.project add command \ + -label {Create Desktop Icon} \ + -command do_windows_shortcut \ + -font font_ui + } } .mbar.project add command -label Quit \ -command do_quit \ From dbccbbda4f4c049552495a87b1747b1b2a1e2823 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 15 Nov 2006 22:45:33 -0500 Subject: [PATCH 099/548] git-gui: Protect ourselves from funny GIT_DIR/working directory setups. Since we have some serious problems with the GIT_DIR environment variable on Windows we cannot let the user use a non-standard GIT_DIR with their working directory. So require that the GIT_DIR name is actually ".git", that it exists, and that its parent directory is our working directory. Signed-off-by: Shawn O. Pearce --- git-gui | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/git-gui b/git-gui index def6a5e070..c35c73c8c3 100755 --- a/git-gui +++ b/git-gui @@ -167,6 +167,16 @@ if { [catch {set gitdir $env(GIT_DIR)}] error_popup "Cannot find the git directory:\n\n$err" exit 1 } +if {![file isdirectory $gitdir]} { + catch {wm withdraw .} + error_popup "Git directory not found:\n\n$gitdir" + exit 1 +} +if {[lindex [file split $gitdir] end] ne {.git}} { + catch {wm withdraw .} + error_popup "Cannot use funny .git directory:\n\n$gitdir" + exit 1 +} if {[catch {cd [file dirname $gitdir]} err]} { catch {wm withdraw .} error_popup "No working directory [file dirname $gitdir]:\n\n$err" @@ -2040,8 +2050,8 @@ proc do_windows_shortcut {} { --absolute \ $gitdir] puts -nonewline $fd "\"$sh\" --login -c \"" - puts -nonewline $fd "GIT_DIR=\\\"$gd\\\"" - puts -nonewline $fd " \\\"$me\\\"" + puts -nonewline $fd "GIT_DIR='$gd'" + puts -nonewline $fd " '$me'" puts $fd "&\"" close $fd } err]} { From 306500fc09e7d8be042e8b9abbd9011b80b3300d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 15 Nov 2006 22:53:53 -0500 Subject: [PATCH 100/548] git-gui: Handle ' within paths when creating Windows shortcuts. Signed-off-by: Shawn O. Pearce --- git-gui | 2 ++ 1 file changed, 2 insertions(+) diff --git a/git-gui b/git-gui index c35c73c8c3..013f21b2e8 100755 --- a/git-gui +++ b/git-gui @@ -2049,6 +2049,8 @@ proc do_windows_shortcut {} { --unix \ --absolute \ $gitdir] + regsub -all ' $me "'\\''" me + regsub -all ' $gd "'\\''" gd puts -nonewline $fd "\"$sh\" --login -c \"" puts -nonewline $fd "GIT_DIR='$gd'" puts -nonewline $fd " '$me'" From c1237ae288aae7e45a18f3d5097b49451293acfe Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 15 Nov 2006 23:52:20 -0500 Subject: [PATCH 101/548] git-gui: Only populate a fetch or push if we have an action. Don't offer to fetch from a remote unless we have at least one Pull: line in its .git/remotes/ file or at least one configuration value for remote..fetch. Ditto for push. Users shouldn't be fetching or pushing branch groups unless they have them configured; anything else is just crazy. Signed-off-by: Shawn O. Pearce --- git-gui | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/git-gui b/git-gui index 013f21b2e8..ea60e327ea 100755 --- a/git-gui +++ b/git-gui @@ -1343,13 +1343,65 @@ proc load_all_remotes {} { set all_remotes [lsort -unique $all_remotes] } -proc populate_remote_menu {m pfx op} { - global all_remotes +proc populate_fetch_menu {m} { + global gitdir all_remotes repo_config - foreach remote $all_remotes { - $m add command -label "$pfx $remote..." \ - -command [list $op $remote] \ - -font font_ui + 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 [file join $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] \ + -font font_ui + } + } +} + +proc populate_push_menu {m} { + global gitdir all_remotes repo_config + + 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 [file join $gitdir remotes $r] r] + while {[gets $fd n] >= 0} { + if {[regexp {^Push:[ \t]*([^:]+):} $n]} { + set enable 1 + break + } + } + close $fd + } + } + + if {$enable} { + $m add command \ + -label "Push to $r..." \ + -command [list push_to $r] \ + -font font_ui + } } } @@ -2713,8 +2765,8 @@ wm title . "$appname ([file normalize [file dirname $gitdir]])" focus -force $ui_comm if {!$single_commit} { load_all_remotes - populate_remote_menu .mbar.fetch From fetch_from - populate_remote_menu .mbar.push To push_to + populate_fetch_menu .mbar.fetch populate_pull_menu .mbar.pull + populate_push_menu .mbar.push } after 1 do_rescan From 06c311157a045c2189acc5496fdc71a806c28f8c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 00:31:00 -0500 Subject: [PATCH 102/548] git-gui: Create a .app file on MacOS X if requested. If a user works with a repository frequently they may want to just create an icon they can use to launch git-gui against that repository. Since we already support this concept on Windows we can do the same on Mac OS X by creating a .app file with a tiny shell script in it that sets up the necessary environment then invokes our script. Signed-off-by: Shawn O. Pearce --- git-gui | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/git-gui b/git-gui index ea60e327ea..472bcb7e32 100755 --- a/git-gui +++ b/git-gui @@ -2114,6 +2114,79 @@ proc do_windows_shortcut {} { } } +proc do_macosx_app {} { + global gitdir appname argv0 env + + set reponame [lindex [file split \ + [file normalize [file dirname $gitdir]]] \ + end] + + 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 PkgInfo] w] + puts -nonewline $fd {APPL????} + close $fd + + 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 [exec git --exec-path]] + 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_lists ui_index ui_other global last_clicked selected_paths @@ -2286,6 +2359,11 @@ if {!$single_commit} { -label {Create Desktop Icon} \ -command do_windows_shortcut \ -font font_ui + } elseif {[is_MacOSX]} { + .mbar.project add command \ + -label {Create Desktop Icon} \ + -command do_macosx_app \ + -font font_ui } } .mbar.project add command -label Quit \ From cbbaa28bc0a2b607b0f5e2e23f910d1619b52b0f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 01:20:37 -0500 Subject: [PATCH 103/548] git-gui: Display error dialog on Mac OS X when no .git found. If we can't locate a .git directory for the given directory we need to show a message to the user to let them know the directory wasn't found. But since this is before we have shown our main application window we cannot use that as the parent for the error popup; on Mac OS X this causes an error and prevents the dialog from showing. Instead only add -parent . to the popup call if we have mapped (shown) the main window. Signed-off-by: Shawn O. Pearce --- git-gui | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/git-gui b/git-gui index 472bcb7e32..12a46e976c 100755 --- a/git-gui +++ b/git-gui @@ -130,12 +130,15 @@ proc error_popup {msg} { end] append title {)} } - tk_messageBox \ - -parent . \ + set cmd [list tk_messageBox \ -icon error \ -type ok \ -title "$title: error" \ - -message $msg + -message $msg] + if {[winfo ismapped .]} { + lappend cmd -parent . + } + eval $cmd } proc info_popup {msg} { From 4539eacd6dde87b4e73c859e946bb0a2c89d71f4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 02:50:58 -0500 Subject: [PATCH 104/548] git-gui: Make initial commits work properly. Apparently I never really tested the logic for making or amending an initial commit, so although most of the code was here in git-gui it didn't quite work as it was intended to. So this is all just bug fixes to make initial commits correctly generate the list of files going into the initial commit, or to show a newly added file's diff, and to amend an initial commit. Because we really want to diff the index against a tree-ish and there is no such tree-ish on an initial commit we create an empty tree through git-mktree and diff against that. This unfortunately creates a dangling tree, which may confuse a new user who uses git-gui to make a new commit and then immediately afterwards runs git fsck-objects to see if their object database is corrupt or not. Signed-off-by: Shawn O. Pearce --- git-gui | 74 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/git-gui b/git-gui index 12a46e976c..e2e0beae95 100755 --- a/git-gui +++ b/git-gui @@ -205,6 +205,7 @@ set index_lock_type none set HEAD {} set PARENT {} set commit_type {} +set empty_tree {} proc lock_index {type} { global index_lock_type disable_on_lock @@ -240,6 +241,7 @@ proc repository_state {hdvar ctvar} { upvar $hdvar hd $ctvar ct if {[catch {set hd [exec git rev-parse --verify HEAD]}]} { + set hd {} set ct initial } elseif {[file exists [file join $gitdir MERGE_HEAD]]} { set ct merge @@ -248,6 +250,18 @@ proc repository_state {hdvar ctvar} { } } +proc PARENT {} { + global PARENT empty_tree + + if {$PARENT ne {}} { + return $PARENT + } + if {$empty_tree eq {}} { + set empty_tree [exec git mktree << {}] + } + return $empty_tree +} + proc rescan {after} { global HEAD PARENT commit_type global ui_index ui_other ui_status_value ui_comm @@ -257,7 +271,7 @@ proc rescan {after} { if {$rescan_active > 0 || ![lock_index read]} return repository_state new_HEAD new_type - if {$commit_type eq {amend} + if {[string match amend* $commit_type] && $new_type eq {normal} && $new_HEAD eq $HEAD} { } else { @@ -296,10 +310,8 @@ proc rescan {after} { } proc rescan_stage2 {fd after} { - global gitdir PARENT commit_type - global ui_index ui_other ui_status_value ui_comm - global rescan_active - global buf_rdi buf_rdf buf_rlo + global gitdir ui_status_value + global rescan_active buf_rdi buf_rdf buf_rlo if {$fd ne {}} { read $fd @@ -320,7 +332,7 @@ proc rescan_stage2 {fd after} { set rescan_active 3 set ui_status_value {Scanning for modified files ...} - set fd_di [open "| git diff-index --cached -z $PARENT" r] + set fd_di [open "| git diff-index --cached -z [PARENT]" r] set fd_df [open "| git diff-files -z" r] set fd_lo [open $ls_others r] @@ -532,7 +544,7 @@ files list, to prevent possible confusion. proc show_diff {path {w {}} {lno {}}} { global file_states file_lists - global PARENT diff_3way diff_active repo_config + global diff_3way diff_active repo_config global ui_diff current_diff ui_status_value if {$diff_active || ![lock_index read]} return @@ -591,7 +603,7 @@ proc show_diff {path {w {}} {lno {}}} { } } - lappend cmd $PARENT + lappend cmd [PARENT] lappend cmd -- lappend cmd $path @@ -671,7 +683,7 @@ proc read_diff {fd} { proc load_last_commit {} { global HEAD PARENT commit_type ui_comm - if {$commit_type eq {amend}} return + if {[string match amend* $commit_type]} return if {$commit_type ne {normal}} { error_popup "Can't amend a $commit_type commit." return @@ -695,23 +707,24 @@ proc load_last_commit {} { return } + if {$parent_count > 1} { + error_popup {Can't amend a merge commit.} + return + } + if {$parent_count == 0} { - set commit_type amend - set HEAD {} + set commit_type amend-initial set PARENT {} - rescan {set ui_status_value {Ready.}} } elseif {$parent_count == 1} { set commit_type amend set PARENT $parent - $ui_comm delete 0.0 end - $ui_comm insert end $msg - $ui_comm edit modified false - $ui_comm edit reset - rescan {set ui_status_value {Ready.}} - } else { - error_popup {You can't amend a merge commit.} - return } + + $ui_comm delete 0.0 end + $ui_comm insert end $msg + $ui_comm edit modified false + $ui_comm edit reset + rescan {set ui_status_value {Ready.}} } proc commit_tree {} { @@ -722,7 +735,7 @@ proc commit_tree {} { # -- Our in memory state should match the repository. # repository_state curHEAD cur_type - if {$commit_type eq {amend} + if {[string match amend* $commit_type] && $cur_type eq {normal} && $curHEAD eq $HEAD} { } elseif {$commit_type ne $cur_type || $HEAD ne $curHEAD} { @@ -2559,13 +2572,18 @@ label $ui_coml -text {Commit Message:} \ -anchor w \ -justify left \ -font font_ui -trace add variable commit_type write {uplevel #0 { - switch -glob $commit_type \ - initial {$ui_coml conf -text {Initial Commit Message:}} \ - amend {$ui_coml conf -text {Amended Commit Message:}} \ - merge {$ui_coml conf -text {Merge Commit Message:}} \ - * {$ui_coml conf -text {Commit Message:}} -}} +proc trace_commit_type {varname args} { + global ui_coml commit_type + switch -glob -- $commit_type { + initial {set txt {Initial Commit Message:}} + amend {set txt {Amended Commit Message:}} + amend-initial {set txt {Amended Initial Commit Message:}} + merge {set txt {Merge Commit Message:}} + * {set txt {Commit Message:}} + } + $ui_coml conf -text $txt +} +trace add variable commit_type write trace_commit_type text $ui_comm -background white -borderwidth 1 \ -undo true \ -maxundo 20 \ From 32e0bcab59334ed8f1955c8afa830de22362c1ec Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 03:03:16 -0500 Subject: [PATCH 105/548] git-gui: Only reshow diff when really necessary. I noticed that we were reshowing the current diff during a commit; this occurs because we feed every added and modified file through update-index just before commit. During the update-index process we reshow the current diff if the current file in the diff pane was one of those added or modified files we reprocessed. This just slows down the UI more than is necessary. So refactoring update_index so that we don't call reshow_diff from within that code; instead we do it at a higher level. Signed-off-by: Shawn O. Pearce --- git-gui | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/git-gui b/git-gui index e2e0beae95..62545b7094 100755 --- a/git-gui +++ b/git-gui @@ -1251,12 +1251,11 @@ proc display_all_files {} { } proc update_index {msg pathList after} { - global update_index_cp update_index_rsd ui_status_value + global update_index_cp ui_status_value if {![lock_index update]} return set update_index_cp 0 - set update_index_rsd 0 set pathList [lsort $pathList] set totalCnt [llength $pathList] set batch [expr {int($totalCnt * .01) + 1}] @@ -1285,13 +1284,12 @@ proc update_index {msg pathList after} { } proc write_update_index {fd pathList totalCnt batch msg after} { - global update_index_cp update_index_rsd ui_status_value + global update_index_cp ui_status_value global file_states current_diff if {$update_index_cp >= $totalCnt} { close $fd unlock_index - if {$update_index_rsd} reshow_diff uplevel #0 $after return } @@ -1321,9 +1319,6 @@ proc write_update_index {fd pathList totalCnt batch msg after} { puts -nonewline $fd $path puts -nonewline $fd "\0" display_file $path $new - if {$current_diff eq $path} { - set update_index_rsd 1 - } } set ui_status_value [format \ @@ -1859,19 +1854,23 @@ proc do_rescan {} { } proc do_include_all {} { - global file_states + global file_states current_diff if {![lock_index begin-update]} return set pathList [list] + set after {} foreach path [array names file_states] { - set s $file_states($path) - set m [lindex $s 0] - switch -- $m { + switch -- [lindex $file_states($path) 0] { AM - MM - _M - - _D {lappend pathList $path} + _D { + lappend pathList $path + if {$path eq $current_diff} { + set after {reshow_diff;} + } + } } } if {$pathList eq {}} { @@ -1880,7 +1879,7 @@ proc do_include_all {} { update_index \ "Including all modified files" \ $pathList \ - {set ui_status_value {Ready to commit.}} + [concat $after {set ui_status_value {Ready to commit.}}] } } @@ -2204,7 +2203,7 @@ proc do_macosx_app {} { } proc toggle_or_diff {w x y} { - global file_lists ui_index ui_other + global file_lists current_diff ui_index ui_other global last_clicked selected_paths set pos [split [$w index @$x,$y] .] @@ -2222,10 +2221,15 @@ proc toggle_or_diff {w x y} { $ui_other tag remove in_sel 0.0 end if {$col == 0} { + if {$current_diff eq $path} { + set after {reshow_diff;} + } else { + set after {} + } update_index \ "Including [short_path $path]" \ [list $path] \ - {set ui_status_value {Ready.}} + [concat $after {set ui_status_value {Ready.}}] } else { show_diff $path $w $lno } @@ -2642,7 +2646,7 @@ bind_button3 $ui_comm "tk_popup $ctxm %X %Y" # -- Diff Header set current_diff {} set diff_actions [list] -proc current_diff_trace {varname args} { +proc trace_current_diff {varname args} { global current_diff diff_actions file_states if {$current_diff eq {}} { set s {} @@ -2664,7 +2668,7 @@ proc current_diff_trace {varname args} { uplevel #0 $w $o } } -trace add variable current_diff write current_diff_trace +trace add variable current_diff write trace_current_diff frame .vpane.lower.diff.header -background orange label .vpane.lower.diff.header.status \ From b67651129897738496a9dcd3c01129a42acd11db Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 03:08:51 -0500 Subject: [PATCH 106/548] git-gui: Refactor file state representations. It just felt wrong to me that I was using _ as part of the mode argument to display_file to mean "don't care/use existing" and * as part of the mode argument to mean "force to _". So instead use ? to mean "don't care/use existing" and _ to mean "force to _". The code is a lot clearer this way and hopefully it won't drive another developer insane, as it did me. Signed-off-by: Shawn O. Pearce --- git-gui | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/git-gui b/git-gui index 62545b7094..901d93236e 100755 --- a/git-gui +++ b/git-gui @@ -378,7 +378,7 @@ proc read_diff_index {fd after} { incr z2 -1 display_file \ [string range $buf_rdi $z1 $z2] \ - [string index $buf_rdi [expr {$z1 - 2}]]_ + [string index $buf_rdi [expr {$z1 - 2}]]? incr c } if {$c < $n} { @@ -407,7 +407,7 @@ proc read_diff_files {fd after} { incr z2 -1 display_file \ [string range $buf_rdf $z1 $z2] \ - _[string index $buf_rdf [expr {$z1 - 2}]] + ?[string index $buf_rdf [expr {$z1 - 2}]] incr c } if {$c < $n} { @@ -426,7 +426,7 @@ proc read_ls_others {fd after} { set pck [split $buf_rlo "\0"] set buf_rlo [lindex $pck end] foreach p [lrange $pck 0 end-1] { - display_file $p _O + display_file $p ?O } rescan_done $fd buf_rlo $after } @@ -1151,15 +1151,15 @@ proc merge_state {path new_state} { set icon [lindex $info 1] } - if {$s0 eq {_}} { + if {$s0 eq {?}} { set s0 [string index $state 0] - } elseif {$s0 eq {*}} { + } elseif {$s0 eq {_}} { set s0 _ } - if {$s1 eq {_}} { + if {$s1 eq {?}} { set s1 [string index $state 1] - } elseif {$s1 eq {*}} { + } elseif {$s1 eq {_}} { set s1 _ } @@ -1303,15 +1303,15 @@ proc write_update_index {fd pathList totalCnt batch msg after} { switch -glob -- [lindex $file_states($path) 0] { AD - MD - - _D {set new D*} + _D {set new D_} _M - MM - - M_ {set new M*} + M_ {set new M_} _O - AM - - A_ {set new A*} + A_ {set new A_} ?? {continue} } From c4ed879fc2b64b6c6f8a42bd156b2e4bd18e9420 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 03:24:20 -0500 Subject: [PATCH 107/548] git-gui: Add menu option to include only selected files. When the user selects a number of files they would typically expect to be able to act on that selection, such as by including those files into the next commit. So we now have a menu option under the Commit menu that lets the user include only the selection, rather than everything. If there is no selection but there is a file in the diff viewer than we consider that to be the selection (a selection of 1). Unfortunately we don't disable this option yet when there's nothing selected to include, but this is probably not a big deal as there are very few situations where there are no selected files. Signed-off-by: Shawn O. Pearce --- git-gui | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/git-gui b/git-gui index 901d93236e..a020895020 100755 --- a/git-gui +++ b/git-gui @@ -1853,14 +1853,14 @@ proc do_rescan {} { rescan {set ui_status_value {Ready.}} } -proc do_include_all {} { +proc include_helper {txt paths} { global file_states current_diff if {![lock_index begin-update]} return set pathList [list] set after {} - foreach path [array names file_states] { + foreach path $paths { switch -- [lindex $file_states($path) 0] { AM - MM - @@ -1877,12 +1877,33 @@ proc do_include_all {} { unlock_index } else { update_index \ - "Including all modified files" \ + $txt \ $pathList \ [concat $after {set ui_status_value {Ready to commit.}}] } } +proc do_include_selection {} { + global current_diff selected_paths + + if {[array size selected_paths] > 0} { + include_helper \ + {Including selected files} \ + [array names selected_paths] + } elseif {$current_diff ne {}} { + include_helper \ + "Including [short_path $current_diff]" \ + [list $current_diff] + } +} + +proc do_include_all {} { + global file_states + include_helper \ + {Including all modified files} \ + [array names file_states] +} + set GIT_COMMITTER_IDENT {} proc do_signoff {} { @@ -2442,6 +2463,11 @@ lappend disable_on_lock \ -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] +.mbar.commit add command -label {Include Selected Files} \ + -command do_include_selection \ + -font font_ui +lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Include All Files} \ -command do_include_all \ -accelerator $M1T-I \ From a49c67d1ff9861d80bae57642d891016ea5debd5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 03:27:23 -0500 Subject: [PATCH 108/548] git-gui: Misc. comment formatting cleanups. Signed-off-by: Shawn O. Pearce --- git-gui | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/git-gui b/git-gui index a020895020..c2b4dd7067 100755 --- a/git-gui +++ b/git-gui @@ -202,11 +202,6 @@ set last_clicked {} set disable_on_lock [list] set index_lock_type none -set HEAD {} -set PARENT {} -set commit_type {} -set empty_tree {} - proc lock_index {type} { global index_lock_type disable_on_lock @@ -2374,6 +2369,7 @@ apply_config ## ui construction # -- Menu Bar +# menu .mbar -tearoff 0 .mbar add cascade -label Project -menu .mbar.project .mbar add cascade -label Edit -menu .mbar.edit @@ -2386,6 +2382,7 @@ if {!$single_commit} { . configure -menu .mbar # -- Project Menu +# menu .mbar.project .mbar.project add command -label Visualize \ -command do_gitk \ @@ -2451,6 +2448,7 @@ menu .mbar.edit -font font_ui # -- Commit Menu +# menu .mbar.commit .mbar.commit add command -label Rescan \ -command do_rescan \ @@ -2485,24 +2483,23 @@ lappend disable_on_lock \ lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] +# -- Transport menus +# if {!$single_commit} { - # -- Fetch Menu menu .mbar.fetch - - # -- Pull Menu menu .mbar.pull - - # -- Push Menu menu .mbar.push } # -- Main Window Layout +# panedwindow .vpane -orient vertical panedwindow .vpane.files -orient horizontal .vpane add .vpane.files -sticky nsew -height 100 -width 400 pack .vpane -anchor n -side top -fill both -expand 1 # -- Index File List +# frame .vpane.files.index -height 100 -width 400 label .vpane.files.index.title -text {Modified Files} \ -background green \ @@ -2520,6 +2517,7 @@ pack $ui_index -side left -fill both -expand 1 .vpane.files add .vpane.files.index -sticky nsew # -- Other (Add) File List +# frame .vpane.files.other -height 100 -width 100 label .vpane.files.other.title -text {Untracked Files} \ -background red \ @@ -2545,6 +2543,7 @@ foreach i [list $ui_index $ui_other] { unset i # -- Diff and Commit Area +# frame .vpane.lower -height 300 -width 400 frame .vpane.lower.commarea frame .vpane.lower.diff -relief sunken -borderwidth 1 @@ -2553,6 +2552,7 @@ pack .vpane.lower.diff -side bottom -fill both -expand 1 .vpane add .vpane.lower -stick nsew # -- Commit Area Buttons +# frame .vpane.lower.commarea.buttons label .vpane.lower.commarea.buttons.l -text {} \ -anchor w \ @@ -2595,10 +2595,11 @@ lappend disable_on_lock \ {.vpane.lower.commarea.buttons.commit conf -state} # -- Commit Message Buffer +# frame .vpane.lower.commarea.buffer set ui_comm .vpane.lower.commarea.buffer.t set ui_coml .vpane.lower.commarea.buffer.l -label $ui_coml -text {Commit Message:} \ +label $ui_coml \ -anchor w \ -justify left \ -font font_ui @@ -2670,6 +2671,7 @@ $ctxm add command \ bind_button3 $ui_comm "tk_popup $ctxm %X %Y" # -- Diff Header +# set current_diff {} set diff_actions [list] proc trace_current_diff {varname args} { @@ -2732,6 +2734,7 @@ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y" # -- Diff Body +# frame .vpane.lower.diff.body set ui_diff .vpane.lower.diff.body.t text $ui_diff -background white -borderwidth 0 \ @@ -2888,6 +2891,11 @@ unset i set file_lists($ui_index) [list] set file_lists($ui_other) [list] + +set HEAD {} +set PARENT {} +set commit_type {} +set empty_tree {} set current_diff {} wm title . "$appname ([file normalize [file dirname $gitdir]])" From 53716a7bc94497cd6d2dfd53b1e4f7669d677b01 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 03:31:25 -0500 Subject: [PATCH 109/548] git-gui: Start UI with the index locked. Because we immediately start a rescan operation, but do so slightly delayed (by 1 ms, to let the UI show before we start forking off git processes), we can't let the user try to activate any of the restricted GUI commands before the 1 ms timer expires and we kick off the rescan. So now we lock the index before we enter the Tk event loop, ensuring that it is impossible for the user to inject a conflicting UI event before our rescan can begin. Signed-off-by: Shawn O. Pearce --- git-gui | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git-gui b/git-gui index c2b4dd7067..4850e78ab0 100755 --- a/git-gui +++ b/git-gui @@ -211,7 +211,7 @@ proc lock_index {type} { uplevel #0 $w disabled } return 1 - } elseif {$index_lock_type eq {begin-update} && $type eq {update}} { + } elseif {$index_lock_type eq "begin-$type"} { set index_lock_type $type return 1 } @@ -2906,4 +2906,5 @@ if {!$single_commit} { populate_pull_menu .mbar.pull populate_push_menu .mbar.push } +lock_index begin-read after 1 do_rescan From ef5c971506bef5046f1a50e1bfb2630cf458afff Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 03:41:54 -0500 Subject: [PATCH 110/548] git-gui: Remove completed items from TODO list. Signed-off-by: Shawn O. Pearce --- TODO | 4 ---- 1 file changed, 4 deletions(-) diff --git a/TODO b/TODO index bb40a13bee..e9525cda3a 100644 --- a/TODO +++ b/TODO @@ -6,12 +6,8 @@ Items outstanding: * Update index with $PARENT version but leave working directory alone. - * Add all new files in one shot (e.g. "git add ."). - * Add file to .gitignore or info/excludes. - * Make initial commits work (currently it crashes the UI). - * Populate the pull menu with local branches. * Make use of the new default merge data stored in repo-config. From 24ac9b752d6df7ab309826d487fbeaae53806025 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 20:59:49 -0500 Subject: [PATCH 111/548] git-gui: Toggle between new commit and amend commit modes. I was starting to find it annoying that once you entered the 'Amend Last' mode there was no way to go back to the 'New Commit' mode without quitting and restarting git-gui. Its just confusing for the end-user. Now we can flip back and forth between a new commit and an amend commit through a pair of radio buttons on the header of the commit buffer area and through a pair of radio menu buttons in the Commit menu. Signed-off-by: Shawn O. Pearce --- git-gui | 97 ++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 18 deletions(-) diff --git a/git-gui b/git-gui index 4850e78ab0..5c6af8940e 100755 --- a/git-gui +++ b/git-gui @@ -722,6 +722,16 @@ proc load_last_commit {} { 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 modified false + $ui_comm edit reset + rescan {set ui_status_value {Ready.}} +} + proc commit_tree {} { global HEAD commit_type file_states ui_comm repo_config @@ -880,7 +890,7 @@ proc commit_writetree {curHEAD msg} { proc commit_committree {fd_wt curHEAD msg} { global single_commit gitdir HEAD PARENT commit_type tcl_platform - global ui_status_value ui_comm + global ui_status_value ui_comm selected_commit_type global file_states selected_paths gets $fd_wt tree_id @@ -980,6 +990,7 @@ proc commit_committree {fd_wt curHEAD msg} { # -- Update status without invoking any git commands. # set commit_type normal + set selected_commit_type new set HEAD $cmt_id set PARENT $cmt_id @@ -1065,13 +1076,14 @@ Commit or throw away all changes before starting a pull operation. } proc post_pull_remote {remote branch success} { - global HEAD PARENT commit_type + global HEAD PARENT commit_type selected_commit_type global ui_status_value unlock_index if {$success} { repository_state HEAD commit_type set PARENT $HEAD + set selected_commit_type new set $ui_status_value "Pulling $branch from $remote complete." } else { set m "Conflicts detected while pulling $branch from $remote." @@ -1930,8 +1942,22 @@ proc do_signoff {} { } } -proc do_amend_last {} { - load_last_commit +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 commit_type new + } + } } proc do_commit {} { @@ -2450,32 +2476,52 @@ menu .mbar.edit # -- Commit Menu # menu .mbar.commit + +.mbar.commit add radiobutton \ + -label {New Commit} \ + -command do_select_commit_type \ + -variable selected_commit_type \ + -value new \ + -font font_ui +lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + +.mbar.commit add radiobutton \ + -label {Amend Last Commit} \ + -command do_select_commit_type \ + -variable selected_commit_type \ + -value amend \ + -font font_ui +lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + +.mbar.commit add separator + .mbar.commit add command -label Rescan \ -command do_rescan \ -accelerator F5 \ -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] -.mbar.commit add command -label {Amend Last Commit} \ - -command do_amend_last \ - -font font_ui -lappend disable_on_lock \ - [list .mbar.commit entryconf [.mbar.commit index last] -state] + .mbar.commit add command -label {Include Selected Files} \ -command do_include_selection \ -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] + .mbar.commit add command -label {Include All Files} \ -command do_include_all \ -accelerator $M1T-I \ -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] + .mbar.commit add command -label {Sign Off} \ -command do_signoff \ -accelerator $M1T-S \ -font font_ui + .mbar.commit add command -label Commit \ -command do_commit \ -accelerator $M1T-Return \ @@ -2568,13 +2614,6 @@ 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.amend -text {Amend Last} \ - -command do_amend_last \ - -font font_ui -pack .vpane.lower.commarea.buttons.amend -side top -fill x -lappend disable_on_lock \ - {.vpane.lower.commarea.buttons.amend conf -state} - button .vpane.lower.commarea.buttons.incall -text {Include All} \ -command do_include_all \ -font font_ui @@ -2597,8 +2636,25 @@ lappend disable_on_lock \ # -- Commit Message Buffer # frame .vpane.lower.commarea.buffer +frame .vpane.lower.commarea.buffer.header set ui_comm .vpane.lower.commarea.buffer.t -set ui_coml .vpane.lower.commarea.buffer.l +set ui_coml .vpane.lower.commarea.buffer.header.l +radiobutton .vpane.lower.commarea.buffer.header.new \ + -text {New Commit} \ + -command do_select_commit_type \ + -variable selected_commit_type \ + -value new \ + -font font_ui +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 +lappend disable_on_lock \ + [list .vpane.lower.commarea.buffer.header.amend conf -state] label $ui_coml \ -anchor w \ -justify left \ @@ -2615,6 +2671,10 @@ proc trace_commit_type {varname args} { $ui_coml conf -text $txt } trace add variable commit_type write trace_commit_type +pack $ui_coml -side left -fill x +pack .vpane.lower.commarea.buffer.header.amend -side right +pack .vpane.lower.commarea.buffer.header.new -side right + text $ui_comm -background white -borderwidth 1 \ -undo true \ -maxundo 20 \ @@ -2625,7 +2685,7 @@ text $ui_comm -background white -borderwidth 1 \ -yscrollcommand {.vpane.lower.commarea.buffer.sby set} scrollbar .vpane.lower.commarea.buffer.sby \ -command [list $ui_comm yview] -pack $ui_coml -side top -fill x +pack .vpane.lower.commarea.buffer.header -side top -fill x pack .vpane.lower.commarea.buffer.sby -side right -fill y pack $ui_comm -side left -fill y pack .vpane.lower.commarea.buffer -side left -fill y @@ -2897,6 +2957,7 @@ set PARENT {} set commit_type {} set empty_tree {} set current_diff {} +set selected_commit_type new wm title . "$appname ([file normalize [file dirname $gitdir]])" focus -force $ui_comm From d63efae2816a277e40a379eb5c5f23f2a656fddc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 21:07:05 -0500 Subject: [PATCH 112/548] git-gui: Verify the user has GIT_COMMITTER_IDENT before comitting. Since git-commit also checks that the user has a GIT_COMMITTER_IDENT value before it lets the user make a commit we should do the same check here in git-gui. We cache the result and assume that the user won't do something which would change the status of GIT_COMMITTER_IDENT while we are running. Signed-off-by: Shawn O. Pearce --- git-gui | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/git-gui b/git-gui index 5c6af8940e..7630a2d2e7 100755 --- a/git-gui +++ b/git-gui @@ -732,10 +732,31 @@ proc create_new_commit {} { 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 [exec 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 if {![lock_index update]} return + if {[committer_ident] eq {}} return # -- Our in memory state should match the repository. # @@ -1911,24 +1932,13 @@ proc do_include_all {} { [array names file_states] } -set GIT_COMMITTER_IDENT {} - proc do_signoff {} { - global ui_comm GIT_COMMITTER_IDENT + global ui_comm - if {$GIT_COMMITTER_IDENT eq {}} { - if {[catch {set me [exec 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 - } - } + set me [committer_ident] + if {$me eq {}} return - set sob "Signed-off-by: $GIT_COMMITTER_IDENT" + set sob "Signed-off-by: $me" set last [$ui_comm get {end -1c linestart} {end -1c}] if {$last ne $sob} { $ui_comm edit separator From bca680b054ffa736e876075fad358bee1caa898a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 21:13:16 -0500 Subject: [PATCH 113/548] git-gui: Rephrase rescan before commit informational message. Its not an error that a rescan is required before commit; its just something we do as a safety feature to try and ensure the user knows what is going into this commit. So the dialog should use the info icon (if one is used by the host OS) rather than the error icon. Its also not "highly likely" that another Git program modified the repository, its completely the case. There is no reason why the repository would not match our last scanned state unless another Git program modified the repository (or someone else did so by hand). So don't be vague about it, own up to the issue and go on with our business. Signed-off-by: Shawn O. Pearce --- git-gui | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/git-gui b/git-gui index 7630a2d2e7..23efe4742b 100755 --- a/git-gui +++ b/git-gui @@ -765,13 +765,13 @@ proc commit_tree {} { && $cur_type eq {normal} && $curHEAD eq $HEAD} { } elseif {$commit_type ne $cur_type || $HEAD ne $curHEAD} { - error_popup {Last scanned state does not match repository state. + info_popup {Last scanned state does not match repository state. -Its highly likely that another Git program modified the -repository since the last scan. A rescan is required -before committing. +Another Git program has modified this repository +since the last scan. A rescan must be performed +before another commit can be created. -A rescan will be automatically started now. +The rescan will be automatically started now. } unlock_index rescan {set ui_status_value {Ready.}} From 54896cf7c150038bd286b909897673f85c691abc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 21:33:04 -0500 Subject: [PATCH 114/548] git-gui: Allow adding untracked files in selection. The previous implementation of do_include_selection did not actually add files in state _O (untracked, not added) into the repository when they were in the selection and Commit->Include Selected Files was used. This was due to the file state filtering logic being the same as that of Commit->Include All Files, which only considers existing files. Also fixed a minor issue with rejected attempts to amend an initial commit. Signed-off-by: Shawn O. Pearce --- git-gui | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/git-gui b/git-gui index 23efe4742b..d1054f6632 100755 --- a/git-gui +++ b/git-gui @@ -1891,9 +1891,13 @@ proc include_helper {txt paths} { foreach path $paths { switch -- [lindex $file_states($path) 0] { AM - + AD - MM - + UM - + U_ - _M - - _D { + _D - + _O { lappend pathList $path if {$path eq $current_diff} { set after {reshow_diff;} @@ -1927,9 +1931,20 @@ proc do_include_selection {} { proc do_include_all {} { global file_states + + set paths [list] + foreach path [array names file_states] { + switch -- [lindex $file_states($path) 0] { + AM - + AD - + MM - + _M - + _D {lappend paths $path} + } + } include_helper \ {Including all modified files} \ - [array names file_states] + $paths } proc do_signoff {} { @@ -1965,7 +1980,7 @@ proc do_select_commit_type {} { # The amend request was rejected... # if {![string match amend* $commit_type]} { - set commit_type new + set selected_commit_type new } } } From d7c0d7c86191925dcd0dfff353823f9f7e473de5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Nov 2006 23:17:41 -0500 Subject: [PATCH 115/548] git-gui: Don't create PkgInfo on Mac OS X "desktop icons". Turns out that we really don't need the Contents/PkgInfo file on Mac OS 10.4. The Finder will still launch the application properly without one. Signed-off-by: Shawn O. Pearce --- git-gui | 4 ---- 1 file changed, 4 deletions(-) diff --git a/git-gui b/git-gui index d1054f6632..063d83c124 100755 --- a/git-gui +++ b/git-gui @@ -2216,10 +2216,6 @@ proc do_macosx_app {} { file mkdir $MacOS - set fd [open [file join $Contents PkgInfo] w] - puts -nonewline $fd {APPL????} - close $fd - set fd [open [file join $Contents Info.plist] w] puts $fd { From 1461c5f3d0634f6fad0013eba1ef1087a38cc748 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 19 Nov 2006 00:29:55 -0500 Subject: [PATCH 116/548] git-gui: Teach the gui how to uninclude a file. Sometimes the user may want to keep their working directory file to be the same content but they don't want it to be part of the current commit anymore. In this case we need to undo any changes made to the index for that file (by reloading the info from HEAD or removing the file from the index) but leave the working directory alone. Signed-off-by: Shawn O. Pearce --- git-gui | 187 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 158 insertions(+), 29 deletions(-) diff --git a/git-gui b/git-gui index 063d83c124..6b886c64ce 100755 --- a/git-gui +++ b/git-gui @@ -1,4 +1,3 @@ -#!/bin/sh # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" @@ -369,12 +368,14 @@ proc read_diff_index {fd after} { set z2 [string first "\0" $buf_rdi $z1] if {$z2 == -1} break - set c $z2 - incr z2 -1 - display_file \ - [string range $buf_rdi $z1 $z2] \ - [string index $buf_rdi [expr {$z1 - 2}]]? incr c + set n [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }] + merge_state \ + [string range $buf_rdi $z1 [expr {$z2 - 1}]] \ + [lindex $n 4]? \ + [list [lindex $n 0] [lindex $n 2]] \ + [list] + set c $z2 } if {$c < $n} { set buf_rdi [string range $buf_rdi $c end] @@ -398,12 +399,14 @@ proc read_diff_files {fd after} { set z2 [string first "\0" $buf_rdf $z1] if {$z2 == -1} break - set c $z2 - incr z2 -1 - display_file \ - [string range $buf_rdf $z1 $z2] \ - ?[string index $buf_rdf [expr {$z1 - 2}]] incr c + set n [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }] + merge_state \ + [string range $buf_rdf $z1 [expr {$z2 - 1}]] \ + ?[lindex $n 4] \ + [list] \ + [list [lindex $n 0] [lindex $n 2]] + set c $z2 } if {$c < $n} { set buf_rdf [string range $buf_rdf $c end] @@ -421,7 +424,7 @@ proc read_ls_others {fd after} { set pck [split $buf_rlo "\0"] set buf_rlo [lindex $pck end] foreach p [lrange $pck 0 end-1] { - display_file $p ?O + merge_state $p ?O } rescan_done $fd buf_rlo $after } @@ -1165,7 +1168,7 @@ proc short_path {path} { set next_icon_id 0 -proc merge_state {path new_state} { +proc merge_state {path new_state {head_info {}} {index_info {}}} { global file_states next_icon_id set s0 [string index $new_state 0] @@ -1177,30 +1180,31 @@ proc merge_state {path new_state} { } else { set state [lindex $info 0] set icon [lindex $info 1] + if {$head_info eq {}} {set head_info [lindex $info 2]} + if {$index_info eq {}} {set index_info [lindex $info 3]} } - if {$s0 eq {?}} { - set s0 [string index $state 0] - } elseif {$s0 eq {_}} { - set s0 _ + if {$s0 eq {?}} {set s0 [string index $state 0]} \ + elseif {$s0 eq {_}} {set s0 _} + + if {$s1 eq {?}} {set s1 [string index $state 1]} \ + elseif {$s1 eq {_}} {set s1 _} + + if {$s0 ne {_} && [string index $state 0] eq {_} + && $head_info eq {}} { + set head_info $index_info } - if {$s1 eq {?}} { - set s1 [string index $state 1] - } elseif {$s1 eq {_}} { - set s1 _ - } - - set file_states($path) [list $s0$s1 $icon] + set file_states($path) [list $s0$s1 $icon \ + $head_info $index_info \ + ] return $state } proc display_file {path state} { - global file_states file_lists selected_paths rescan_active + global file_states file_lists selected_paths set old_m [merge_state $path $state] - if {$rescan_active > 0} return - set s $file_states($path) set new_m [lindex $s 0] set new_w [mapcol $new_m $path] @@ -1278,6 +1282,80 @@ proc display_all_files {} { $ui_other 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 \ + -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 + + 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 _?} + ?? {continue} + } + set info [lindex $s 2] + if {$info eq {}} continue + + puts -nonewline $fd $info + puts -nonewline $fd "\t" + puts -nonewline $fd $path + puts -nonewline $fd "\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 @@ -1881,6 +1959,49 @@ proc do_rescan {} { rescan {set ui_status_value {Ready.}} } +proc remove_helper {txt paths} { + global file_states current_diff + + 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} { + set after {reshow_diff;} + } + } + } + } + if {$pathList eq {}} { + unlock_index + } else { + update_indexinfo \ + $txt \ + $pathList \ + [concat $after {set ui_status_value {Ready.}}] + } +} + +proc do_remove_selection {} { + global current_diff selected_paths + + if {[array size selected_paths] > 0} { + remove_helper \ + {Removing selected files from commit} \ + [array names selected_paths] + } elseif {$current_diff ne {}} { + remove_helper \ + "Removing [short_path $current_diff] from commit" \ + [list $current_diff] + } +} + proc include_helper {txt paths} { global file_states current_diff @@ -2525,19 +2646,27 @@ lappend disable_on_lock \ lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] -.mbar.commit add command -label {Include Selected Files} \ +.mbar.commit add command -label {Remove From Commit} \ + -command do_remove_selection \ + -font font_ui +lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + +.mbar.commit add command -label {Include In Commit} \ -command do_include_selection \ -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] -.mbar.commit add command -label {Include All Files} \ +.mbar.commit add command -label {Include All} \ -command do_include_all \ -accelerator $M1T-I \ -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] +.mbar.commit add separator + .mbar.commit add command -label {Sign Off} \ -command do_signoff \ -accelerator $M1T-S \ From 74d18d2edfc84d35b20e1b4520e496aadc14c365 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 19 Nov 2006 00:37:49 -0500 Subject: [PATCH 117/548] git-gui: Make consecutive icon clicks toggle included status of a file. If the user clicks on the icon associated with a file we now flip to the inverse status. Partially included files first fully include, then fully uninclude, as we don't keep track of intermediate partial inclusions. Signed-off-by: Shawn O. Pearce --- git-gui | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/git-gui b/git-gui index 6b886c64ce..7126f8d897 100755 --- a/git-gui +++ b/git-gui @@ -2387,7 +2387,7 @@ proc do_macosx_app {} { } proc toggle_or_diff {w x y} { - global file_lists current_diff ui_index ui_other + global file_states file_lists current_diff ui_index ui_other global last_clicked selected_paths set pos [split [$w index @$x,$y] .] @@ -2410,10 +2410,23 @@ proc toggle_or_diff {w x y} { } else { set after {} } - update_index \ - "Including [short_path $path]" \ - [list $path] \ - [concat $after {set ui_status_value {Ready.}}] + switch -glob -- [lindex $file_states($path) 0] { + A_ - + AO - + M_ - + D_ { + update_indexinfo \ + "Removing [short_path $path] from commit" \ + [list $path] \ + [concat $after {set ui_status_value {Ready.}}] + } + ?? { + update_index \ + "Including [short_path $path]" \ + [list $path] \ + [concat $after {set ui_status_value {Ready.}}] + } + } } else { show_diff $path $w $lno } From dde5974ef109ed3aadfbac4d233899fb04d1c9ff Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 19 Nov 2006 00:46:08 -0500 Subject: [PATCH 118/548] git-gui: Correct toggling of deleted file status. There was a bug with the way we handled deleted file status. A file really shouldn't be in D_ state when it has been deleted, instead it is really DD. Therefore we should have toggled _D to DD, not D_, thereby letting us toggle back to _D. Signed-off-by: Shawn O. Pearce --- git-gui | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/git-gui b/git-gui index 7126f8d897..7ea31e5089 100755 --- a/git-gui +++ b/git-gui @@ -1022,6 +1022,8 @@ proc commit_committree {fd_wt curHEAD msg} { set s $file_states($path) set m [lindex $s 0] switch -glob -- $m { + DD - + AO {set m __} A? - M? - D? {set m _[string index $m 1]} @@ -1409,7 +1411,7 @@ proc write_update_index {fd pathList totalCnt batch msg after} { switch -glob -- [lindex $file_states($path) 0] { AD - MD - - _D {set new D_} + _D {set new DD} _M - MM - @@ -2414,6 +2416,7 @@ proc toggle_or_diff {w x y} { A_ - AO - M_ - + DD - D_ { update_indexinfo \ "Removing [short_path $path] from commit" \ From 86291555c94300b057a156d87239a6cab09511b3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 19 Nov 2006 01:00:48 -0500 Subject: [PATCH 119/548] git-gui: Fix list loading corruption introduced by 1461c5f3. Tcl let me assign two different types of values to the variable $n. Prior to 1461c5f3 $n was the total number of bytes in the string; but in that commit it also became the current info list for the current file. This caused $c < $n to fail as $n was now treated as 0 and we only loaded the first file in each buffer. So use a different variable, like $i, instead. Signed-off-by: Shawn O. Pearce --- git-gui | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/git-gui b/git-gui index 7ea31e5089..bd22125e51 100755 --- a/git-gui +++ b/git-gui @@ -369,13 +369,14 @@ proc read_diff_index {fd after} { if {$z2 == -1} break incr c - set n [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }] + set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }] merge_state \ [string range $buf_rdi $z1 [expr {$z2 - 1}]] \ - [lindex $n 4]? \ - [list [lindex $n 0] [lindex $n 2]] \ + [lindex $i 4]? \ + [list [lindex $i 0] [lindex $i 2]] \ [list] set c $z2 + incr c } if {$c < $n} { set buf_rdi [string range $buf_rdi $c end] @@ -400,13 +401,14 @@ proc read_diff_files {fd after} { if {$z2 == -1} break incr c - set n [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }] + set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }] merge_state \ [string range $buf_rdf $z1 [expr {$z2 - 1}]] \ - ?[lindex $n 4] \ + ?[lindex $i 4] \ [list] \ - [list [lindex $n 0] [lindex $n 2]] + [list [lindex $i 0] [lindex $i 2]] set c $z2 + incr c } if {$c < $n} { set buf_rdf [string range $buf_rdf $c end] From 0d5709cf88e9f242e0e31ccbda42a1c827c90a22 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 19 Nov 2006 01:06:42 -0500 Subject: [PATCH 120/548] git-gui: Describe deleted symlinks in a more friendly way. Currently core-git's diff utilities report a deleted symlink as a deleted file with a mode of 120000. This is not nearly as user friendly as one might like, as the user must remember that 120000 is the UNIX mode bits for a symlink. So instead we transform the not-so-friendly message from core-git into a slightly more user friendly "deleted symlink" message. Signed-off-by: Shawn O. Pearce --- git-gui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git-gui b/git-gui index bd22125e51..3f7e408356 100755 --- a/git-gui +++ b/git-gui @@ -628,6 +628,9 @@ proc read_diff {fd} { if {[string match {diff --combined *} $line]} continue if {[string match {--- *} $line]} continue if {[string match {+++ *} $line]} continue + if {$line eq {deleted file mode 120000}} { + set line "deleted symlink" + } if {[string match index* $line]} { if {[string first , $line] >= 0} { set diff_3way 1 From 51cc47fedaaea46a556aac7d4e32683abca1b57b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 19 Nov 2006 01:20:42 -0500 Subject: [PATCH 121/548] git-gui: Correct toggling of added/untracked status for new files. New files also lack index data from diff-files therefore we cannot use their diff-files index data when we update-index. Instead we can use the fact that Git has them hardcoded as "0 0{40}" and do the same thing ourselves. This way you can toggle an untracked file into added status and back out to untracked. Signed-off-by: Shawn O. Pearce --- git-gui | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/git-gui b/git-gui index 3f7e408356..75d1640c0f 100755 --- a/git-gui +++ b/git-gui @@ -1174,9 +1174,10 @@ proc short_path {path} { } set next_icon_id 0 +set null_sha1 [string repeat 0 40] proc merge_state {path new_state {head_info {}} {index_info {}}} { - global file_states next_icon_id + global file_states next_icon_id null_sha1 set s0 [string index $new_state 0] set s1 [string index $new_state 1] @@ -1197,7 +1198,9 @@ proc merge_state {path new_state {head_info {}} {index_info {}}} { if {$s1 eq {?}} {set s1 [string index $state 1]} \ elseif {$s1 eq {_}} {set s1 _} - if {$s0 ne {_} && [string index $state 0] eq {_} + if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} { + set head_info [list 0 $null_sha1] + } elseif {$s0 ne {_} && [string index $state 0] eq {_} && $head_info eq {}} { set head_info $index_info } From 0c70864b8506ea17cc0615ae4ba55b60fed6af58 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 19 Nov 2006 01:23:06 -0500 Subject: [PATCH 122/548] git-gui: Updated TODO list now that a task is complete. Signed-off-by: Shawn O. Pearce --- TODO | 3 --- 1 file changed, 3 deletions(-) diff --git a/TODO b/TODO index e9525cda3a..d27227c376 100644 --- a/TODO +++ b/TODO @@ -3,9 +3,6 @@ Items outstanding: * Checkout $PARENT version to working directory, overwriting current version. ($PARENT is HEAD, except when amending). - * Update index with $PARENT version but leave working directory - alone. - * Add file to .gitignore or info/excludes. * Populate the pull menu with local branches. From 38dbe273ffb86d8dc5c90403c27c561e683ade4a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 19 Nov 2006 02:46:52 -0500 Subject: [PATCH 123/548] git-gui: Refactored diff line display formatting logic. The tags used for diff formatting (which I inherited from gitool) just didn't make a whole lot of sense, especially if you wanted to try to match them to the diff output you were seeing on screen. It did not help that the diff-index -c output's first two columns are also munged to make the diff output more user friendly. So this is a large refactoring of the tags used for diff display. Now our tag names match what we put in the left column of each line, which makes it easier to correlate presentation and implementation. I removed bold font usage from everything except the hunk headers as I really did not like the way bold font caused column alignments to become out of whack within the diff viewer. It also drew attention to the parts of the file which were identically changed in both the index and in the working directory, yet these are usually the parts I find myself caring the least about. So its very counter-intuitive. Lines which are changed differently by both the index and the working directory are now shown with background colors which span the entire line, making these lines easier to pick out of the diff. In general these are the lines that appear to be more interesting to me when looking at the 3-way diff as they are the ones which contain recent and quite different changes. Signed-off-by: Shawn O. Pearce --- git-gui | 80 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/git-gui b/git-gui index 75d1640c0f..a2a76c11dd 100755 --- a/git-gui +++ b/git-gui @@ -544,7 +544,7 @@ files list, to prevent possible confusion. proc show_diff {path {w {}} {lno {}}} { global file_states file_lists - global diff_3way diff_active repo_config + global is_3way_diff diff_active repo_config global ui_diff current_diff ui_status_value if {$diff_active || ![lock_index read]} return @@ -565,7 +565,7 @@ proc show_diff {path {w {}} {lno {}}} { set s $file_states($path) set m [lindex $s 0] - set diff_3way 0 + set is_3way_diff 0 set diff_active 1 set current_diff $path set ui_status_value "Loading diff of [escape_path $path]..." @@ -620,51 +620,52 @@ proc show_diff {path {w {}} {lno {}}} { } proc read_diff {fd} { - global ui_diff ui_status_value diff_3way diff_active + global ui_diff ui_status_value is_3way_diff diff_active global repo_config + $ui_diff conf -state normal while {[gets $fd line] >= 0} { - if {[string match {diff --git *} $line]} continue + # -- Cleanup uninteresting diff header lines. + # + if {[string match {diff --git *} $line]} continue if {[string match {diff --combined *} $line]} continue - if {[string match {--- *} $line]} continue - if {[string match {+++ *} $line]} continue + if {[string match {--- *} $line]} continue + if {[string match {+++ *} $line]} continue if {$line eq {deleted file mode 120000}} { set line "deleted symlink" } - if {[string match index* $line]} { - if {[string first , $line] >= 0} { - set diff_3way 1 - } - } - $ui_diff conf -state normal - if {!$diff_3way} { - set x [string index $line 0] - switch -- $x { - "@" {set tags da} - "+" {set tags dp} - "-" {set tags dm} + # -- Automatically detect if this is a 3 way diff. + # + if {[string match {@@@ *} $line]} {set is_3way_diff 1} + + # -- Reformat a 3 way diff, 'cause its too weird. + # + if {$is_3way_diff} { + set op [string range $line 0 1] + switch -- $op { + {@@} {set tags d_@} + {++} {set tags d_+ ; set op { +}} + {--} {set tags d_- ; set op { -}} + { +} {set tags d_++; set op {++}} + { -} {set tags d_--; set op {--}} + {+ } {set tags d_-+; set op {-+}} + {- } {set tags d_+-; set op {+-}} default {set tags {}} } + set line [string replace $line 0 1 $op] } else { - set x [string range $line 0 1] - switch -- $x { - default {set tags {}} - "@@" {set tags da} - "++" {set tags dp; set x " +"} - " +" {set tags {di bold}; set x "++"} - "+ " {set tags dni; set x "-+"} - "--" {set tags dm; set x " -"} - " -" {set tags {dm bold}; set x "--"} - "- " {set tags di; set x "+-"} + switch -- [string index $line 0] { + @ {set tags d_@} + + {set tags d_+} + - {set tags d_-} default {set tags {}} } - set line [string replace $line 0 1 $x] } $ui_diff insert end $line $tags - $ui_diff insert end "\n" - $ui_diff conf -state disabled + $ui_diff insert end "\n" $tags } + $ui_diff conf -state disabled if {[eof $fd]} { close $fd @@ -2987,12 +2988,17 @@ pack $ui_diff -side left -fill both -expand 1 pack .vpane.lower.diff.header -side top -fill x pack .vpane.lower.diff.body -side bottom -fill both -expand 1 -$ui_diff tag conf dm -foreground red -$ui_diff tag conf dp -foreground blue -$ui_diff tag conf di -foreground {#00a000} -$ui_diff tag conf dni -foreground {#a000a0} -$ui_diff tag conf da -font font_diffbold -$ui_diff tag conf bold -font font_diffbold +$ui_diff tag conf d_@ -font font_diffbold +$ui_diff tag conf d_+ -foreground blue +$ui_diff tag conf d_- -foreground red +$ui_diff tag conf d_++ -foreground {#00a000} +$ui_diff tag conf d_-- -foreground {#a000a0} +$ui_diff tag conf d_+- \ + -foreground red \ + -background {light goldenrod yellow} +$ui_diff tag conf d_-+ \ + -foreground blue \ + -background azure2 # -- Diff Body Context Menu # From bd11b82db88049fc4822d52764c009cc55b55afd Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 19 Nov 2006 02:57:58 -0500 Subject: [PATCH 124/548] git-gui: Restore the all important shebang line. Accidentally removed by an unnoticed fat finger accident in vi during commit 1461c5f3. Signed-off-by: Shawn O. Pearce --- git-gui | 1 + 1 file changed, 1 insertion(+) diff --git a/git-gui b/git-gui index a2a76c11dd..3f0424a33a 100755 --- a/git-gui +++ b/git-gui @@ -1,3 +1,4 @@ +#!/bin/sh # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" From a29481e2123c2fd14fbcb2531be9de16adf21779 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 19 Nov 2006 03:38:48 -0500 Subject: [PATCH 125/548] git-gui: Update in memory states after commit. In order to allow the user to toggle include/exclude from next commit for files which were partially included in the last commit we need the current index mode+sha1 data stored in our file_states array. For any partially included file we have this information from diff-files, so we just have to copy it over to the diff-index portion of our state array. Signed-off-by: Shawn O. Pearce --- git-gui | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/git-gui b/git-gui index 3f0424a33a..dcb25c8d69 100755 --- a/git-gui +++ b/git-gui @@ -922,7 +922,7 @@ proc commit_writetree {curHEAD msg} { proc commit_committree {fd_wt curHEAD msg} { global single_commit gitdir HEAD PARENT commit_type tcl_platform global ui_status_value ui_comm selected_commit_type - global file_states selected_paths + global file_states selected_paths rescan_active gets $fd_wt tree_id if {$tree_id eq {} || [catch {close $fd_wt} err]} { @@ -1018,7 +1018,7 @@ proc commit_committree {fd_wt curHEAD msg} { if {$single_commit} do_quit - # -- Update status without invoking any git commands. + # -- Update in memory status # set commit_type normal set selected_commit_type new @@ -1029,18 +1029,29 @@ proc commit_committree {fd_wt curHEAD msg} { set s $file_states($path) set m [lindex $s 0] switch -glob -- $m { - DD - - AO {set m __} - A? - - M? - - D? {set m _[string index $m 1]} - } - - if {$m eq {__}} { + _O - + _M - + _D {continue} + __ - + A_ - + M_ - + DD { unset file_states($path) catch {unset selected_paths($path)} - } else { - lset file_states($path) 0 $m + } + DO { + set file_states($path) [list _O [lindex $s 1] {} {}] + } + AM - + AD - + MM - + DM { + set file_states($path) [list \ + _[string index $m 1] \ + [lindex $s 1] \ + [lindex $s 3] \ + {}] + } } } @@ -1661,9 +1672,9 @@ foreach i { {AD o question "Added (but now gone)"} {_D i question "Missing"} - {D_ i removed "Removed by commit"} {DD i removed "Removed by commit"} {DO i removed "Removed (still exists)"} + {DM i removed "Removed (but modified)"} {UM i merge "Merge conflicts"} {U_ i merge "Merge conflicts"} @@ -2424,7 +2435,6 @@ proc toggle_or_diff {w x y} { } switch -glob -- [lindex $file_states($path) 0] { A_ - - AO - M_ - DD - D_ { From 375f38828e9c50ad79b6582e768db410216c2c41 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 19 Nov 2006 03:46:29 -0500 Subject: [PATCH 126/548] git-gui: Correct some state matchings for include/remove. Signed-off-by: Shawn O. Pearce --- git-gui | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/git-gui b/git-gui index dcb25c8d69..dcbb100feb 100755 --- a/git-gui +++ b/git-gui @@ -1676,6 +1676,7 @@ foreach i { {DO i removed "Removed (still exists)"} {DM i removed "Removed (but modified)"} + {UD i merge "Merge conflicts"} {UM i merge "Merge conflicts"} {U_ i merge "Merge conflicts"} } { @@ -2033,12 +2034,11 @@ proc include_helper {txt paths} { set pathList [list] set after {} foreach path $paths { - switch -- [lindex $file_states($path) 0] { + switch -glob -- [lindex $file_states($path) 0] { AM - AD - MM - - UM - - U_ - + U? - _M - _D - _O { @@ -2437,7 +2437,8 @@ proc toggle_or_diff {w x y} { A_ - M_ - DD - - D_ { + DO - + DM { update_indexinfo \ "Removing [short_path $path] from commit" \ [list $path] \ From f18e40a1a60d506065b6cc0e45704ce29ea0a035 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 20 Nov 2006 21:27:22 -0500 Subject: [PATCH 127/548] git-gui: Improve handling of merge commits. Its useful to be able to amend the last commit even if it was a merge commit, so we really should support that in the gui. We now do so by making PARENT a list. We always diff against the first parent but we create a commit consisting of the parent(s) listed in this list, in order. We also should recheck the repository state during an amend. Earlier I was bitten by this exact bug when I switched branches through a command prompt and then did not do a rescan in git-gui. When I hit "Amend Last Commit" I was surprised to see information from the prior branch appear. This was due to git-gui caching the data from the last rescan and using that data form the amend data load request, rather than the data of the current branch. Improved error text in the dialogs used to tell the user why an amend is being refused by git-gui. In general this is only during an initial commit (nothing prior to amend) and during a merge commit (it is simply too confusing to amend the last commit while also trying to complete a merge). Fixed a couple of minor bugs in the pull logic. Since this code isn't really useful nobody has recently tested it and noticed the breakage. It really needs to be rewritten anyway. Signed-off-by: Shawn O. Pearce --- git-gui | 143 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 79 insertions(+), 64 deletions(-) diff --git a/git-gui b/git-gui index dcbb100feb..d5738baf10 100755 --- a/git-gui +++ b/git-gui @@ -231,25 +231,38 @@ proc unlock_index {} { ## ## status -proc repository_state {hdvar ctvar} { +proc repository_state {ctvar hdvar mhvar} { global gitdir - upvar $hdvar hd $ctvar ct + upvar $ctvar ct $hdvar hd $mhvar mh + + set mh [list] if {[catch {set hd [exec git rev-parse --verify HEAD]}]} { set hd {} set ct initial - } elseif {[file exists [file join $gitdir MERGE_HEAD]]} { - set ct merge - } else { - set ct normal + return } + + set merge_head [file join $gitdir MERGE_HEAD] + if {[file exists $merge_head]} { + set ct merge + set fd_mh [open $merge_head r] + while {[gets $fd_mh line] >= 0} { + lappend mh $line + } + close $fd_mh + return + } + + set ct normal } proc PARENT {} { global PARENT empty_tree - if {$PARENT ne {}} { - return $PARENT + set p [lindex $PARENT 0] + if {$p ne {}} { + return $p } if {$empty_tree eq {}} { set empty_tree [exec git mktree << {}] @@ -258,21 +271,22 @@ proc PARENT {} { } proc rescan {after} { - global HEAD PARENT commit_type + global HEAD PARENT MERGE_HEAD commit_type global ui_index ui_other ui_status_value ui_comm global rescan_active file_states global repo_config if {$rescan_active > 0 || ![lock_index read]} return - repository_state new_HEAD new_type + repository_state newType newHEAD newMERGE_HEAD if {[string match amend* $commit_type] - && $new_type eq {normal} - && $new_HEAD eq $HEAD} { + && $newType eq {normal} + && $newHEAD eq $HEAD} { } else { - set HEAD $new_HEAD - set PARENT $new_HEAD - set commit_type $new_type + set HEAD $newHEAD + set PARENT $newHEAD + set MERGE_HEAD $newMERGE_HEAD + set commit_type $newType } array unset file_states @@ -686,23 +700,36 @@ proc read_diff {fd} { ## commit proc load_last_commit {} { - global HEAD PARENT commit_type ui_comm + global HEAD PARENT MERGE_HEAD commit_type ui_comm - if {[string match amend* $commit_type]} return - if {$commit_type ne {normal}} { - error_popup "Can't amend a $commit_type commit." + 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 parent {} - set parent_count 0 + set parents [list] if {[catch { - set fd [open "| git cat-file commit $HEAD" r] + set fd [open "| git cat-file commit $curHEAD" r] while {[gets $fd line] > 0} { if {[string match {parent *} $line]} { - set parent [string range $line 7 end] - incr parent_count + lappend parents [string range $line 7 end] } } set msg [string trim [read $fd]] @@ -712,17 +739,13 @@ proc load_last_commit {} { return } - if {$parent_count > 1} { - error_popup {Can't amend a merge commit.} - return - } - - if {$parent_count == 0} { - set commit_type amend-initial - set PARENT {} - } elseif {$parent_count == 1} { - set commit_type amend - set PARENT $parent + 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 @@ -770,11 +793,11 @@ proc commit_tree {} { # -- Our in memory state should match the repository. # - repository_state curHEAD cur_type + repository_state curType curHEAD curMERGE_HEAD if {[string match amend* $commit_type] - && $cur_type eq {normal} + && $curType eq {normal} && $curHEAD eq $HEAD} { - } elseif {$commit_type ne $cur_type || $HEAD ne $curHEAD} { + } 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 @@ -920,7 +943,8 @@ proc commit_writetree {curHEAD msg} { } proc commit_committree {fd_wt curHEAD msg} { - global single_commit gitdir HEAD PARENT commit_type tcl_platform + global HEAD PARENT MERGE_HEAD commit_type + global single_commit gitdir tcl_platform global ui_status_value ui_comm selected_commit_type global file_states selected_paths rescan_active @@ -935,24 +959,12 @@ proc commit_committree {fd_wt curHEAD msg} { # -- Create the commit. # set cmd [list git commit-tree $tree_id] - if {$PARENT ne {}} { - lappend cmd -p $PARENT - } - if {$commit_type eq {merge}} { - if {[catch { - set fd_mh [open [file join $gitdir MERGE_HEAD] r] - while {[gets $fd_mh merge_head] >= 0} { - lappend cmd -p $merge_head - } - close $fd_mh - } err]} { - error_popup "Loading MERGE_HEAD failed:\n\n$err" - set ui_status_value {Commit failed.} - unlock_index - return + set parents [concat $PARENT $MERGE_HEAD] + if {[llength $parents] > 0} { + foreach p $parents { + lappend cmd -p $p } - } - if {$PARENT eq {}} { + } else { # git commit-tree writes to stderr during initial commit. lappend cmd 2>/dev/null } @@ -1020,10 +1032,11 @@ proc commit_committree {fd_wt curHEAD msg} { # -- Update in memory status # - set commit_type normal 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) @@ -1081,8 +1094,8 @@ proc pull_remote {remote branch} { # -- Our in memory state should match the repository. # - repository_state curHEAD cur_type - if {$commit_type ne $cur_type || $HEAD ne $curHEAD} { + repository_state curType curHEAD curMERGE_HEAD + if {$commit_type ne $curType || $HEAD ne $curHEAD} { error_popup {Last scanned state does not match repository state. Its highly likely that another Git program modified the @@ -1120,18 +1133,18 @@ Commit or throw away all changes before starting a pull operation. } proc post_pull_remote {remote branch success} { - global HEAD PARENT commit_type selected_commit_type + global HEAD PARENT MERGE_HEAD commit_type selected_commit_type global ui_status_value unlock_index if {$success} { - repository_state HEAD commit_type + repository_state commit_type HEAD MERGE_HEAD set PARENT $HEAD set selected_commit_type new - set $ui_status_value "Pulling $branch from $remote complete." + set ui_status_value "Pulling $branch from $remote complete." } else { - set m "Conflicts detected while pulling $branch from $remote." - rescan "set ui_status_value {$m}" + rescan [list set ui_status_value \ + "Conflicts detected while pulling $branch from $remote."] } } @@ -2852,6 +2865,7 @@ proc trace_commit_type {varname args} { initial {set txt {Initial Commit Message:}} amend {set txt {Amended Commit Message:}} amend-initial {set txt {Amended Initial Commit Message:}} + amend-merge {set txt {Amended Merge Commit Message:}} merge {set txt {Merge Commit Message:}} * {set txt {Commit Message:}} } @@ -3146,6 +3160,7 @@ set file_lists($ui_other) [list] set HEAD {} set PARENT {} +set MERGE_HEAD [list] set commit_type {} set empty_tree {} set current_diff {} From 444f92d097425e8d1043a14571ebfd82c1c3b0a5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 20 Nov 2006 21:43:41 -0500 Subject: [PATCH 128/548] git-gui: Allow users to run fsck-objects from the gui. I recently found a need to run fsck-objects in a number of repositories that I also use git-gui against. Tossing in a menu option to invoke fsck-objects and have its output show up in a console window is simple enough to do. We probably need to enhance the console window used by fsck-objects, like to open up the Git fsck-objects manual page and let the user see what each message means (such as "dangling commit") and to also let the user invoke prune, to cleanup any such dangling objects. But right now I'm going to ignore that problem in favor of getting other more important features implemented. Signed-off-by: Shawn O. Pearce --- git-gui | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/git-gui b/git-gui index d5738baf10..2aa82781e9 100755 --- a/git-gui +++ b/git-gui @@ -1954,6 +1954,15 @@ proc do_repack {} { console_exec $w $cmd } +proc do_fsck_objects {} { + set w [new_console "verify" "Verifying the object database"] + set cmd [list git fsck-objects] + lappend cmd --full + lappend cmd --cache + lappend cmd --strict + console_exec $w $cmd +} + set is_quitting 0 proc do_quit {} { @@ -2610,6 +2619,10 @@ if {!$single_commit} { -command do_repack \ -font font_ui + .mbar.project add command -label {Verify Database} \ + -command do_fsck_objects \ + -font font_ui + if {$tcl_platform(platform) eq {windows}} { .mbar.project add command \ -label {Create Desktop Icon} \ From 21d7744fbc98b526bc00138fce287565df2c0075 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 20 Nov 2006 21:59:19 -0500 Subject: [PATCH 129/548] git-gui: Don't save amended commit message buffer. Because we don't automatically restart in amend mode when we quit while in amend mode the commit message buffer shouldn't be saved to GITGUI_MSG as it would be misleading when the user restarts the application. Signed-off-by: Shawn O. Pearce --- git-gui | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/git-gui b/git-gui index 2aa82781e9..d0176aca77 100755 --- a/git-gui +++ b/git-gui @@ -297,8 +297,8 @@ proc rescan {after} { } elseif {[load_message MERGE_MSG]} { } elseif {[load_message SQUASH_MSG]} { } - $ui_comm edit modified false $ui_comm edit reset + $ui_comm edit modified false } if {$repo_config(gui.trustmtime) eq {true}} { @@ -750,8 +750,8 @@ current merge activity. $ui_comm delete 0.0 end $ui_comm insert end $msg - $ui_comm edit modified false $ui_comm edit reset + $ui_comm edit modified false rescan {set ui_status_value {Ready.}} } @@ -760,8 +760,8 @@ proc create_new_commit {} { set commit_type normal $ui_comm delete 0.0 end - $ui_comm edit modified false $ui_comm edit reset + $ui_comm edit modified false rescan {set ui_status_value {Ready.}} } @@ -1025,8 +1025,8 @@ proc commit_committree {fd_wt curHEAD msg} { } $ui_comm delete 0.0 end - $ui_comm edit modified false $ui_comm edit reset + $ui_comm edit modified false if {$single_commit} do_quit @@ -1966,7 +1966,7 @@ proc do_fsck_objects {} { set is_quitting 0 proc do_quit {} { - global gitdir ui_comm is_quitting repo_config + global gitdir ui_comm is_quitting repo_config commit_type if {$is_quitting} return set is_quitting 1 @@ -1975,14 +1975,16 @@ proc do_quit {} { # set save [file join $gitdir GITGUI_MSG] set msg [string trim [$ui_comm get 0.0 end]] - if {[$ui_comm edit modified] && $msg ne {}} { + if {![string match amend* $commit_type] + && [$ui_comm edit modified] + && $msg ne {}} { catch { set fd [open $save w] puts $fd [string trim [$ui_comm get 0.0 end]] close $fd } - } elseif {$msg eq {} && [file exists $save]} { - file delete $save + } else { + catch {file delete $save} } # -- Stash our current window geometry into this repository. From 93a79912055dfc0cbdc974e98c705bc75950862a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 20 Nov 2006 22:17:15 -0500 Subject: [PATCH 130/548] git-gui: Reworded verify console title. It would be something of a disservice to our users if we refer to fsck-objects as "verify". So instead we call it fsck-objects in the console title, and indicate that's how we are verifying the object database. We probably should call our menu option "fsck-objects" or similar but I really do think that "Verify Database" more accurately describes the action then "fsck-objects" does, especially to users who aren't file system developers. Signed-off-by: Shawn O. Pearce --- git-gui | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/git-gui b/git-gui index d0176aca77..7f75ffeaa0 100755 --- a/git-gui +++ b/git-gui @@ -1947,7 +1947,8 @@ proc do_gitk {} { } proc do_repack {} { - set w [new_console "repack" "Repacking the object database"] + set w [new_console {repack} \ + {Repacking the object database}] set cmd [list git repack] lappend cmd -a lappend cmd -d @@ -1955,7 +1956,8 @@ proc do_repack {} { } proc do_fsck_objects {} { - set w [new_console "verify" "Verifying the object database"] + 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 From 75e355d6bec2796ea0e69f4f4421670141ac77a2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 20 Nov 2006 22:22:10 -0500 Subject: [PATCH 131/548] git-gui: Seperate out the database operations in project menu. The project menu is just too cluttered without using separator entries to split out the database operations (such as repack and verify) from the other options in the same menu. Signed-off-by: Shawn O. Pearce --- git-gui | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/git-gui b/git-gui index 7f75ffeaa0..83ff5100ac 100755 --- a/git-gui +++ b/git-gui @@ -2619,6 +2619,8 @@ menu .mbar.project -command do_gitk \ -font font_ui if {!$single_commit} { + .mbar.project add separator + .mbar.project add command -label {Repack Database} \ -command do_repack \ -font font_ui @@ -2627,6 +2629,8 @@ if {!$single_commit} { -command do_fsck_objects \ -font font_ui + .mbar.project add separator + if {$tcl_platform(platform) eq {windows}} { .mbar.project add command \ -label {Create Desktop Icon} \ From a4abfa62d69ff92e3159ca0fd41184f5b72e16a4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 20 Nov 2006 23:01:47 -0500 Subject: [PATCH 132/548] git-gui: Rename Project menu to Repository. Since all of the actions in our Project menu actually apply to the Git concept of a repository, it is a disservice to our users to call it "project". This is especially true if Git ever gets any sort of subproject support, as the term would then most definately conflict. Signed-off-by: Shawn O. Pearce --- git-gui | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/git-gui b/git-gui index 83ff5100ac..e770717b4e 100755 --- a/git-gui +++ b/git-gui @@ -2602,7 +2602,7 @@ apply_config # -- Menu Bar # menu .mbar -tearoff 0 -.mbar add cascade -label Project -menu .mbar.project +.mbar add cascade -label Repository -menu .mbar.repository .mbar add cascade -label Edit -menu .mbar.edit .mbar add cascade -label Commit -menu .mbar.commit if {!$single_commit} { @@ -2612,38 +2612,38 @@ if {!$single_commit} { } . configure -menu .mbar -# -- Project Menu +# -- Repository Menu # -menu .mbar.project -.mbar.project add command -label Visualize \ +menu .mbar.repository +.mbar.repository add command -label Visualize \ -command do_gitk \ -font font_ui if {!$single_commit} { - .mbar.project add separator + .mbar.repository add separator - .mbar.project add command -label {Repack Database} \ + .mbar.repository add command -label {Repack Database} \ -command do_repack \ -font font_ui - .mbar.project add command -label {Verify Database} \ + .mbar.repository add command -label {Verify Database} \ -command do_fsck_objects \ -font font_ui - .mbar.project add separator + .mbar.repository add separator if {$tcl_platform(platform) eq {windows}} { - .mbar.project add command \ + .mbar.repository add command \ -label {Create Desktop Icon} \ -command do_windows_shortcut \ -font font_ui } elseif {[is_MacOSX]} { - .mbar.project add command \ + .mbar.repository add command \ -label {Create Desktop Icon} \ -command do_macosx_app \ -font font_ui } } -.mbar.project add command -label Quit \ +.mbar.repository add command -label Quit \ -command do_quit \ -accelerator $M1T-Q \ -font font_ui @@ -2729,7 +2729,7 @@ lappend disable_on_lock \ lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] -.mbar.commit add command -label {Include All} \ +.mbar.commit add command -label {Include All In Commit} \ -command do_include_all \ -accelerator $M1T-I \ -font font_ui From 82aa23545f84f01e83fb2164b5751b41fd449b62 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 20 Nov 2006 23:55:51 -0500 Subject: [PATCH 133/548] git-gui: Added about dialog box. Created a help menu with an about dialog box. This about dialog shows the copyright notice for the application, the fact that it is covered by the GPL v2.0 or later, the authors, and the current version of Git it is invoking when users perform actions within it. Signed-off-by: Shawn O. Pearce --- git-gui | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/git-gui b/git-gui index e770717b4e..57120bd2b4 100755 --- a/git-gui +++ b/git-gui @@ -2159,6 +2159,55 @@ proc do_commit {} { commit_tree } +proc do_about {} { + global appname + + 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} \ + -font font_ui \ + -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 "$appname - a commit creation tool for Git. + +Copyright © 2006 Shawn Pearce, Paul Mackerras + +Use and redistribute under the terms of the +GNU General Public License, v. 2.0 or later." \ + -padx 5 -pady 5 \ + -justify left \ + -anchor w \ + -borderwidth 1 \ + -relief solid \ + -font font_ui + pack $w.desc -side top -fill x -padx 5 -pady 5 + + label $w.vers \ + -text [exec git --version] \ + -padx 5 -pady 5 \ + -justify left \ + -anchor w \ + -borderwidth 1 \ + -relief solid \ + -font font_ui + pack $w.vers -side top -fill x -padx 5 -pady 5 + + bind $w "grab $w; focus $w" + bind $w "destroy $w" + wm title $w "About $appname" + tkwait window $w +} + proc do_options {} { global appname gitdir font_descs global repo_config global_config @@ -2610,6 +2659,7 @@ if {!$single_commit} { .mbar add cascade -label Pull -menu .mbar.pull .mbar add cascade -label Push -menu .mbar.push } +.mbar add cascade -label Help -menu .mbar.help . configure -menu .mbar # -- Repository Menu @@ -2758,6 +2808,14 @@ if {!$single_commit} { menu .mbar.push } +# -- Help Menm +# +menu .mbar.help + +.mbar.help add command -label "About $appname" \ + -command do_about \ + -font font_ui + # -- Main Window Layout # panedwindow .vpane -orient vertical From 0c8d7839c9dafcfd33dae2bd35b9bf4a98ffa07a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 21 Nov 2006 02:33:56 -0500 Subject: [PATCH 134/548] git-gui: Be more Macintosh like. It is tradition for applications to store their about and preferences menu options within the application menu. This is the first menu in the menu bar, just after the apple menu. Apparently the way to access this menu from Tk on Mac OS X systems is to create a special menu whose name ends in ".apple" and place it into the menu bar. So now if we are on Mac OS X we move our about menu and our options menu into the application menu, like other Mac OS X applications. Signed-off-by: Shawn O. Pearce --- git-gui | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/git-gui b/git-gui index 57120bd2b4..4b07612f58 100755 --- a/git-gui +++ b/git-gui @@ -2659,7 +2659,6 @@ if {!$single_commit} { .mbar add cascade -label Pull -menu .mbar.pull .mbar add cascade -label Push -menu .mbar.push } -.mbar add cascade -label Help -menu .mbar.help . configure -menu .mbar # -- Repository Menu @@ -2731,10 +2730,6 @@ menu .mbar.edit -command {catch {[focus] tag add sel 0.0 end}} \ -accelerator $M1T-A \ -font font_ui -.mbar.edit add separator -.mbar.edit add command -label {Options...} \ - -command do_options \ - -font font_ui # -- Commit Menu # @@ -2808,13 +2803,36 @@ if {!$single_commit} { menu .mbar.push } -# -- Help Menm -# -menu .mbar.help +if {[is_MacOSX]} { + # -- Apple Menu (Mac OS X only) + # + .mbar add cascade -label Apple -menu .mbar.apple + menu .mbar.apple + + .mbar.apple add command -label "About $appname" \ + -command do_about \ + -font font_ui + .mbar.apple add command -label "$appname Options..." \ + -command do_options \ + -font font_ui +} else { + # -- Edit Menu + # + .mbar.edit add separator + .mbar.edit add command -label {Options...} \ + -command do_options \ + -font font_ui + + # -- Help Menu + # + .mbar add cascade -label Help -menu .mbar.help + menu .mbar.help + + .mbar.help add command -label "About $appname" \ + -command do_about \ + -font font_ui +} -.mbar.help add command -label "About $appname" \ - -command do_about \ - -font font_ui # -- Main Window Layout # From bdc9ea202407114737d1d58f7bef00b9579df9b7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 21 Nov 2006 02:36:55 -0500 Subject: [PATCH 135/548] git-gui: Make the copyright notice serve double duty. The copyright notice we display in the about dialog should be the same as the one at the top of our source code. By putting the copyright notice that appears at the top of our source code into a global variable rather than a comment we can trivially make them the same at all times. Signed-off-by: Shawn O. Pearce --- git-gui | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/git-gui b/git-gui index 4b07612f58..b28786657d 100755 --- a/git-gui +++ b/git-gui @@ -2,10 +2,15 @@ # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" -# Copyright (C) 2006 Shawn Pearce, Paul Mackerras. All rights reserved. -# This program is free software; it may be used, copied, modified -# and distributed under the terms of the GNU General Public Licence, -# either version 2, or (at your option) any later version. +set copyright { +Copyright © 2006 Shawn Pearce, Paul Mackerras. + +All rights reserved. + +This program is free software; it may be used, copied, modified +and distributed under the terms of the GNU General Public Licence, +either version 2, or (at your option) any later version. +} set appname [lindex [file split $argv0] end] set gitdir {} @@ -2160,7 +2165,7 @@ proc do_commit {} { } proc do_about {} { - global appname + global appname copyright set w .about_dialog toplevel $w @@ -2179,11 +2184,7 @@ proc do_about {} { label $w.desc \ -text "$appname - a commit creation tool for Git. - -Copyright © 2006 Shawn Pearce, Paul Mackerras - -Use and redistribute under the terms of the -GNU General Public License, v. 2.0 or later." \ +$copyright" \ -padx 5 -pady 5 \ -justify left \ -anchor w \ From 53f7a33bdc89a6ec870fdce5221c05a2759974d3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 21 Nov 2006 02:46:51 -0500 Subject: [PATCH 136/548] git-gui: Include the Tcl/Tk version in the about dialog. Users may need to know what version of Tcl they are running git-gui under, in case there is an interesting interface quirk or other compatability problem we don't know about right now that we may need to explore (and maybe fix). Since its simple enough to show a line with this version data we should do so. We also try to reduce the amount of text shown as often the Tcl and Tk version numbers will be identical; when this happens we should only show the one version number. Signed-off-by: Shawn O. Pearce --- git-gui | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/git-gui b/git-gui index b28786657d..dfdce94cf6 100755 --- a/git-gui +++ b/git-gui @@ -9,8 +9,7 @@ All rights reserved. This program is free software; it may be used, copied, modified and distributed under the terms of the GNU General Public Licence, -either version 2, or (at your option) any later version. -} +either version 2, or (at your option) any later version.} set appname [lindex [file split $argv0] end] set gitdir {} @@ -2166,6 +2165,7 @@ proc do_commit {} { proc do_about {} { global appname copyright + global tcl_patchLevel tk_patchLevel set w .about_dialog toplevel $w @@ -2193,8 +2193,17 @@ $copyright" \ -font font_ui pack $w.desc -side top -fill x -padx 5 -pady 5 + set v [exec git --version] + append v "\n\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 [exec git --version] \ + -text $v \ -padx 5 -pady 5 \ -justify left \ -anchor w \ From 7b85a17b86062c87ceebbf70acfbd62e00cd4bac Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 21 Nov 2006 11:57:41 -0500 Subject: [PATCH 137/548] git-gui: Abstract out windows platform test to is_Windows proc. Like the is_MacOSX proc we shouldn't keep repeating the platform test for Windows. Instead abstract the code out into a procedure and use the procedure whenever we need to do something special. Signed-off-by: Shawn O. Pearce --- git-gui | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/git-gui b/git-gui index dfdce94cf6..3e53fbd623 100755 --- a/git-gui +++ b/git-gui @@ -891,14 +891,14 @@ A good commit message has the following format: } proc commit_prehook {curHEAD msg} { - global tcl_platform gitdir ui_status_value pch_error + global gitdir ui_status_value pch_error + + set pchook [file join $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. - - set pchook [file join $gitdir hooks pre-commit] - if {$tcl_platform(platform) eq {windows} - && [file isfile $pchook]} { + # + if {[is_Windows] && [file isfile $pchook]} { set pchook [list sh -c [concat \ "if test -x \"$pchook\";" \ "then exec \"$pchook\" 2>&1;" \ @@ -948,7 +948,7 @@ proc commit_writetree {curHEAD msg} { proc commit_committree {fd_wt curHEAD msg} { global HEAD PARENT MERGE_HEAD commit_type - global single_commit gitdir tcl_platform + global single_commit gitdir global ui_status_value ui_comm selected_commit_type global file_states selected_paths rescan_active @@ -1016,7 +1016,7 @@ proc commit_committree {fd_wt curHEAD msg} { # -- Run the post-commit hook. # set pchook [file join $gitdir hooks post-commit] - if {$tcl_platform(platform) eq {windows} && [file isfile $pchook]} { + if {[is_Windows] && [file isfile $pchook]} { set pchook [list sh -c [concat \ "if test -x \"$pchook\";" \ "then exec \"$pchook\";" \ @@ -1724,6 +1724,14 @@ proc is_MacOSX {} { return 0 } +proc is_Windows {} { + global tcl_platform + if {$tcl_platform(platform) eq {windows}} { + return 1 + } + return 0 +} + proc bind_button3 {w cmd} { bind $w $cmd if {[is_MacOSX]} { @@ -1854,12 +1862,10 @@ proc console_init {w} { } proc console_exec {w cmd {after {}}} { - global tcl_platform - # -- Windows tosses the enviroment when we exec our child. # But most users need that so we have to relogin. :-( # - if {$tcl_platform(platform) eq {windows}} { + if {[is_Windows]} { set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"] } @@ -1934,7 +1940,7 @@ proc console_read {w fd after} { set starting_gitk_msg {Please wait... Starting gitk...} proc do_gitk {} { - global tcl_platform ui_status_value starting_gitk_msg + global ui_status_value starting_gitk_msg set ui_status_value $starting_gitk_msg after 10000 { @@ -1943,7 +1949,7 @@ proc do_gitk {} { } } - if {$tcl_platform(platform) eq {windows}} { + if {[is_Windows]} { exec sh -c gitk & } else { exec gitk & @@ -2613,7 +2619,7 @@ font create font_diffbold set M1B M1 set M1T M1 -if {$tcl_platform(platform) eq {windows}} { +if {[is_Windows]} { set M1B Control set M1T Ctrl } elseif {[is_MacOSX]} { @@ -2690,7 +2696,7 @@ if {!$single_commit} { .mbar.repository add separator - if {$tcl_platform(platform) eq {windows}} { + if {[is_Windows]} { .mbar.repository add command \ -label {Create Desktop Icon} \ -command do_windows_shortcut \ From 3add5d3517972f6407da450fcd7dff08187cca34 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 21 Nov 2006 12:00:50 -0500 Subject: [PATCH 138/548] git-gui: Correct is_MacOSX platform test. Darwn based UNIX systems are not necessarily Mac OS X. However the only windowing system used by Tk that is Mac OS X is 'aqua', and only 'aqua' exists on Mac OS X. Therefore this is a more reliable test for the Macintosh platform. Signed-off-by: Shawn O. Pearce --- git-gui | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/git-gui b/git-gui index 3e53fbd623..afd9ac026e 100755 --- a/git-gui +++ b/git-gui @@ -1716,9 +1716,7 @@ unset filemask i proc is_MacOSX {} { global tcl_platform tk_library - if {$tcl_platform(platform) eq {unix} - && $tcl_platform(os) eq {Darwin} - && [string match /Library/Frameworks/* $tk_library]} { + if {[tk windowingsystem] eq {aqua}} { return 1 } return 0 From 1d8b3cbf2841612791178853efbfe9ba60b48f2b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 21 Nov 2006 15:28:14 -0500 Subject: [PATCH 139/548] git-gui: Warn Cygwin users about possible environment issues. Because the Tcl binary distributed with Cygwin tends to not pass along its own environment (the env array) to its children, its unlikely that any Git commands spawned by git-gui will receive the same environment variables that git-gui itself received from the shell which started it. If the user is counting on environment variables to pass down, like say GIT_INDEX_FILE, they may not, so we warn them during git-gui startup that things may not work out as the user intended. Perhaps one day when git-gui and git are running on native Windows (rather than through the Cygwin emulation layers) things will work better. Signed-off-by: Shawn O. Pearce --- git-gui | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/git-gui b/git-gui index afd9ac026e..5253141875 100755 --- a/git-gui +++ b/git-gui @@ -145,6 +145,28 @@ proc error_popup {msg} { eval $cmd } +proc warn_popup {msg} { + global gitdir appname + + set title $appname + if {$gitdir ne {}} { + append title { (} + append title [lindex \ + [file split [file normalize [file dirname $gitdir]]] \ + end] + append title {)} + } + 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} { global gitdir appname @@ -158,7 +180,7 @@ proc info_popup {msg} { } tk_messageBox \ -parent . \ - -icon error \ + -icon info \ -type ok \ -title $title \ -message $msg @@ -3279,6 +3301,64 @@ set selected_commit_type new wm title . "$appname ([file normalize [file dirname $gitdir]])" focus -force $ui_comm + +# -- Warn the user about environmental problems. +# Cygwin's Tcl does *not* pass its env array +# onto any processes it spawns. This means +# that the git processes get none of our +# environment. That may not work... +# +if {[is_Windows]} { + set ignored_env 0 + set suggest_user {} + set msg "Possible environment issues exist. + +The following environment variables are probably +going to be ignored by any Git subprocess run +by $appname: + +" + foreach name [array names env] { + switch -regexp -- $name { + {^GIT_INDEX_FILE$} - + {^GIT_OBJECT_DIRECTORY$} - + {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} - + {^GIT_DIFF_OPTS$} - + {^GIT_EXTERNAL_DIFF$} - + {^GIT_PAGER$} - + {^GIT_TRACE$} - + {^GIT_CONFIG$} - + {^GIT_CONFIG_LOCAL$} - + {^GIT_(AUTHOR|COMMITTER)_DATE$} { + append msg " - $name\n" + incr ignored_env + } + {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} { + append msg " - $name\n" + incr ignored_env + set suggest_user $name + } + } + } + if {$ignored_env > 0} { + append msg " +This is due to a known issue with the +Tcl binary distributed by Cygwin." + + if {$suggest_user ne {}} { + append msg " + +A good replacement for $suggest_user +is placing values for the user.name and +user.email settings into your personal +~/.gitconfig file. +" + } + warn_popup $msg + } + unset ignored_env msg suggest_user name +} + if {!$single_commit} { load_all_remotes populate_fetch_menu .mbar.fetch From 1d72cb6c1833ec0b5065e33cfc7b5a55ee6b3d8e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 21 Nov 2006 15:38:49 -0500 Subject: [PATCH 140/548] git-gui: Added configuration editor TODO list. Signed-off-by: Shawn O. Pearce --- TODO | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index d27227c376..b8fa807938 100644 --- a/TODO +++ b/TODO @@ -29,4 +29,11 @@ Items outstanding: * Show a shortlog of the last couple of commits in the main window, to give the user warm fuzzy feelings that we have their data - saved. + saved. Actually this may be the set of commits not yet in + the upstream (aka default merge branch remote repository). + + * GUI configuration editor for options listed in + git.git/Documentation/config.txt. Ideally this would + parse that file and generate the options dialog from + the documentation itself, and include the help text + from the documentation as part of the UI somehow. From b673bbc59cf6950808d12b0d4020ae606019a8cd Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 21 Nov 2006 20:21:11 -0500 Subject: [PATCH 141/548] git-gui: Refactor M1 binding selection. Signed-off-by: Shawn O. Pearce --- git-gui | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/git-gui b/git-gui index 5253141875..13b40823ee 100755 --- a/git-gui +++ b/git-gui @@ -2637,14 +2637,15 @@ catch { font create font_uibold font create font_diffbold -set M1B M1 -set M1T M1 if {[is_Windows]} { set M1B Control set M1T Ctrl } elseif {[is_MacOSX]} { set M1B M1 set M1T Cmd +} else { + set M1B M1 + set M1T M1 } proc apply_config {} { From d075242923691e5f3c671f240945b0bdf26a7cd0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 21 Nov 2006 20:33:09 -0500 Subject: [PATCH 142/548] git-gui: Added menu command to visualize all branches. Sometimes its useful to start gitk with the --all option, to view all of the known branches and tags within this repository. Rather than making the user startup gitk and then edit the view we can pass the option along for them. This also makes it slightly more explicit, that when gitk starts up by default its showing the current branch and not everything. Yes gitk isn't showing that to the user, but the fact that the user had to make a decision between seeing this current branch or all branches will hopefully make them study gitk's display before jumping to a conclusion. Signed-off-by: Shawn O. Pearce --- git-gui | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/git-gui b/git-gui index 13b40823ee..ef8c7cf065 100755 --- a/git-gui +++ b/git-gui @@ -1959,20 +1959,28 @@ proc console_read {w fd after} { set starting_gitk_msg {Please wait... Starting gitk...} -proc do_gitk {} { +proc do_gitk {revs} { global ui_status_value starting_gitk_msg - set ui_status_value $starting_gitk_msg - after 10000 { - if {$ui_status_value eq $starting_gitk_msg} { - set ui_status_value {Ready.} - } + set cmd gitk + if {$revs ne {}} { + append cmd { } + append cmd $revs } - if {[is_Windows]} { - exec sh -c gitk & + set cmd "sh -c \"exec $cmd\"" + } + append cmd { &} + + if {[catch {eval exec $cmd} err]} { + error_popup "Failed to start gitk:\n\n$err" } else { - exec gitk & + set ui_status_value $starting_gitk_msg + after 10000 { + if {$ui_status_value eq $starting_gitk_msg} { + set ui_status_value {Ready.} + } + } } } @@ -2701,12 +2709,17 @@ if {!$single_commit} { # -- Repository Menu # menu .mbar.repository -.mbar.repository add command -label Visualize \ - -command do_gitk \ +.mbar.repository add command \ + -label {Visualize Current Branch} \ + -command {do_gitk {}} \ -font font_ui -if {!$single_commit} { - .mbar.repository add separator +.mbar.repository add command \ + -label {Visualize All Branches} \ + -command {do_gitk {--all}} \ + -font font_ui +.mbar.repository add separator +if {!$single_commit} { .mbar.repository add command -label {Repack Database} \ -command do_repack \ -font font_ui From 5040f926f9b06946b6b6144eb358db1850dce505 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 21 Nov 2006 22:58:28 -0500 Subject: [PATCH 143/548] git-gui: Don't start 'gitk --all' on Mac OS X. Since gitk is currently broken on Mac OS X and is unable to start itself when given command line parameters just don't offer the "Visual All Branches" menu option on Mac OS X. Once this feature of gitk is fixed we should change this section of code to make sure a working version of gitk will be executed before we offer the option up to the user. Signed-off-by: Shawn O. Pearce --- git-gui | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/git-gui b/git-gui index ef8c7cf065..d1cc0ec248 100755 --- a/git-gui +++ b/git-gui @@ -2713,10 +2713,12 @@ menu .mbar.repository -label {Visualize Current Branch} \ -command {do_gitk {}} \ -font font_ui -.mbar.repository add command \ - -label {Visualize All Branches} \ - -command {do_gitk {--all}} \ - -font font_ui +if {![is_MacOSX]} { + .mbar.repository add command \ + -label {Visualize All Branches} \ + -command {do_gitk {--all}} \ + -font font_ui +} .mbar.repository add separator if {!$single_commit} { From 4c2035d55e0a5c013677a8e83193e37d51000793 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 22 Nov 2006 19:24:41 -0500 Subject: [PATCH 144/548] git-gui: Improve pull error dialogs. Just like prior to a commit its only an informational message that we refuse to perform a pull on a dirty working directory. Therefore we should not use an error icon. Signed-off-by: Shawn O. Pearce --- git-gui | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/git-gui b/git-gui index d1cc0ec248..8246037fac 100755 --- a/git-gui +++ b/git-gui @@ -1122,11 +1122,13 @@ proc pull_remote {remote branch} { # repository_state curType curHEAD curMERGE_HEAD if {$commit_type ne $curType || $HEAD ne $curHEAD} { - error_popup {Last scanned state does not match repository state. + info_popup {Last scanned state does not match repository state. -Its highly likely that another Git program modified the -repository since our last scan. A rescan is required -before a pull can be started. +Another Git program has modified this repository +since the last scan. A rescan must be performed +before a pull operation can be started. + +The rescan will be automatically started now. } unlock_index rescan {set ui_status_value {Ready.}} @@ -1138,10 +1140,12 @@ before a pull can be started. if {[array size file_states] != 0} { error_popup {Uncommitted but modified files are present. -You should not perform a pull with unmodified files in your working -directory as Git would be unable to recover from an incorrect merge. +You should not perform a pull with unmodified +files in your working directory as Git will be +unable to recover from an incorrect merge. -Commit or throw away all changes before starting a pull operation. +You should commit or revert all changes before +starting a pull operation. } unlock_index return From e734817db0af028f2aea356b44c74b7ac51023cd Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 23 Nov 2006 21:40:45 -0500 Subject: [PATCH 145/548] git-gui: Added revert changes command. Users sometimes need to be able to throw away locally modified files in order to go back to the last committed version of that file. To perform a revert the user must first uninclude each file from the new commit as the working file must at least partially match the index, and we use git-checkout-index to update the working directory. Signed-off-by: Shawn O. Pearce --- git-gui | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 177 insertions(+), 7 deletions(-) diff --git a/git-gui b/git-gui index 8246037fac..34a1daa44d 100755 --- a/git-gui +++ b/git-gui @@ -1277,9 +1277,26 @@ proc display_file {path state} { set old_w [mapcol $old_m $path] set new_icon [mapicon $new_m $path] + if {$new_m eq {__}} { + set lno [lsearch -sorted $file_lists($old_w) $path] + if {$lno >= 0} { + set file_lists($old_w) \ + [lreplace $file_lists($old_w) $lno $lno] + incr lno + $old_w conf -state normal + $old_w delete $lno.0 [expr {$lno + 1}].0 + $old_w conf -state disabled + } + unset file_states($path) + catch {unset selected_paths($path)} + return + } + if {$new_w ne $old_w} { set lno [lsearch -sorted $file_lists($old_w) $path] if {$lno >= 0} { + set file_lists($old_w) \ + [lreplace $file_lists($old_w) $lno $lno] incr lno $old_w conf -state normal $old_w delete $lno.0 [expr {$lno + 1}].0 @@ -1500,6 +1517,84 @@ proc write_update_index {fd pathList totalCnt batch msg after} { [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 \ + -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 + + 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] { + AM - + AD {set new A_} + MM - + MD {set new M_} + _M - + _D {set new __} + ?? {continue} + } + + puts -nonewline $fd $path + puts -nonewline $fd "\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}]] +} + ###################################################################### ## ## remote management @@ -1708,11 +1803,12 @@ foreach i { {_M i mod "Modified"} {M_ i fulltick "Included in commit"} {MM i parttick "Partially included"} + {MD i question "Included (but gone)"} {_O o plain "Untracked"} {A_ o fulltick "Added by commit"} {AM o parttick "Partially added"} - {AD o question "Added (but now gone)"} + {AD o question "Added (but gone)"} {_D i question "Missing"} {DD i removed "Removed by commit"} @@ -2159,6 +2255,74 @@ proc do_include_all {} { $paths } +proc revert_helper {txt paths} { + global file_states current_diff + + if {![lock_index begin-update]} return + + set pathList [list] + set after {} + foreach path $paths { + switch -glob -- [lindex $file_states($path) 0] { + AM - + AD - + MM - + MD - + _M - + _D { + lappend pathList $path + if {$path eq $current_diff} { + 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 \ + "title" \ + "Revert unincluded changes in $s? + +Any unincluded changes will be permanently lost by the revert." \ + questhead \ + 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 selected_paths + + if {[array size selected_paths] > 0} { + revert_helper \ + {Reverting selected files} \ + [array names selected_paths] + } elseif {$current_diff ne {}} { + revert_helper \ + "Reverting [short_path $current_diff]" \ + [list $current_diff] + } +} + proc do_signoff {} { global ui_comm @@ -2818,12 +2982,6 @@ lappend disable_on_lock \ lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] -.mbar.commit add command -label {Remove From Commit} \ - -command do_remove_selection \ - -font font_ui -lappend disable_on_lock \ - [list .mbar.commit entryconf [.mbar.commit index last] -state] - .mbar.commit add command -label {Include In Commit} \ -command do_include_selection \ -font font_ui @@ -2837,6 +2995,18 @@ lappend disable_on_lock \ lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] +.mbar.commit add command -label {Remove From Commit} \ + -command do_remove_selection \ + -font font_ui +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 +lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + .mbar.commit add separator .mbar.commit add command -label {Sign Off} \ From 8553b772d7aeebe8c83710233877483dd409b846 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 24 Nov 2006 15:38:18 -0500 Subject: [PATCH 146/548] git-gui: Display the current branch. Users want to know what branch they are sitting on before making a commit, as they may need to switch to a different branch first. Signed-off-by: Shawn O. Pearce --- git-gui | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/git-gui b/git-gui index 34a1daa44d..dd2d750ab1 100755 --- a/git-gui +++ b/git-gui @@ -258,11 +258,20 @@ proc unlock_index {} { ## status proc repository_state {ctvar hdvar mhvar} { - global gitdir + global gitdir current_branch upvar $ctvar ct $hdvar hd $mhvar mh set mh [list] + if {[catch {set current_branch [exec git symbolic-ref HEAD]}]} { + set current_branch {} + } else { + regsub ^refs/(heads|tags)/ \ + $current_branch \ + {} \ + current_branch + } + if {[catch {set hd [exec git rev-parse --verify HEAD]}]} { set hd {} set ct initial @@ -3060,6 +3069,25 @@ if {[is_MacOSX]} { } +# -- Branch Control +# +frame .branch \ + -borderwidth 1 \ + -relief sunken +label .branch.l1 \ + -text {Current Branch:} \ + -anchor w \ + -justify left \ + -font font_ui +label .branch.cb \ + -textvariable current_branch \ + -anchor w \ + -justify left \ + -font font_ui +pack .branch.l1 -side left +pack .branch.cb -side left -fill x +pack .branch -side top -fill x + # -- Main Window Layout # panedwindow .vpane -orient vertical @@ -3486,6 +3514,7 @@ set PARENT {} set MERGE_HEAD [list] set commit_type {} set empty_tree {} +set current_branch {} set current_diff {} set selected_commit_type new From 9342e26d3a832dd24878725b42444b8efe4fa1c4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 24 Nov 2006 15:59:34 -0500 Subject: [PATCH 147/548] git-gui: Support file state MD (modified/deleted). Apparently I missed the file state MD, which is a file modified and updated in the index but then removed from the working directory. This should be treated just like AD, an added file which has been deleted from the working directory. Signed-off-by: Shawn O. Pearce --- git-gui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git-gui b/git-gui index dd2d750ab1..e9d4600a25 100755 --- a/git-gui +++ b/git-gui @@ -1093,6 +1093,7 @@ proc commit_committree {fd_wt curHEAD msg} { AM - AD - MM - + MD - DM { set file_states($path) [list \ _[string index $m 1] \ @@ -2211,6 +2212,7 @@ proc include_helper {txt paths} { AM - AD - MM - + MD - U? - _M - _D - @@ -2255,6 +2257,7 @@ proc do_include_all {} { AM - AD - MM - + MD - _M - _D {lappend paths $path} } From 700a65ce380f29a5083bcc230aa1ef5c28e66f2c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 24 Nov 2006 17:30:12 -0500 Subject: [PATCH 148/548] git-gui: Created Branch menu. This is an early start at branch management from within git-gui. The branch menu has create/delete command entries to create and delete branches as well as a list of radiobutton entries for each branch found in the repository through for-each-ref. Signed-off-by: Shawn O. Pearce --- git-gui | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/git-gui b/git-gui index e9d4600a25..69ebd90958 100755 --- a/git-gui +++ b/git-gui @@ -1605,6 +1605,44 @@ proc write_checkout_index {fd pathList totalCnt batch msg after} { [expr {100.0 * $update_index_cp / $totalCnt}]] } +###################################################################### +## +## branch management + +proc load_all_branches {} { + global all_branches + + set all_branches [list] + set cmd [list git for-each-ref] + lappend cmd --format=%(refname) + lappend cmd refs/heads + set fd [open "| $cmd" r] + while {[gets $fd line] > 0} { + if {[regsub ^refs/heads/ $line {} line]} { + lappend all_branches $line + } + } + close $fd + + set all_branches [lsort $all_branches] +} + +proc populate_branch_menu {m} { + global all_branches disable_on_lock + + $m add separator + foreach b $all_branches { + $m add radiobutton \ + -label $b \ + -command [list do_switch_branch $b] \ + -variable current_branch \ + -value $b \ + -font font_ui + lappend disable_on_lock \ + [list $m entryconf [$m index last] -state] + } +} + ###################################################################### ## ## remote management @@ -2878,6 +2916,9 @@ apply_config menu .mbar -tearoff 0 .mbar add cascade -label Repository -menu .mbar.repository .mbar add cascade -label Edit -menu .mbar.edit +if {!$single_commit} { + .mbar add cascade -label Branch -menu .mbar.branch +} .mbar add cascade -label Commit -menu .mbar.commit if {!$single_commit} { .mbar add cascade -label Fetch -menu .mbar.fetch @@ -2963,6 +3004,24 @@ menu .mbar.edit -accelerator $M1T-A \ -font font_ui +if {!$single_commit} { + # -- Branch Menu + # + menu .mbar.branch + + .mbar.branch add command -label {Create...} \ + -command do_create_branch \ + -font font_ui + 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 + lappend disable_on_lock [list .mbar.branch entryconf \ + [.mbar.branch index last] -state] +} + # -- Commit Menu # menu .mbar.commit @@ -3583,6 +3642,8 @@ user.email settings into your personal if {!$single_commit} { load_all_remotes + load_all_branches + populate_branch_menu .mbar.branch populate_fetch_menu .mbar.fetch populate_pull_menu .mbar.pull populate_push_menu .mbar.push From d90d83a3a95e5fb4672906589ac0a19c19f1187b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 25 Nov 2006 02:45:19 -0500 Subject: [PATCH 149/548] git-gui: Parse off refs/remotes when showing current branch. Even though the user shouldn't have a remote branch checked out, if they do we should still show as short of the branch name as possible. Signed-off-by: Shawn O. Pearce --- git-gui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-gui b/git-gui index 69ebd90958..48e781dd30 100755 --- a/git-gui +++ b/git-gui @@ -266,7 +266,7 @@ proc repository_state {ctvar hdvar mhvar} { if {[catch {set current_branch [exec git symbolic-ref HEAD]}]} { set current_branch {} } else { - regsub ^refs/(heads|tags)/ \ + regsub ^refs/((heads|tags|remotes)/)? \ $current_branch \ {} \ current_branch From 2171bf4b44884fd75bc5c1c412a39c2d4e645453 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 25 Nov 2006 02:47:18 -0500 Subject: [PATCH 150/548] git-gui: Abort on not implemented branch switching. I'm not currently ready to implement branch switching, so I'm just going to punt on it for now. :-) Signed-off-by: Shawn O. Pearce --- git-gui | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/git-gui b/git-gui index 48e781dd30..9bfed1f6a9 100755 --- a/git-gui +++ b/git-gui @@ -1634,7 +1634,7 @@ proc populate_branch_menu {m} { foreach b $all_branches { $m add radiobutton \ -label $b \ - -command [list do_switch_branch $b] \ + -command [list switch_branch $b] \ -variable current_branch \ -value $b \ -font font_ui @@ -1643,6 +1643,10 @@ proc populate_branch_menu {m} { } } +proc switch_branch {b} { + error "NOT IMPLEMENTED" +} + ###################################################################### ## ## remote management From 359ca42a4b9288421d2f0409652f76e9a365b801 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 25 Nov 2006 03:33:03 -0500 Subject: [PATCH 151/548] git-gui: Automatically skip tracking branches in branch menu. Since the user should not work on a tracking branch we automatically hide any branch which is used as a tracking branch by either a remote..fetch config entry or by a Pull: line in a remotes file. Signed-off-by: Shawn O. Pearce --- git-gui | 46 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/git-gui b/git-gui index 9bfed1f6a9..7406238c90 100755 --- a/git-gui +++ b/git-gui @@ -1610,7 +1610,7 @@ proc write_checkout_index {fd pathList totalCnt batch msg after} { ## branch management proc load_all_branches {} { - global all_branches + global all_branches tracking_branches set all_branches [list] set cmd [list git for-each-ref] @@ -1618,9 +1618,9 @@ proc load_all_branches {} { lappend cmd refs/heads set fd [open "| $cmd" r] while {[gets $fd line] > 0} { - if {[regsub ^refs/heads/ $line {} line]} { - lappend all_branches $line - } + if {![catch {set info $tracking_branches($line)}]} continue + if {![regsub ^refs/heads/ $line {} name]} continue + lappend all_branches $name } close $fd @@ -1652,21 +1652,49 @@ proc switch_branch {b} { ## remote management proc load_all_remotes {} { - global gitdir all_remotes repo_config + global gitdir repo_config + global all_remotes tracking_branches set all_remotes [list] + array unset tracking_branches + set rm_dir [file join $gitdir remotes] if {[file isdirectory $rm_dir]} { - set all_remotes [concat $all_remotes [glob \ + set all_remotes [glob \ -types f \ -tails \ -nocomplain \ - -directory $rm_dir *]] + -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]} { - lappend all_remotes $name + 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] } } From bb1ad51a5365cbe48d5dcee7e00a8d9f90d89171 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 25 Nov 2006 03:35:33 -0500 Subject: [PATCH 152/548] git-gui: Rename all_branches -> all_heads. Since this list is really the set of refs which match "refs/heads/*" it really is the set of heads and not necessarily the set of all branches, as the remote tracking branches are not listed in this set, even if it appears in the "refs/heads/*" namespace (e.g. an old style repository). Signed-off-by: Shawn O. Pearce --- git-gui | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/git-gui b/git-gui index 7406238c90..6035105f8e 100755 --- a/git-gui +++ b/git-gui @@ -1609,10 +1609,10 @@ proc write_checkout_index {fd pathList totalCnt batch msg after} { ## ## branch management -proc load_all_branches {} { - global all_branches tracking_branches +proc load_all_heads {} { + global all_heads tracking_branches - set all_branches [list] + set all_heads [list] set cmd [list git for-each-ref] lappend cmd --format=%(refname) lappend cmd refs/heads @@ -1620,18 +1620,18 @@ proc load_all_branches {} { while {[gets $fd line] > 0} { if {![catch {set info $tracking_branches($line)}]} continue if {![regsub ^refs/heads/ $line {} name]} continue - lappend all_branches $name + lappend all_heads $name } close $fd - set all_branches [lsort $all_branches] + set all_heads [lsort $all_heads] } proc populate_branch_menu {m} { - global all_branches disable_on_lock + global all_heads disable_on_lock $m add separator - foreach b $all_branches { + foreach b $all_heads { $m add radiobutton \ -label $b \ -command [list switch_branch $b] \ @@ -3674,7 +3674,7 @@ user.email settings into your personal if {!$single_commit} { load_all_remotes - load_all_branches + load_all_heads populate_branch_menu .mbar.branch populate_fetch_menu .mbar.fetch populate_pull_menu .mbar.pull From 85ab313ed3dc9a951ec3859fcc3a32a6c5c3ee19 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 25 Nov 2006 03:38:39 -0500 Subject: [PATCH 153/548] git-gui: Misc. comment and formatting cleanups. Signed-off-by: Shawn O. Pearce --- git-gui | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/git-gui b/git-gui index 6035105f8e..f52dd522c1 100755 --- a/git-gui +++ b/git-gui @@ -2997,6 +2997,7 @@ if {!$single_commit} { -font font_ui } } + .mbar.repository add command -label Quit \ -command do_quit \ -accelerator $M1T-Q \ @@ -3036,9 +3037,9 @@ menu .mbar.edit -accelerator $M1T-A \ -font font_ui +# -- Branch Menu +# if {!$single_commit} { - # -- Branch Menu - # menu .mbar.branch .mbar.branch add command -label {Create...} \ @@ -3615,11 +3616,9 @@ set selected_commit_type new wm title . "$appname ([file normalize [file dirname $gitdir]])" focus -force $ui_comm -# -- Warn the user about environmental problems. -# Cygwin's Tcl does *not* pass its env array -# onto any processes it spawns. This means -# that the git processes get none of our -# environment. That may not work... +# -- Warn the user about environmental problems. Cygwin's Tcl +# does *not* pass its env array onto any processes it spawns. +# This means that git processes get none of our environment. # if {[is_Windows]} { set ignored_env 0 @@ -3672,13 +3671,17 @@ user.email settings into your personal unset ignored_env msg suggest_user name } +# -- Only initialize complex UI if we are going to stay running. +# if {!$single_commit} { load_all_remotes load_all_heads + populate_branch_menu .mbar.branch populate_fetch_menu .mbar.fetch populate_pull_menu .mbar.pull populate_push_menu .mbar.push } + lock_index begin-read after 1 do_rescan From 84e0bf1de4fcdada4698e2bd53bafaeaea6b5cbd Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 25 Nov 2006 04:04:24 -0500 Subject: [PATCH 154/548] git-gui: Started implementation of switch_branch. This implementation of switch_branch is not yet finished, and thus it throws a "NOT FINISHED" error rather than completing the switch. But its a rough sketch of the procedure required. Signed-off-by: Shawn O. Pearce --- git-gui | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/git-gui b/git-gui index f52dd522c1..3845cf7168 100755 --- a/git-gui +++ b/git-gui @@ -1643,10 +1643,59 @@ proc populate_branch_menu {m} { } } -proc switch_branch {b} { +proc do_create_branch {} { error "NOT IMPLEMENTED" } +proc do_delete_branch {} { + error "NOT IMPLEMENTED" +} + +proc switch_branch {b} { + global HEAD commit_type file_states current_branch + global selected_commit_type ui_comm + + if {![lock_index switch]} return + + # -- Backup the selected branch (repository_state resets it) + # + set new_branch $current_branch + + # -- 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 + } + + # -- Toss the message buffer if we are in amend mode. + # + if {[string match amend* $curType]} { + $ui_comm delete 0.0 end + $ui_comm edit reset + $ui_comm edit modified false + } + + set selected_commit_type new + set current_branch $new_branch + + unlock_index + error "NOT FINISHED" +} + ###################################################################### ## ## remote management From 9208487b34706887fcc10ce6423099134f301f5e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 25 Nov 2006 12:40:29 -0500 Subject: [PATCH 155/548] git-gui: Set a proper title on our revert confirm dialog box. Signed-off-by: Shawn O. Pearce --- git-gui | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/git-gui b/git-gui index 3845cf7168..09fc40aaf8 100755 --- a/git-gui +++ b/git-gui @@ -2387,6 +2387,7 @@ proc do_include_all {} { } proc revert_helper {txt paths} { + global gitdir appname global file_states current_diff if {![lock_index begin-update]} return @@ -2419,13 +2420,17 @@ proc revert_helper {txt paths} { set s "these $n files" } + set reponame [lindex [file split \ + [file normalize [file dirname $gitdir]]] \ + end] + set reply [tk_dialog \ .confirm_revert \ - "title" \ + "$appname ($reponame)" \ "Revert unincluded changes in $s? Any unincluded changes will be permanently lost by the revert." \ - questhead \ + question \ 1 \ {Do Nothing} \ {Revert Changes} \ From 5e926cbf7eeb2c89e0957bed7941c2c6490767c2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 25 Nov 2006 23:16:16 -0500 Subject: [PATCH 156/548] git-gui: Updated todo list. Signed-off-by: Shawn O. Pearce --- TODO | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/TODO b/TODO index b8fa807938..ef4f50b304 100644 --- a/TODO +++ b/TODO @@ -1,16 +1,11 @@ Items outstanding: - * Checkout $PARENT version to working directory, overwriting current - version. ($PARENT is HEAD, except when amending). - * Add file to .gitignore or info/excludes. * Populate the pull menu with local branches. * Make use of the new default merge data stored in repo-config. - * Indicate what the current branch is. - * Checkout or create a different local branch. * Delete a local branch. @@ -37,3 +32,18 @@ Items outstanding: parse that file and generate the options dialog from the documentation itself, and include the help text from the documentation as part of the UI somehow. + +Known bugs: + + * git-gui sometimes just closes on Windows with no error message. + I'm not sure what the problem is here. I suspect the wish + process is just terminating due to a segfault or something, + as the do_quit proc in git-gui doesn't run. It often seems to + occur while writing a commit message in the buffer. Odd. + + * At one point after using git-gui for a while to make many commits + to a repository I reverted one file through git-gui and another + manually in my editor; during commit git-gui crashed with an + error about the icon name it was trying to update no longer + existed in the widget. I suspect something didn't update right + in file_states... From f70c3a2caccf4f0f5abfd3d0db4120e8659dd0d7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 26 Nov 2006 19:45:39 -0500 Subject: [PATCH 157/548] git-gui: Enable resolution of merge conflicts. If a file has a merge conflict (index state = U) the user will need to run update-index on that file to resolve all stages down to stage 0, by including the file in the working directory. Like core Git we'll just trust the user that their resolution is correct, and that they didn't just include the file into the commit while merge conflicts still exist within the file. Signed-off-by: Shawn O. Pearce --- git-gui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git-gui b/git-gui index 09fc40aaf8..0b0f1e3e70 100755 --- a/git-gui +++ b/git-gui @@ -1502,10 +1502,13 @@ proc write_update_index {fd pathList totalCnt batch msg after} { switch -glob -- [lindex $file_states($path) 0] { AD - MD - + UD - _D {set new DD} _M - MM - + UM - + U_ - M_ {set new M_} _O - From c15ad650c77ef3213d723efec4e1dca89efba6cd Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 26 Nov 2006 19:46:45 -0500 Subject: [PATCH 158/548] git-gui: Auto-update any A? or M? files during rescan. If the user has partial includes disabled then it doesn't matter what state the working directory is in; if the file has been included in the next commit its index state is A or M and we should immediately run update-index on the working directory file to bring the index in sync with the working directory. Signed-off-by: Shawn O. Pearce --- git-gui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-gui b/git-gui index 0b0f1e3e70..899fa35a60 100755 --- a/git-gui +++ b/git-gui @@ -499,8 +499,8 @@ proc rescan_done {fd buf after} { set pathList [list] foreach path [array names file_states] { switch -- [lindex $file_states($path) 0] { - AM - - MM {lappend pathList $path} + A? - + M? {lappend pathList $path} } } if {$pathList ne {}} { From db5e523fddd2a1a47d9ea63498734d0141925513 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 5 Aug 2006 02:04:21 -0400 Subject: [PATCH 159/548] Created fast-import, a tool to quickly generating a pack from blobs. Signed-off-by: Shawn O. Pearce --- .gitignore | 1 + Makefile | 1 + fast-import.c | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 fast-import.c diff --git a/.gitignore b/.gitignore index 55cd9844d6..8ddccd7dac 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ git-diff-index git-diff-stages git-diff-tree git-describe +git-fast-import git-fetch git-fetch-pack git-findtags diff --git a/Makefile b/Makefile index b15b420ea2..a37f74a1ff 100644 --- a/Makefile +++ b/Makefile @@ -186,6 +186,7 @@ SIMPLE_PROGRAMS = \ PROGRAMS = \ git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ + git-fast-import$X \ git-merge-base$X \ git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \ git-peek-remote$X git-receive-pack$X \ diff --git a/fast-import.c b/fast-import.c new file mode 100644 index 0000000000..416ba5c7c6 --- /dev/null +++ b/fast-import.c @@ -0,0 +1,214 @@ +#include "builtin.h" +#include "cache.h" +#include "object.h" +#include "blob.h" +#include "delta.h" +#include "pack.h" +#include "csum-file.h" + +static int max_depth = 10; +static unsigned long object_count; +static int packfd; +static int current_depth; +static void *lastdat; +static unsigned long lastdatlen; +static unsigned char lastsha1[20]; + +static ssize_t yread(int fd, void *buffer, size_t length) +{ + ssize_t ret = 0; + while (ret < length) { + ssize_t size = xread(fd, (char *) buffer + ret, length - ret); + if (size < 0) { + return size; + } + if (size == 0) { + return ret; + } + ret += size; + } + return ret; +} + +static ssize_t ywrite(int fd, void *buffer, size_t length) +{ + ssize_t ret = 0; + while (ret < length) { + ssize_t size = xwrite(fd, (char *) buffer + ret, length - ret); + if (size < 0) { + return size; + } + if (size == 0) { + return ret; + } + ret += size; + } + return ret; +} + +static unsigned long encode_header(enum object_type type, unsigned long size, unsigned char *hdr) +{ + int n = 1; + unsigned char c; + + if (type < OBJ_COMMIT || type > OBJ_DELTA) + die("bad type %d", type); + + c = (type << 4) | (size & 15); + size >>= 4; + while (size) { + *hdr++ = c | 0x80; + c = size & 0x7f; + size >>= 7; + n++; + } + *hdr = c; + return n; +} + +static void write_blob (void *dat, unsigned long datlen) +{ + z_stream s; + void *out, *delta; + unsigned char hdr[64]; + unsigned long hdrlen, deltalen; + + if (lastdat && current_depth < max_depth) { + delta = diff_delta(lastdat, lastdatlen, + dat, datlen, + &deltalen, 0); + } else + delta = 0; + + memset(&s, 0, sizeof(s)); + deflateInit(&s, zlib_compression_level); + + if (delta) { + current_depth++; + s.next_in = delta; + s.avail_in = deltalen; + hdrlen = encode_header(OBJ_DELTA, deltalen, hdr); + if (ywrite(packfd, hdr, hdrlen) != hdrlen) + die("Can't write object header: %s", strerror(errno)); + if (ywrite(packfd, lastsha1, sizeof(lastsha1)) != sizeof(lastsha1)) + die("Can't write object base: %s", strerror(errno)); + } else { + current_depth = 0; + s.next_in = dat; + s.avail_in = datlen; + hdrlen = encode_header(OBJ_BLOB, datlen, hdr); + if (ywrite(packfd, hdr, hdrlen) != hdrlen) + die("Can't write object header: %s", strerror(errno)); + } + + s.avail_out = deflateBound(&s, s.avail_in); + s.next_out = out = xmalloc(s.avail_out); + while (deflate(&s, Z_FINISH) == Z_OK) + /* nothing */; + deflateEnd(&s); + + if (ywrite(packfd, out, s.total_out) != s.total_out) + die("Failed writing compressed data %s", strerror(errno)); + + free(out); + if (delta) + free(delta); +} + +static void init_pack_header () +{ + const char* magic = "PACK"; + unsigned long version = 2; + unsigned long zero = 0; + + version = htonl(version); + + if (ywrite(packfd, (char*)magic, 4) != 4) + die("Can't write pack magic: %s", strerror(errno)); + if (ywrite(packfd, &version, 4) != 4) + die("Can't write pack version: %s", strerror(errno)); + if (ywrite(packfd, &zero, 4) != 4) + die("Can't write 0 object count: %s", strerror(errno)); +} + +static void fixup_header_footer () +{ + SHA_CTX c; + char hdr[8]; + unsigned char sha1[20]; + unsigned long cnt; + char *buf; + size_t n; + + if (lseek(packfd, 0, SEEK_SET) != 0) + die("Failed seeking to start: %s", strerror(errno)); + + SHA1_Init(&c); + if (yread(packfd, hdr, 8) != 8) + die("Failed reading header: %s", strerror(errno)); + SHA1_Update(&c, hdr, 8); + +fprintf(stderr, "%lu objects\n", object_count); + cnt = htonl(object_count); + SHA1_Update(&c, &cnt, 4); + if (ywrite(packfd, &cnt, 4) != 4) + die("Failed writing object count: %s", strerror(errno)); + + buf = xmalloc(128 * 1024); + for (;;) { + n = xread(packfd, buf, 128 * 1024); + if (n <= 0) + break; + SHA1_Update(&c, buf, n); + } + free(buf); + + SHA1_Final(sha1, &c); + if (ywrite(packfd, sha1, sizeof(sha1)) != sizeof(sha1)) + die("Failed writing pack checksum: %s", strerror(errno)); +} + +int main (int argc, const char **argv) +{ + packfd = open(argv[1], O_RDWR|O_CREAT|O_TRUNC, 0666); + if (packfd < 0) + die("Can't create pack file %s: %s", argv[1], strerror(errno)); + + init_pack_header(); + for (;;) { + unsigned long datlen; + int hdrlen; + void *dat; + char hdr[128]; + unsigned char sha1[20]; + SHA_CTX c; + + if (yread(0, &datlen, 4) != 4) + break; + + dat = xmalloc(datlen); + if (yread(0, dat, datlen) != datlen) + break; + + hdrlen = sprintf(hdr, "blob %lu", datlen) + 1; + SHA1_Init(&c); + SHA1_Update(&c, hdr, hdrlen); + SHA1_Update(&c, dat, datlen); + SHA1_Final(sha1, &c); + + write_blob(dat, datlen); + object_count++; + printf("%s\n", sha1_to_hex(sha1)); + fflush(stdout); + + if (lastdat) + free(lastdat); + lastdat = dat; + lastdatlen = datlen; + memcpy(lastsha1, sha1, sizeof(sha1)); + } + fixup_header_footer(); + close(packfd); + + return 0; +} From 8bcce30126b90af83c1291e072f74950e73a2584 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 6 Aug 2006 13:51:39 -0400 Subject: [PATCH 160/548] Added automatic index generation to fast-import. Signed-off-by: Shawn O. Pearce --- fast-import.c | 182 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 163 insertions(+), 19 deletions(-) diff --git a/fast-import.c b/fast-import.c index 416ba5c7c6..0d95118499 100644 --- a/fast-import.c +++ b/fast-import.c @@ -8,11 +8,75 @@ static int max_depth = 10; static unsigned long object_count; +static unsigned long duplicate_count; +static unsigned long packoff; +static unsigned long overflow_count; static int packfd; static int current_depth; static void *lastdat; static unsigned long lastdatlen; static unsigned char lastsha1[20]; +static unsigned char packsha1[20]; + +struct object_entry +{ + struct object_entry *next; + unsigned long offset; + unsigned char sha1[20]; +}; + +struct overflow_object_entry +{ + struct overflow_object_entry *next; + struct object_entry oe; +}; + +struct object_entry *pool_start; +struct object_entry *pool_next; +struct object_entry *pool_end; +struct overflow_object_entry *overflow; +struct object_entry *table[1 << 16]; + +static struct object_entry* new_object(unsigned char *sha1) +{ + if (pool_next != pool_end) { + struct object_entry *e = pool_next++; + memcpy(e->sha1, sha1, sizeof(e->sha1)); + return e; + } else { + struct overflow_object_entry *e; + + e = xmalloc(sizeof(struct overflow_object_entry)); + e->next = overflow; + memcpy(e->oe.sha1, sha1, sizeof(e->oe.sha1)); + overflow = e; + overflow_count++; + return &e->oe; + } +} + +static struct object_entry* insert_object(unsigned char *sha1) +{ + unsigned int h = sha1[0] << 8 | sha1[1]; + struct object_entry *e = table[h]; + struct object_entry *p = 0; + + while (e) { + if (!memcmp(sha1, e->sha1, sizeof(e->sha1))) + return e; + p = e; + e = e->next; + } + + e = new_object(sha1); + e->next = 0; + e->offset = 0; + if (p) + p->next = e; + else + table[h] = e; + return e; +} static ssize_t yread(int fd, void *buffer, size_t length) { @@ -66,7 +130,7 @@ static unsigned long encode_header(enum object_type type, unsigned long size, un return n; } -static void write_blob (void *dat, unsigned long datlen) +static void write_blob(void *dat, unsigned long datlen) { z_stream s; void *out, *delta; @@ -92,6 +156,7 @@ static void write_blob (void *dat, unsigned long datlen) die("Can't write object header: %s", strerror(errno)); if (ywrite(packfd, lastsha1, sizeof(lastsha1)) != sizeof(lastsha1)) die("Can't write object base: %s", strerror(errno)); + packoff += hdrlen + sizeof(lastsha1); } else { current_depth = 0; s.next_in = dat; @@ -99,6 +164,7 @@ static void write_blob (void *dat, unsigned long datlen) hdrlen = encode_header(OBJ_BLOB, datlen, hdr); if (ywrite(packfd, hdr, hdrlen) != hdrlen) die("Can't write object header: %s", strerror(errno)); + packoff += hdrlen; } s.avail_out = deflateBound(&s, s.avail_in); @@ -109,13 +175,14 @@ static void write_blob (void *dat, unsigned long datlen) if (ywrite(packfd, out, s.total_out) != s.total_out) die("Failed writing compressed data %s", strerror(errno)); + packoff += s.total_out; free(out); if (delta) free(delta); } -static void init_pack_header () +static void init_pack_header() { const char* magic = "PACK"; unsigned long version = 2; @@ -129,13 +196,13 @@ static void init_pack_header () die("Can't write pack version: %s", strerror(errno)); if (ywrite(packfd, &zero, 4) != 4) die("Can't write 0 object count: %s", strerror(errno)); + packoff = 4 * 3; } -static void fixup_header_footer () +static void fixup_header_footer() { SHA_CTX c; char hdr[8]; - unsigned char sha1[20]; unsigned long cnt; char *buf; size_t n; @@ -148,7 +215,6 @@ static void fixup_header_footer () die("Failed reading header: %s", strerror(errno)); SHA1_Update(&c, hdr, 8); -fprintf(stderr, "%lu objects\n", object_count); cnt = htonl(object_count); SHA1_Update(&c, &cnt, 4); if (ywrite(packfd, &cnt, 4) != 4) @@ -163,16 +229,81 @@ fprintf(stderr, "%lu objects\n", object_count); } free(buf); - SHA1_Final(sha1, &c); - if (ywrite(packfd, sha1, sizeof(sha1)) != sizeof(sha1)) + SHA1_Final(packsha1, &c); + if (ywrite(packfd, packsha1, sizeof(packsha1)) != sizeof(packsha1)) die("Failed writing pack checksum: %s", strerror(errno)); } -int main (int argc, const char **argv) +static int oecmp (const void *_a, const void *_b) { - packfd = open(argv[1], O_RDWR|O_CREAT|O_TRUNC, 0666); + struct object_entry *a = *((struct object_entry**)_a); + struct object_entry *b = *((struct object_entry**)_b); + return memcmp(a->sha1, b->sha1, sizeof(a->sha1)); +} + +static void write_index(const char *idx_name) +{ + struct sha1file *f; + struct object_entry **idx, **c, **last; + struct object_entry *e; + struct overflow_object_entry *o; + unsigned int array[256]; + int i; + + /* Build the sorted table of object IDs. */ + idx = xmalloc(object_count * sizeof(struct object_entry*)); + c = idx; + for (e = pool_start; e != pool_next; e++) + *c++ = e; + for (o = overflow; o; o = o->next) + *c++ = &o->oe; + last = idx + object_count; + qsort(idx, object_count, sizeof(struct object_entry*), oecmp); + + /* Generate the fan-out array. */ + c = idx; + for (i = 0; i < 256; i++) { + struct object_entry **next = c;; + while (next < last) { + if ((*next)->sha1[0] != i) + break; + next++; + } + array[i] = htonl(next - idx); + c = next; + } + + f = sha1create("%s", idx_name); + sha1write(f, array, 256 * sizeof(int)); + for (c = idx; c != last; c++) { + unsigned int offset = htonl((*c)->offset); + sha1write(f, &offset, 4); + sha1write(f, (*c)->sha1, sizeof((*c)->sha1)); + } + sha1write(f, packsha1, sizeof(packsha1)); + sha1close(f, NULL, 1); + free(idx); +} + +int main(int argc, const char **argv) +{ + const char *base_name = argv[1]; + int est_obj_cnt = atoi(argv[2]); + char *pack_name; + char *idx_name; + + pack_name = xmalloc(strlen(base_name) + 6); + sprintf(pack_name, "%s.pack", base_name); + idx_name = xmalloc(strlen(base_name) + 5); + sprintf(idx_name, "%s.idx", base_name); + + packfd = open(pack_name, O_RDWR|O_CREAT|O_TRUNC, 0666); if (packfd < 0) - die("Can't create pack file %s: %s", argv[1], strerror(errno)); + die("Can't create pack file %s: %s", pack_name, strerror(errno)); + + pool_start = xmalloc(est_obj_cnt * sizeof(struct object_entry)); + pool_next = pool_start; + pool_end = pool_start + est_obj_cnt; init_pack_header(); for (;;) { @@ -182,8 +313,10 @@ int main (int argc, const char **argv) char hdr[128]; unsigned char sha1[20]; SHA_CTX c; + struct object_entry *e; if (yread(0, &datlen, 4) != 4) + break; dat = xmalloc(datlen); @@ -196,19 +329,30 @@ int main (int argc, const char **argv) SHA1_Update(&c, dat, datlen); SHA1_Final(sha1, &c); - write_blob(dat, datlen); - object_count++; - printf("%s\n", sha1_to_hex(sha1)); - fflush(stdout); + e = insert_object(sha1); + if (!e->offset) { + e->offset = packoff; + write_blob(dat, datlen); + object_count++; + printf("%s\n", sha1_to_hex(sha1)); + fflush(stdout); - if (lastdat) - free(lastdat); - lastdat = dat; - lastdatlen = datlen; - memcpy(lastsha1, sha1, sizeof(sha1)); + if (lastdat) + free(lastdat); + lastdat = dat; + lastdatlen = datlen; + memcpy(lastsha1, sha1, sizeof(sha1)); + } else { + duplicate_count++; + free(dat); + } } fixup_header_footer(); close(packfd); + write_index(idx_name); + + fprintf(stderr, "%lu objects, %lu duplicates, %lu pool overflow\n", + object_count, duplicate_count, overflow_count); return 0; } From 27d6d29035473f01ba5bb3b52c86ee4181d251fe Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 8 Aug 2006 00:03:59 -0400 Subject: [PATCH 161/548] Cleaned up memory allocation for object_entry structs. Although its easy to ask the user to tell us how many objects they will need, its probably better to dynamically grow the object table in large units. But if the user can give us a hint as to roughly how many objects then we can still use it during startup. Also stopped printing the SHA1 strings to stdout as no user is currently making use of that facility. Signed-off-by: Shawn O. Pearce --- fast-import.c | 97 +++++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/fast-import.c b/fast-import.c index 0d95118499..3856c87c4e 100644 --- a/fast-import.c +++ b/fast-import.c @@ -6,18 +6,6 @@ #include "pack.h" #include "csum-file.h" -static int max_depth = 10; -static unsigned long object_count; -static unsigned long duplicate_count; -static unsigned long packoff; -static unsigned long overflow_count; -static int packfd; -static int current_depth; -static void *lastdat; -static unsigned long lastdatlen; -static unsigned char lastsha1[20]; -static unsigned char packsha1[20]; - struct object_entry { struct object_entry *next; @@ -25,40 +13,57 @@ struct object_entry unsigned char sha1[20]; }; -struct overflow_object_entry +struct object_entry_block { - struct overflow_object_entry *next; - struct object_entry oe; + struct object_entry_block *next_block; + struct object_entry *next_free; + struct object_entry *end; + struct object_entry entries[0]; }; -struct object_entry *pool_start; -struct object_entry *pool_next; -struct object_entry *pool_end; -struct overflow_object_entry *overflow; -struct object_entry *table[1 << 16]; +static int max_depth = 10; +static unsigned long alloc_count; +static unsigned long object_count; +static unsigned long duplicate_count; +static unsigned long packoff; +static int packfd; +static int current_depth; +static void *lastdat; +static unsigned long lastdatlen; +static unsigned char lastsha1[20]; +static unsigned char packsha1[20]; +struct object_entry *object_table[1 << 16]; +struct object_entry_block *blocks; + +static void alloc_objects(int cnt) +{ + struct object_entry_block *b; + + b = xmalloc(sizeof(struct object_entry_block) + + cnt * sizeof(struct object_entry)); + b->next_block = blocks; + b->next_free = b->entries; + b->end = b->entries + cnt; + blocks = b; + alloc_count += cnt; +} static struct object_entry* new_object(unsigned char *sha1) { - if (pool_next != pool_end) { - struct object_entry *e = pool_next++; - memcpy(e->sha1, sha1, sizeof(e->sha1)); - return e; - } else { - struct overflow_object_entry *e; + struct object_entry *e; - e = xmalloc(sizeof(struct overflow_object_entry)); - e->next = overflow; - memcpy(e->oe.sha1, sha1, sizeof(e->oe.sha1)); - overflow = e; - overflow_count++; - return &e->oe; - } + if (blocks->next_free == blocks->end) + alloc_objects(1000); + + e = blocks->next_free++; + memcpy(e->sha1, sha1, sizeof(e->sha1)); + return e; } static struct object_entry* insert_object(unsigned char *sha1) { unsigned int h = sha1[0] << 8 | sha1[1]; - struct object_entry *e = table[h]; + struct object_entry *e = object_table[h]; struct object_entry *p = 0; while (e) { @@ -74,7 +79,7 @@ static struct object_entry* insert_object(unsigned char *sha1) if (p) p->next = e; else - table[h] = e; + object_table[h] = e; return e; } @@ -246,17 +251,16 @@ static void write_index(const char *idx_name) struct sha1file *f; struct object_entry **idx, **c, **last; struct object_entry *e; - struct overflow_object_entry *o; + struct object_entry_block *o; unsigned int array[256]; int i; /* Build the sorted table of object IDs. */ idx = xmalloc(object_count * sizeof(struct object_entry*)); c = idx; - for (e = pool_start; e != pool_next; e++) - *c++ = e; - for (o = overflow; o; o = o->next) - *c++ = &o->oe; + for (o = blocks; o; o = o->next_block) + for (e = o->entries; e != o->next_free; e++) + *c++ = e; last = idx + object_count; qsort(idx, object_count, sizeof(struct object_entry*), oecmp); @@ -297,14 +301,11 @@ int main(int argc, const char **argv) idx_name = xmalloc(strlen(base_name) + 5); sprintf(idx_name, "%s.idx", base_name); - packfd = open(pack_name, O_RDWR|O_CREAT|O_TRUNC, 0666); + packfd = open(pack_name, O_RDWR|O_CREAT|O_EXCL, 0666); if (packfd < 0) die("Can't create pack file %s: %s", pack_name, strerror(errno)); - pool_start = xmalloc(est_obj_cnt * sizeof(struct object_entry)); - pool_next = pool_start; - pool_end = pool_start + est_obj_cnt; - + alloc_objects(est_obj_cnt); init_pack_header(); for (;;) { unsigned long datlen; @@ -334,8 +335,6 @@ int main(int argc, const char **argv) e->offset = packoff; write_blob(dat, datlen); object_count++; - printf("%s\n", sha1_to_hex(sha1)); - fflush(stdout); if (lastdat) free(lastdat); @@ -351,8 +350,8 @@ int main(int argc, const char **argv) close(packfd); write_index(idx_name); - fprintf(stderr, "%lu objects, %lu duplicates, %lu pool overflow\n", - object_count, duplicate_count, overflow_count); + fprintf(stderr, "%lu objects, %lu duplicates, %lu allocated (%lu overflow)\n", + object_count, duplicate_count, alloc_count, alloc_count - est_obj_cnt); return 0; } From ac47a738a7c866eeffc0c6374c0ef3f7ca6ee79d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 8 Aug 2006 00:46:13 -0400 Subject: [PATCH 162/548] Refactored fast-import's internals for future additions. Too many globals variables were being used not not enough code was resuable to process trees and commits so this is a simple refactoring of the existing blob processing code to get into a state that will be easier to handle trees and commits in. Signed-off-by: Shawn O. Pearce --- fast-import.c | 149 ++++++++++++++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 66 deletions(-) diff --git a/fast-import.c b/fast-import.c index 3856c87c4e..8b4be28f60 100644 --- a/fast-import.c +++ b/fast-import.c @@ -18,22 +18,34 @@ struct object_entry_block struct object_entry_block *next_block; struct object_entry *next_free; struct object_entry *end; - struct object_entry entries[0]; + struct object_entry entries[FLEX_ARRAY]; /* more */ }; +struct last_object +{ + void *data; + unsigned long len; + int depth; + unsigned char sha1[20]; +}; + +/* Stats and misc. counters. */ static int max_depth = 10; static unsigned long alloc_count; static unsigned long object_count; static unsigned long duplicate_count; -static unsigned long packoff; -static int packfd; -static int current_depth; -static void *lastdat; -static unsigned long lastdatlen; -static unsigned char lastsha1[20]; -static unsigned char packsha1[20]; -struct object_entry *object_table[1 << 16]; + +/* The .pack file */ +static int pack_fd; +static unsigned long pack_offset; +static unsigned char pack_sha1[20]; + +/* Table of objects we've written. */ struct object_entry_block *blocks; +struct object_entry *object_table[1 << 16]; + +/* Our last blob */ +struct last_object last_blob; static void alloc_objects(int cnt) { @@ -115,7 +127,10 @@ static ssize_t ywrite(int fd, void *buffer, size_t length) return ret; } -static unsigned long encode_header(enum object_type type, unsigned long size, unsigned char *hdr) +static unsigned long encode_header( + enum object_type type, + unsigned long size, + unsigned char *hdr) { int n = 1; unsigned char c; @@ -135,41 +150,62 @@ static unsigned long encode_header(enum object_type type, unsigned long size, un return n; } -static void write_blob(void *dat, unsigned long datlen) +static int store_object( + enum object_type type, + void *dat, + unsigned long datlen, + struct last_object *last) { - z_stream s; void *out, *delta; - unsigned char hdr[64]; + struct object_entry *e; + unsigned char hdr[96]; + unsigned char sha1[20]; unsigned long hdrlen, deltalen; + SHA_CTX c; + z_stream s; - if (lastdat && current_depth < max_depth) { - delta = diff_delta(lastdat, lastdatlen, + hdrlen = sprintf((char*)hdr,"%s %lu",type_names[type],datlen) + 1; + SHA1_Init(&c); + SHA1_Update(&c, hdr, hdrlen); + SHA1_Update(&c, dat, datlen); + SHA1_Final(sha1, &c); + + e = insert_object(sha1); + if (e->offset) { + duplicate_count++; + return 0; + } + e->offset = pack_offset; + object_count++; + + if (last->data && last->depth < max_depth) + delta = diff_delta(last->data, last->len, dat, datlen, &deltalen, 0); - } else + else delta = 0; memset(&s, 0, sizeof(s)); deflateInit(&s, zlib_compression_level); if (delta) { - current_depth++; + last->depth++; s.next_in = delta; s.avail_in = deltalen; hdrlen = encode_header(OBJ_DELTA, deltalen, hdr); - if (ywrite(packfd, hdr, hdrlen) != hdrlen) + if (ywrite(pack_fd, hdr, hdrlen) != hdrlen) die("Can't write object header: %s", strerror(errno)); - if (ywrite(packfd, lastsha1, sizeof(lastsha1)) != sizeof(lastsha1)) + if (ywrite(pack_fd, last->sha1, sizeof(sha1)) != sizeof(sha1)) die("Can't write object base: %s", strerror(errno)); - packoff += hdrlen + sizeof(lastsha1); + pack_offset += hdrlen + sizeof(sha1); } else { - current_depth = 0; + last->depth = 0; s.next_in = dat; s.avail_in = datlen; - hdrlen = encode_header(OBJ_BLOB, datlen, hdr); - if (ywrite(packfd, hdr, hdrlen) != hdrlen) + hdrlen = encode_header(type, datlen, hdr); + if (ywrite(pack_fd, hdr, hdrlen) != hdrlen) die("Can't write object header: %s", strerror(errno)); - packoff += hdrlen; + pack_offset += hdrlen; } s.avail_out = deflateBound(&s, s.avail_in); @@ -178,13 +214,19 @@ static void write_blob(void *dat, unsigned long datlen) /* nothing */; deflateEnd(&s); - if (ywrite(packfd, out, s.total_out) != s.total_out) + if (ywrite(pack_fd, out, s.total_out) != s.total_out) die("Failed writing compressed data %s", strerror(errno)); - packoff += s.total_out; + pack_offset += s.total_out; free(out); if (delta) free(delta); + if (last->data) + free(last->data); + last->data = dat; + last->len = datlen; + memcpy(last->sha1, sha1, sizeof(sha1)); + return 1; } static void init_pack_header() @@ -195,13 +237,13 @@ static void init_pack_header() version = htonl(version); - if (ywrite(packfd, (char*)magic, 4) != 4) + if (ywrite(pack_fd, (char*)magic, 4) != 4) die("Can't write pack magic: %s", strerror(errno)); - if (ywrite(packfd, &version, 4) != 4) + if (ywrite(pack_fd, &version, 4) != 4) die("Can't write pack version: %s", strerror(errno)); - if (ywrite(packfd, &zero, 4) != 4) + if (ywrite(pack_fd, &zero, 4) != 4) die("Can't write 0 object count: %s", strerror(errno)); - packoff = 4 * 3; + pack_offset = 4 * 3; } static void fixup_header_footer() @@ -212,30 +254,30 @@ static void fixup_header_footer() char *buf; size_t n; - if (lseek(packfd, 0, SEEK_SET) != 0) + if (lseek(pack_fd, 0, SEEK_SET) != 0) die("Failed seeking to start: %s", strerror(errno)); SHA1_Init(&c); - if (yread(packfd, hdr, 8) != 8) + if (yread(pack_fd, hdr, 8) != 8) die("Failed reading header: %s", strerror(errno)); SHA1_Update(&c, hdr, 8); cnt = htonl(object_count); SHA1_Update(&c, &cnt, 4); - if (ywrite(packfd, &cnt, 4) != 4) + if (ywrite(pack_fd, &cnt, 4) != 4) die("Failed writing object count: %s", strerror(errno)); buf = xmalloc(128 * 1024); for (;;) { - n = xread(packfd, buf, 128 * 1024); + n = xread(pack_fd, buf, 128 * 1024); if (n <= 0) break; SHA1_Update(&c, buf, n); } free(buf); - SHA1_Final(packsha1, &c); - if (ywrite(packfd, packsha1, sizeof(packsha1)) != sizeof(packsha1)) + SHA1_Final(pack_sha1, &c); + if (ywrite(pack_fd, pack_sha1, sizeof(pack_sha1)) != sizeof(pack_sha1)) die("Failed writing pack checksum: %s", strerror(errno)); } @@ -284,7 +326,7 @@ static void write_index(const char *idx_name) sha1write(f, &offset, 4); sha1write(f, (*c)->sha1, sizeof((*c)->sha1)); } - sha1write(f, packsha1, sizeof(packsha1)); + sha1write(f, pack_sha1, sizeof(pack_sha1)); sha1close(f, NULL, 1); free(idx); } @@ -301,53 +343,28 @@ int main(int argc, const char **argv) idx_name = xmalloc(strlen(base_name) + 5); sprintf(idx_name, "%s.idx", base_name); - packfd = open(pack_name, O_RDWR|O_CREAT|O_EXCL, 0666); - if (packfd < 0) + pack_fd = open(pack_name, O_RDWR|O_CREAT|O_EXCL, 0666); + if (pack_fd < 0) die("Can't create pack file %s: %s", pack_name, strerror(errno)); alloc_objects(est_obj_cnt); init_pack_header(); for (;;) { unsigned long datlen; - int hdrlen; void *dat; - char hdr[128]; - unsigned char sha1[20]; - SHA_CTX c; - struct object_entry *e; if (yread(0, &datlen, 4) != 4) - break; dat = xmalloc(datlen); if (yread(0, dat, datlen) != datlen) break; - hdrlen = sprintf(hdr, "blob %lu", datlen) + 1; - SHA1_Init(&c); - SHA1_Update(&c, hdr, hdrlen); - SHA1_Update(&c, dat, datlen); - SHA1_Final(sha1, &c); - - e = insert_object(sha1); - if (!e->offset) { - e->offset = packoff; - write_blob(dat, datlen); - object_count++; - - if (lastdat) - free(lastdat); - lastdat = dat; - lastdatlen = datlen; - memcpy(lastsha1, sha1, sizeof(sha1)); - } else { - duplicate_count++; + if (!store_object(OBJ_BLOB, dat, datlen, &last_blob)) free(dat); - } } fixup_header_footer(); - close(packfd); + close(pack_fd); write_index(idx_name); fprintf(stderr, "%lu objects, %lu duplicates, %lu allocated (%lu overflow)\n", From 6143f0644e79686407c1dc0e1b4dadff74e80046 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 8 Aug 2006 01:14:21 -0400 Subject: [PATCH 163/548] Added basic command handler to fast-import. Moved the new_blob logic off into a new subroutine and invoked it when getting the 'blob' command. Added statistics dump to STDERR when the program terminates listing what it did at a high level. This is somewhat interesting. Signed-off-by: Shawn O. Pearce --- fast-import.c | 60 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/fast-import.c b/fast-import.c index 8b4be28f60..c9c48c5869 100644 --- a/fast-import.c +++ b/fast-import.c @@ -34,6 +34,8 @@ static int max_depth = 10; static unsigned long alloc_count; static unsigned long object_count; static unsigned long duplicate_count; +static unsigned long object_count_by_type[9]; +static unsigned long duplicate_count_by_type[9]; /* The .pack file */ static int pack_fd; @@ -173,10 +175,12 @@ static int store_object( e = insert_object(sha1); if (e->offset) { duplicate_count++; + duplicate_count_by_type[type]++; return 0; } e->offset = pack_offset; object_count++; + object_count_by_type[type]++; if (last->data && last->depth < max_depth) delta = diff_delta(last->data, last->len, @@ -232,7 +236,7 @@ static int store_object( static void init_pack_header() { const char* magic = "PACK"; - unsigned long version = 2; + unsigned long version = 3; unsigned long zero = 0; version = htonl(version); @@ -331,12 +335,29 @@ static void write_index(const char *idx_name) free(idx); } +static void new_blob() +{ + unsigned long datlen; + void *dat; + + if (yread(0, &datlen, 4) != 4) + die("Can't obtain blob length"); + + dat = xmalloc(datlen); + if (yread(0, dat, datlen) != datlen) + die("Con't obtain %lu bytes of blob data", datlen); + + if (!store_object(OBJ_BLOB, dat, datlen, &last_blob)) + free(dat); +} + int main(int argc, const char **argv) { const char *base_name = argv[1]; int est_obj_cnt = atoi(argv[2]); char *pack_name; char *idx_name; + struct stat sb; pack_name = xmalloc(strlen(base_name) + 6); sprintf(pack_name, "%s.pack", base_name); @@ -345,30 +366,41 @@ int main(int argc, const char **argv) pack_fd = open(pack_name, O_RDWR|O_CREAT|O_EXCL, 0666); if (pack_fd < 0) - die("Can't create pack file %s: %s", pack_name, strerror(errno)); + die("Can't create %s: %s", pack_name, strerror(errno)); alloc_objects(est_obj_cnt); init_pack_header(); for (;;) { - unsigned long datlen; - void *dat; - - if (yread(0, &datlen, 4) != 4) + unsigned long cmd; + if (yread(0, &cmd, 4) != 4) break; - dat = xmalloc(datlen); - if (yread(0, dat, datlen) != datlen) - break; - - if (!store_object(OBJ_BLOB, dat, datlen, &last_blob)) - free(dat); + switch (cmd) { + case 'blob': new_blob(); break; + default: + die("Invalid command %lu", cmd); + } } fixup_header_footer(); close(pack_fd); write_index(idx_name); - fprintf(stderr, "%lu objects, %lu duplicates, %lu allocated (%lu overflow)\n", - object_count, duplicate_count, alloc_count, alloc_count - est_obj_cnt); + fprintf(stderr, "%s statistics:\n", argv[0]); + fprintf(stderr, "---------------------------------------------------\n"); + fprintf(stderr, "Alloc'd objects: %10lu (%10lu overflow )\n", alloc_count, alloc_count - est_obj_cnt); + fprintf(stderr, "Total objects: %10lu (%10lu duplicates)\n", object_count, duplicate_count); + fprintf(stderr, " blobs : %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB]); + fprintf(stderr, " trees : %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE]); + fprintf(stderr, " commits: %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT]); + fprintf(stderr, " tags : %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG]); + fprintf(stderr, "---------------------------------------------------\n"); + + stat(pack_name, &sb); + fprintf(stderr, "Pack size: %10lu KiB\n", (unsigned long)(sb.st_size/1024)); + stat(idx_name, &sb); + fprintf(stderr, "Index size: %10lu KiB\n", (unsigned long)(sb.st_size/1024)); + + fprintf(stderr, "\n"); return 0; } From 6bb5b3291df99bf050c91ab742b406d2404b8f73 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 8 Aug 2006 03:36:45 -0400 Subject: [PATCH 164/548] Implemented branch handling and basic tree support in fast-import. This provides the basic data structures needed to store trees in memory while we are processing them for a branch. What we are attempting to do is track one complete tree for each branch that the frontend has registered with us through the 'newb' (new_branch) command. When the frontend edits that tree through 'updf' or 'delf' commands we'll mark the affected tree(s) as being dirty and recompute their objects during 'comt' (commit). Currently the protocol is decidedly _not_ user friendly. I crashed fast-import by giving it bad input data from Perl. I may try to improve upon it, or at least upon its error handling. Signed-off-by: Shawn O. Pearce --- fast-import.c | 170 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 165 insertions(+), 5 deletions(-) diff --git a/fast-import.c b/fast-import.c index c9c48c5869..98c5d1cbdd 100644 --- a/fast-import.c +++ b/fast-import.c @@ -24,14 +24,39 @@ struct object_entry_block struct last_object { void *data; - unsigned long len; - int depth; + unsigned int len; + unsigned int depth; unsigned char sha1[20]; }; +struct tree; +struct tree_entry +{ + struct tree *tree; + mode_t mode; + unsigned char sha1[20]; + char name[FLEX_ARRAY]; /* more */ +}; + +struct tree +{ + struct last_object last_tree; + unsigned long entry_count; + struct tree_entry **entries; +}; + +struct branch +{ + struct branch *next_branch; + struct tree_entry tree; + unsigned char sha1[20]; + const char *name; +}; + /* Stats and misc. counters. */ static int max_depth = 10; static unsigned long alloc_count; +static unsigned long branch_count; static unsigned long object_count; static unsigned long duplicate_count; static unsigned long object_count_by_type[9]; @@ -49,6 +74,10 @@ struct object_entry *object_table[1 << 16]; /* Our last blob */ struct last_object last_blob; +/* Branch data */ +struct branch *branches; +struct branch *current_branch; + static void alloc_objects(int cnt) { struct object_entry_block *b; @@ -129,6 +158,32 @@ static ssize_t ywrite(int fd, void *buffer, size_t length) return ret; } +static const char* read_string() +{ + static char sn[PATH_MAX]; + unsigned long slen; + + if (yread(0, &slen, 4) != 4) + die("Can't obtain string"); + if (!slen) + return 0; + if (slen > (PATH_MAX - 1)) + die("Can't handle excessive string length %lu", slen); + + if (yread(0, sn, slen) != slen) + die("Can't obtain string of length %lu", slen); + sn[slen] = 0; + return sn; +} + +static const char* read_required_string() +{ + const char *r = read_string(); + if (!r) + die("Expected string command parameter, didn't find one"); + return r; +} + static unsigned long encode_header( enum object_type type, unsigned long size, @@ -156,7 +211,8 @@ static int store_object( enum object_type type, void *dat, unsigned long datlen, - struct last_object *last) + struct last_object *last, + unsigned char *sha1out) { void *out, *delta; struct object_entry *e; @@ -171,6 +227,8 @@ static int store_object( SHA1_Update(&c, hdr, hdrlen); SHA1_Update(&c, dat, datlen); SHA1_Final(sha1, &c); + if (sha1out) + memcpy(sha1out, sha1, sizeof(sha1)); e = insert_object(sha1); if (e->offset) { @@ -347,10 +405,108 @@ static void new_blob() if (yread(0, dat, datlen) != datlen) die("Con't obtain %lu bytes of blob data", datlen); - if (!store_object(OBJ_BLOB, dat, datlen, &last_blob)) + if (!store_object(OBJ_BLOB, dat, datlen, &last_blob, 0)) free(dat); } +static struct branch* lookup_branch(const char *name) +{ + struct branch *b; + for (b = branches; b; b = b->next_branch) { + if (!strcmp(name, b->name)) + return b; + } + die("No branch named '%s' has been declared", name); +} + +static struct tree* deep_copy_tree (struct tree *t) +{ + struct tree *r = xmalloc(sizeof(struct tree)); + unsigned long i; + + if (t->last_tree.data) { + r->last_tree.data = xmalloc(t->last_tree.len); + r->last_tree.len = t->last_tree.len; + r->last_tree.depth = t->last_tree.depth; + memcpy(r->last_tree.data, t->last_tree.data, t->last_tree.len); + memcpy(r->last_tree.sha1, t->last_tree.sha1, sizeof(t->last_tree.sha1)); + } + + r->entry_count = t->entry_count; + r->entries = xmalloc(t->entry_count * sizeof(struct tree_entry*)); + for (i = 0; i < t->entry_count; i++) { + struct tree_entry *a = t->entries[i]; + struct tree_entry *b; + + b = xmalloc(sizeof(struct tree_entry) + strlen(a->name) + 1); + b->tree = a->tree ? deep_copy_tree(a->tree) : 0; + b->mode = a->mode; + memcpy(b->sha1, a->sha1, sizeof(a->sha1)); + strcpy(b->name, a->name); + r->entries[i] = b; + } + + return r; +} + +static void store_tree (struct tree_entry *e) +{ + struct tree *t = e->tree; + unsigned long maxlen, i; + char *buf, *c; + + if (memcmp(null_sha1, e->sha1, sizeof(e->sha1))) + return; + + maxlen = t->entry_count * 32; + for (i = 0; i < t->entry_count; i++) + maxlen += strlen(t->entries[i]->name); + + buf = c = xmalloc(maxlen); + for (i = 0; i < t->entry_count; i++) { + struct tree_entry *e = t->entries[i]; + c += sprintf(c, "%o %s", e->mode, e->name) + 1; + if (e->tree) + store_tree(e); + memcpy(c, e->sha1, sizeof(e->sha1)); + c += sizeof(e->sha1); + } + + if (!store_object(OBJ_TREE, buf, c - buf, &t->last_tree, e->sha1)) + free(buf); +} + +static void new_branch() +{ + struct branch *nb = xcalloc(1, sizeof(struct branch)); + const char *source_name; + + nb->name = strdup(read_required_string()); + source_name = read_string(); + if (source_name) { + struct branch *sb = lookup_branch(source_name); + nb->tree.tree = deep_copy_tree(sb->tree.tree); + memcpy(nb->tree.sha1, sb->tree.sha1, sizeof(sb->tree.sha1)); + memcpy(nb->sha1, sb->sha1, sizeof(sb->sha1)); + } else { + nb->tree.tree = xcalloc(1, sizeof(struct tree)); + nb->tree.tree->entries = xmalloc(8*sizeof(struct tree_entry*)); + } + nb->next_branch = branches; + branches = nb; + branch_count++; +} + +static void set_branch() +{ + current_branch = lookup_branch(read_required_string()); +} + +static void commit() +{ + store_tree(¤t_branch->tree); +} + int main(int argc, const char **argv) { const char *base_name = argv[1]; @@ -376,7 +532,10 @@ int main(int argc, const char **argv) break; switch (cmd) { - case 'blob': new_blob(); break; + case 'blob': new_blob(); break; + case 'newb': new_branch(); break; + case 'setb': set_branch(); break; + case 'comt': commit(); break; default: die("Invalid command %lu", cmd); } @@ -393,6 +552,7 @@ int main(int argc, const char **argv) fprintf(stderr, " trees : %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE]); fprintf(stderr, " commits: %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT]); fprintf(stderr, " tags : %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG]); + fprintf(stderr, "Total branches: %10lu\n", branch_count); fprintf(stderr, "---------------------------------------------------\n"); stat(pack_name, &sb); From 463acbe1c60fc5009dac9d033df6c2b9c5037a91 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 14 Aug 2006 00:58:19 -0400 Subject: [PATCH 165/548] Added tree and commit writing to fast-import. The tree of the current commit can be altered by file_change commands before the commit gets written to the pack. The file changes are rather primitive as they simply allow removal of a tree entry or setting/adding a tree entry. Currently trees and commits aren't being deltafied when written to the pack and branch reloading from the current pack doesn't work, so at most 5 branches can be worked with at any one time. Signed-off-by: Shawn O. Pearce --- fast-import.c | 914 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 733 insertions(+), 181 deletions(-) diff --git a/fast-import.c b/fast-import.c index 98c5d1cbdd..4605b7469b 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1,9 +1,70 @@ +/* +Format of STDIN stream: + + stream ::= cmd*; + + cmd ::= new_blob + | new_commit + | new_branch + | new_tag + ; + + new_blob ::= 'blob' blob_data; + + new_commit ::= 'comt' ref_name author_committer_msg + file_change* + '0'; + + new_branch ::= 'brch' dst_ref_name src_ref_name; + dst_ref_name ::= ref_name; + src_ref_name ::= ref_name | sha1_exp; + + new_tag ::= 'tagg' ref_name tag_name tagger_msg; + + file_change ::= 'M' path_name hexsha1 + | 'D' path_name + ; + + author_committer_msg ::= len32 + 'author' sp name '<' email '>' ts tz lf + 'committer' sp name '<' email '>' ts tz lf + lf + binary_data; + + tagger_msg ::= len32 + 'tagger' sp name '<' email '>' ts tz lf + lf + binary_data; + + blob_data ::= len32 binary_data; # max len is 2^32-1 + path_name ::= len32 path; # max len is PATH_MAX-1 + ref_name ::= len32 ref; # max len is PATH_MAX-1 + tag_name ::= len32 tag; # max len is PATH_MAX-1 + sha1_exp ::= len32 sha1exp; # max len is PATH_MAX-1 + + len32 ::= # unsigned 32 bit value, native format; + binary_data ::= # file content, not interpreted; + sp ::= # ASCII space character; + lf ::= # ASCII newline (LF) character; + path ::= # GIT style file path, e.g. "a/b/c"; + ref ::= # GIT ref name, e.g. "refs/heads/MOZ_GECKO_EXPERIMENT"; + tag ::= # GIT tag name, e.g. "FIREFOX_1_5"; + sha1exp ::= # Any valid GIT SHA1 expression; + hexsha1 ::= # SHA1 in hexadecimal format; + name ::= # valid GIT author/committer name; + email ::= # valid GIT author/committer email; + ts ::= # time since the epoch in seconds, ascii decimal; + tz ::= # GIT style timezone; +*/ + #include "builtin.h" #include "cache.h" #include "object.h" #include "blob.h" +#include "tree.h" #include "delta.h" #include "pack.h" +#include "refs.h" #include "csum-file.h" struct object_entry @@ -13,9 +74,9 @@ struct object_entry unsigned char sha1[20]; }; -struct object_entry_block +struct object_entry_pool { - struct object_entry_block *next_block; + struct object_entry_pool *next_pool; struct object_entry *next_free; struct object_entry *end; struct object_entry entries[FLEX_ARRAY]; /* more */ @@ -29,31 +90,55 @@ struct last_object unsigned char sha1[20]; }; -struct tree; -struct tree_entry +struct mem_pool { - struct tree *tree; - mode_t mode; - unsigned char sha1[20]; - char name[FLEX_ARRAY]; /* more */ + struct mem_pool *next_pool; + char *next_free; + char *end; + char space[FLEX_ARRAY]; /* more */ }; -struct tree +struct atom_str { - struct last_object last_tree; - unsigned long entry_count; - struct tree_entry **entries; + struct atom_str *next_atom; + int str_len; + char str_dat[FLEX_ARRAY]; /* more */ +}; + +struct tree_content; +struct tree_entry +{ + struct tree_content *tree; + struct atom_str* name; + unsigned int mode; + unsigned char sha1[20]; +}; + +struct tree_content +{ + unsigned int entry_capacity; /* must match avail_tree_content */ + unsigned int entry_count; + struct tree_entry *entries[FLEX_ARRAY]; /* more */ +}; + +struct avail_tree_content +{ + unsigned int entry_capacity; /* must match tree_content */ + struct avail_tree_content *next_avail; }; struct branch { - struct branch *next_branch; - struct tree_entry tree; - unsigned char sha1[20]; + struct branch *table_next_branch; + struct branch *active_next_branch; const char *name; + unsigned long last_commit; + struct tree_entry branch_tree; + unsigned char sha1[20]; }; -/* Stats and misc. counters. */ + +/* Stats and misc. counters */ static int max_depth = 10; static unsigned long alloc_count; static unsigned long branch_count; @@ -62,29 +147,50 @@ static unsigned long duplicate_count; static unsigned long object_count_by_type[9]; static unsigned long duplicate_count_by_type[9]; -/* The .pack file */ +/* Memory pools */ +static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool); +static size_t total_allocd; +static struct mem_pool *mem_pool; + +/* atom management */ +static unsigned int atom_table_sz = 4451; +static unsigned int atom_cnt; +static struct atom_str **atom_table; + +/* The .pack file being generated */ static int pack_fd; static unsigned long pack_offset; static unsigned char pack_sha1[20]; /* Table of objects we've written. */ -struct object_entry_block *blocks; -struct object_entry *object_table[1 << 16]; +static unsigned int object_entry_alloc = 1000; +static struct object_entry_pool *blocks; +static struct object_entry *object_table[1 << 16]; /* Our last blob */ -struct last_object last_blob; +static struct last_object last_blob; + +/* Tree management */ +static unsigned int tree_entry_alloc = 1000; +static void *avail_tree_entry; +static unsigned int avail_tree_table_sz = 100; +static struct avail_tree_content **avail_tree_table; /* Branch data */ -struct branch *branches; -struct branch *current_branch; +static unsigned int max_active_branches = 5; +static unsigned int cur_active_branches; +static unsigned int branch_table_sz = 1039; +static struct branch **branch_table; +static struct branch *active_branches; + static void alloc_objects(int cnt) { - struct object_entry_block *b; + struct object_entry_pool *b; - b = xmalloc(sizeof(struct object_entry_block) + b = xmalloc(sizeof(struct object_entry_pool) + cnt * sizeof(struct object_entry)); - b->next_block = blocks; + b->next_pool = blocks; b->next_free = b->entries; b->end = b->entries + cnt; blocks = b; @@ -96,18 +202,28 @@ static struct object_entry* new_object(unsigned char *sha1) struct object_entry *e; if (blocks->next_free == blocks->end) - alloc_objects(1000); + alloc_objects(object_entry_alloc); e = blocks->next_free++; memcpy(e->sha1, sha1, sizeof(e->sha1)); return e; } +static struct object_entry* find_object(unsigned char *sha1) +{ + unsigned int h = sha1[0] << 8 | sha1[1]; + struct object_entry *e; + for (e = object_table[h]; e; e = e->next) + if (!memcmp(sha1, e->sha1, sizeof(e->sha1))) + return e; + return NULL; +} + static struct object_entry* insert_object(unsigned char *sha1) { unsigned int h = sha1[0] << 8 | sha1[1]; struct object_entry *e = object_table[h]; - struct object_entry *p = 0; + struct object_entry *p = NULL; while (e) { if (!memcmp(sha1, e->sha1, sizeof(e->sha1))) @@ -117,7 +233,7 @@ static struct object_entry* insert_object(unsigned char *sha1) } e = new_object(sha1); - e->next = 0; + e->next = NULL; e->offset = 0; if (p) p->next = e; @@ -126,64 +242,240 @@ static struct object_entry* insert_object(unsigned char *sha1) return e; } -static ssize_t yread(int fd, void *buffer, size_t length) +static unsigned int hc_str(const char *s, size_t len) +{ + unsigned int r = 0; + while (len-- > 0) + r = r * 31 + *s++; + return r; +} + +static void* pool_alloc(size_t len) +{ + struct mem_pool *p; + void *r; + + for (p = mem_pool; p; p = p->next_pool) + if ((p->end - p->next_free >= len)) + break; + + if (!p) { + if (len >= (mem_pool_alloc/2)) { + total_allocd += len; + return xmalloc(len); + } + total_allocd += sizeof(struct mem_pool) + mem_pool_alloc; + p = xmalloc(sizeof(struct mem_pool) + mem_pool_alloc); + p->next_pool = mem_pool; + p->next_free = p->space; + p->end = p->next_free + mem_pool_alloc; + mem_pool = p; + } + + r = p->next_free; + p->next_free += len; + return r; +} + +static void* pool_calloc(size_t count, size_t size) +{ + size_t len = count * size; + void *r = pool_alloc(len); + memset(r, 0, len); + return r; +} + +static char* pool_strdup(const char *s) +{ + char *r = pool_alloc(strlen(s) + 1); + strcpy(r, s); + return r; +} + +static struct atom_str* to_atom(const char *s, size_t len) +{ + unsigned int hc = hc_str(s, len) % atom_table_sz; + struct atom_str *c; + + for (c = atom_table[hc]; c; c = c->next_atom) + if (c->str_len == len && !strncmp(s, c->str_dat, len)) + return c; + + c = pool_alloc(sizeof(struct atom_str) + len + 1); + c->str_len = len; + strncpy(c->str_dat, s, len); + c->str_dat[len] = 0; + c->next_atom = atom_table[hc]; + atom_table[hc] = c; + atom_cnt++; + return c; +} + +static struct branch* lookup_branch(const char *name) +{ + unsigned int hc = hc_str(name, strlen(name)) % branch_table_sz; + struct branch *b; + + for (b = branch_table[hc]; b; b = b->table_next_branch) + if (!strcmp(name, b->name)) + return b; + return NULL; +} + +static struct branch* new_branch(const char *name) +{ + unsigned int hc = hc_str(name, strlen(name)) % branch_table_sz; + struct branch* b = lookup_branch(name); + + if (b) + die("Invalid attempt to create duplicate branch: %s", name); + + b = pool_calloc(1, sizeof(struct branch)); + b->name = pool_strdup(name); + b->table_next_branch = branch_table[hc]; + branch_table[hc] = b; + branch_count++; + return b; +} + +static unsigned int hc_entries(unsigned int cnt) +{ + cnt = cnt & 7 ? (cnt / 8) + 1 : cnt / 8; + return cnt < avail_tree_table_sz ? cnt : avail_tree_table_sz - 1; +} + +static struct tree_content* new_tree_content(unsigned int cnt) +{ + struct avail_tree_content *f, *l = NULL; + struct tree_content *t; + unsigned int hc = hc_entries(cnt); + + for (f = avail_tree_table[hc]; f; l = f, f = f->next_avail) + if (f->entry_capacity >= cnt) + break; + + if (f) { + if (l) + l->next_avail = f->next_avail; + else + avail_tree_table[hc] = f->next_avail; + } else { + cnt = cnt & 7 ? ((cnt / 8) + 1) * 8 : cnt; + f = pool_alloc(sizeof(*t) + sizeof(t->entries[0]) * cnt); + f->entry_capacity = cnt; + } + + t = (struct tree_content*)f; + t->entry_count = 0; + return t; +} + +static void release_tree_entry(struct tree_entry *e); +static void release_tree_content(struct tree_content *t) +{ + struct avail_tree_content *f = (struct avail_tree_content*)t; + unsigned int hc = hc_entries(f->entry_capacity); + unsigned int i; + for (i = 0; i < t->entry_count; i++) + release_tree_entry(t->entries[i]); + f->next_avail = avail_tree_table[hc]; + avail_tree_table[hc] = f; +} + +static struct tree_content* grow_tree_content( + struct tree_content *t, + int amt) +{ + struct tree_content *r = new_tree_content(t->entry_count + amt); + r->entry_count = t->entry_count; + memcpy(r->entries,t->entries,t->entry_count*sizeof(t->entries[0])); + release_tree_content(t); + return r; +} + +static struct tree_entry* new_tree_entry() +{ + struct tree_entry *e; + + if (!avail_tree_entry) { + unsigned int n = tree_entry_alloc; + avail_tree_entry = e = xmalloc(n * sizeof(struct tree_entry)); + while (n--) { + *((void**)e) = e + 1; + e++; + } + } + + e = avail_tree_entry; + avail_tree_entry = *((void**)e); + return e; +} + +static void release_tree_entry(struct tree_entry *e) +{ + if (e->tree) + release_tree_content(e->tree); + *((void**)e) = avail_tree_entry; + avail_tree_entry = e; +} + +static void yread(int fd, void *buffer, size_t length) { ssize_t ret = 0; while (ret < length) { ssize_t size = xread(fd, (char *) buffer + ret, length - ret); - if (size < 0) { - return size; - } - if (size == 0) { - return ret; - } + if (!size) + die("Read from descriptor %i: end of stream", fd); + if (size < 0) + die("Read from descriptor %i: %s", fd, strerror(errno)); ret += size; } - return ret; } -static ssize_t ywrite(int fd, void *buffer, size_t length) +static int optional_read(int fd, void *buffer, size_t length) +{ + ssize_t ret = 0; + while (ret < length) { + ssize_t size = xread(fd, (char *) buffer + ret, length - ret); + if (!size && !ret) + return 1; + if (!size) + die("Read from descriptor %i: end of stream", fd); + if (size < 0) + die("Read from descriptor %i: %s", fd, strerror(errno)); + ret += size; + } + return 0; +} + +static void ywrite(int fd, void *buffer, size_t length) { ssize_t ret = 0; while (ret < length) { ssize_t size = xwrite(fd, (char *) buffer + ret, length - ret); - if (size < 0) { - return size; - } - if (size == 0) { - return ret; - } + if (!size) + die("Write to descriptor %i: end of file", fd); + if (size < 0) + die("Write to descriptor %i: %s", fd, strerror(errno)); ret += size; } - return ret; } -static const char* read_string() +static const char* read_path() { static char sn[PATH_MAX]; unsigned long slen; - if (yread(0, &slen, 4) != 4) - die("Can't obtain string"); + yread(0, &slen, 4); if (!slen) - return 0; + die("Expected string command parameter, didn't find one"); if (slen > (PATH_MAX - 1)) die("Can't handle excessive string length %lu", slen); - - if (yread(0, sn, slen) != slen) - die("Can't obtain string of length %lu", slen); + yread(0, sn, slen); sn[slen] = 0; return sn; } -static const char* read_required_string() -{ - const char *r = read_string(); - if (!r) - die("Expected string command parameter, didn't find one"); - return r; -} - static unsigned long encode_header( enum object_type type, unsigned long size, @@ -234,13 +526,13 @@ static int store_object( if (e->offset) { duplicate_count++; duplicate_count_by_type[type]++; - return 0; + return 1; } e->offset = pack_offset; object_count++; object_count_by_type[type]++; - if (last->data && last->depth < max_depth) + if (last && last->data && last->depth < max_depth) delta = diff_delta(last->data, last->len, dat, datlen, &deltalen, 0); @@ -255,18 +547,16 @@ static int store_object( s.next_in = delta; s.avail_in = deltalen; hdrlen = encode_header(OBJ_DELTA, deltalen, hdr); - if (ywrite(pack_fd, hdr, hdrlen) != hdrlen) - die("Can't write object header: %s", strerror(errno)); - if (ywrite(pack_fd, last->sha1, sizeof(sha1)) != sizeof(sha1)) - die("Can't write object base: %s", strerror(errno)); + ywrite(pack_fd, hdr, hdrlen); + ywrite(pack_fd, last->sha1, sizeof(sha1)); pack_offset += hdrlen + sizeof(sha1); } else { - last->depth = 0; + if (last) + last->depth = 0; s.next_in = dat; s.avail_in = datlen; hdrlen = encode_header(type, datlen, hdr); - if (ywrite(pack_fd, hdr, hdrlen) != hdrlen) - die("Can't write object header: %s", strerror(errno)); + ywrite(pack_fd, hdr, hdrlen); pack_offset += hdrlen; } @@ -276,18 +566,220 @@ static int store_object( /* nothing */; deflateEnd(&s); - if (ywrite(pack_fd, out, s.total_out) != s.total_out) - die("Failed writing compressed data %s", strerror(errno)); + ywrite(pack_fd, out, s.total_out); pack_offset += s.total_out; free(out); if (delta) free(delta); - if (last->data) - free(last->data); - last->data = dat; - last->len = datlen; - memcpy(last->sha1, sha1, sizeof(sha1)); + if (last) { + if (last->data) + free(last->data); + last->data = dat; + last->len = datlen; + memcpy(last->sha1, sha1, sizeof(sha1)); + } + return 0; +} + +static const char *get_mode(const char *str, unsigned int *modep) +{ + unsigned char c; + unsigned int mode = 0; + + while ((c = *str++) != ' ') { + if (c < '0' || c > '7') + return NULL; + mode = (mode << 3) + (c - '0'); + } + *modep = mode; + return str; +} + +static void load_tree(struct tree_entry *root) +{ + struct object_entry *myoe; + struct tree_content *t; + unsigned long size; + char *buf; + const char *c; + char type[20]; + + root->tree = t = new_tree_content(8); + if (!memcmp(root->sha1, null_sha1, 20)) + return; + + myoe = find_object(root->sha1); + if (myoe) { + die("FIXME"); + } else { + buf = read_sha1_file(root->sha1, type, &size); + if (!buf || strcmp(type, tree_type)) + die("Can't load existing tree %s", sha1_to_hex(root->sha1)); + } + + c = buf; + while (c != (buf + size)) { + struct tree_entry *e = new_tree_entry(); + + if (t->entry_count == t->entry_capacity) + root->tree = t = grow_tree_content(t, 8); + t->entries[t->entry_count++] = e; + + e->tree = NULL; + c = get_mode(c, &e->mode); + if (!c) + die("Corrupt mode in %s", sha1_to_hex(root->sha1)); + e->name = to_atom(c, strlen(c)); + c += e->name->str_len + 1; + memcpy(e->sha1, c, sizeof(e->sha1)); + c += 20; + } + free(buf); +} + +static int tecmp (const void *_a, const void *_b) +{ + struct tree_entry *a = *((struct tree_entry**)_a); + struct tree_entry *b = *((struct tree_entry**)_b); + return base_name_compare( + a->name->str_dat, a->name->str_len, a->mode, + b->name->str_dat, b->name->str_len, b->mode); +} + +static void store_tree(struct tree_entry *root) +{ + struct tree_content *t = root->tree; + unsigned int i; + size_t maxlen; + char *buf, *c; + + if (memcmp(root->sha1, null_sha1, 20)) + return; + + maxlen = 0; + for (i = 0; i < t->entry_count; i++) { + maxlen += t->entries[i]->name->str_len + 34; + if (t->entries[i]->tree) + store_tree(t->entries[i]); + } + + qsort(t->entries, t->entry_count, sizeof(t->entries[0]), tecmp); + buf = c = xmalloc(maxlen); + for (i = 0; i < t->entry_count; i++) { + struct tree_entry *e = t->entries[i]; + c += sprintf(c, "%o", e->mode); + *c++ = ' '; + strcpy(c, e->name->str_dat); + c += e->name->str_len + 1; + memcpy(c, e->sha1, 20); + c += 20; + } + store_object(OBJ_TREE, buf, c - buf, NULL, root->sha1); + free(buf); +} + +static int tree_content_set( + struct tree_entry *root, + const char *p, + const unsigned char *sha1, + const unsigned int mode) +{ + struct tree_content *t = root->tree; + const char *slash1; + unsigned int i, n; + struct tree_entry *e; + + slash1 = strchr(p, '/'); + if (slash1) + n = slash1 - p; + else + n = strlen(p); + + for (i = 0; i < t->entry_count; i++) { + e = t->entries[i]; + if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) { + if (!slash1) { + if (e->mode == mode && !memcmp(e->sha1, sha1, 20)) + return 0; + e->mode = mode; + memcpy(e->sha1, sha1, 20); + if (e->tree) { + release_tree_content(e->tree); + e->tree = NULL; + } + memcpy(root->sha1, null_sha1, 20); + return 1; + } + if (!S_ISDIR(e->mode)) { + e->tree = new_tree_content(8); + e->mode = 040000; + } + if (!e->tree) + load_tree(e); + if (tree_content_set(e, slash1 + 1, sha1, mode)) { + memcpy(root->sha1, null_sha1, 20); + return 1; + } + return 0; + } + } + + if (t->entry_count == t->entry_capacity) + root->tree = t = grow_tree_content(t, 8); + e = new_tree_entry(); + e->name = to_atom(p, n); + t->entries[t->entry_count++] = e; + if (slash1) { + e->tree = new_tree_content(8); + e->mode = 040000; + tree_content_set(e, slash1 + 1, sha1, mode); + } else { + e->tree = NULL; + e->mode = mode; + memcpy(e->sha1, sha1, 20); + } + memcpy(root->sha1, null_sha1, 20); + return 1; +} + +static int tree_content_remove(struct tree_entry *root, const char *p) +{ + struct tree_content *t = root->tree; + const char *slash1; + unsigned int i, n; + struct tree_entry *e; + + slash1 = strchr(p, '/'); + if (slash1) + n = slash1 - p; + else + n = strlen(p); + + for (i = 0; i < t->entry_count; i++) { + e = t->entries[i]; + if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) { + if (!slash1 || !S_ISDIR(e->mode)) + goto del_entry; + if (!e->tree) + load_tree(e); + if (tree_content_remove(e, slash1 + 1)) { + if (!e->tree->entry_count) + goto del_entry; + memcpy(root->sha1, null_sha1, 20); + return 1; + } + return 0; + } + } + return 0; + +del_entry: + for (i++; i < t->entry_count; i++) + t->entries[i-1] = t->entries[i]; + t->entry_count--; + release_tree_entry(e); + memcpy(root->sha1, null_sha1, 20); return 1; } @@ -298,13 +790,9 @@ static void init_pack_header() unsigned long zero = 0; version = htonl(version); - - if (ywrite(pack_fd, (char*)magic, 4) != 4) - die("Can't write pack magic: %s", strerror(errno)); - if (ywrite(pack_fd, &version, 4) != 4) - die("Can't write pack version: %s", strerror(errno)); - if (ywrite(pack_fd, &zero, 4) != 4) - die("Can't write 0 object count: %s", strerror(errno)); + ywrite(pack_fd, (char*)magic, 4); + ywrite(pack_fd, &version, 4); + ywrite(pack_fd, &zero, 4); pack_offset = 4 * 3; } @@ -320,14 +808,12 @@ static void fixup_header_footer() die("Failed seeking to start: %s", strerror(errno)); SHA1_Init(&c); - if (yread(pack_fd, hdr, 8) != 8) - die("Failed reading header: %s", strerror(errno)); + yread(pack_fd, hdr, 8); SHA1_Update(&c, hdr, 8); cnt = htonl(object_count); SHA1_Update(&c, &cnt, 4); - if (ywrite(pack_fd, &cnt, 4) != 4) - die("Failed writing object count: %s", strerror(errno)); + ywrite(pack_fd, &cnt, 4); buf = xmalloc(128 * 1024); for (;;) { @@ -339,8 +825,7 @@ static void fixup_header_footer() free(buf); SHA1_Final(pack_sha1, &c); - if (ywrite(pack_fd, pack_sha1, sizeof(pack_sha1)) != sizeof(pack_sha1)) - die("Failed writing pack checksum: %s", strerror(errno)); + ywrite(pack_fd, pack_sha1, sizeof(pack_sha1)); } static int oecmp (const void *_a, const void *_b) @@ -355,14 +840,14 @@ static void write_index(const char *idx_name) struct sha1file *f; struct object_entry **idx, **c, **last; struct object_entry *e; - struct object_entry_block *o; + struct object_entry_pool *o; unsigned int array[256]; int i; /* Build the sorted table of object IDs. */ idx = xmalloc(object_count * sizeof(struct object_entry*)); c = idx; - for (o = blocks; o; o = o->next_block) + for (o = blocks; o; o = o->next_pool) for (e = o->entries; e != o->next_free; e++) *c++ = e; last = idx + object_count; @@ -393,118 +878,175 @@ static void write_index(const char *idx_name) free(idx); } -static void new_blob() +static void dump_branches() +{ + static const char *msg = "fast-import"; + unsigned int i; + struct branch *b; + struct ref_lock *lock; + + for (i = 0; i < branch_table_sz; i++) { + for (b = branch_table[i]; b; b = b->table_next_branch) { + lock = lock_any_ref_for_update(b->name, NULL, 0); + if (!lock || write_ref_sha1(lock, b->sha1, msg) < 0) + die("Can't write %s", b->name); + } + } +} + +static void cmd_new_blob() { unsigned long datlen; + unsigned char sha1[20]; void *dat; - if (yread(0, &datlen, 4) != 4) - die("Can't obtain blob length"); - + yread(0, &datlen, 4); dat = xmalloc(datlen); - if (yread(0, dat, datlen) != datlen) - die("Con't obtain %lu bytes of blob data", datlen); - - if (!store_object(OBJ_BLOB, dat, datlen, &last_blob, 0)) + yread(0, dat, datlen); + if (store_object(OBJ_BLOB, dat, datlen, &last_blob, sha1)) free(dat); } -static struct branch* lookup_branch(const char *name) +static void unload_one_branch() { - struct branch *b; - for (b = branches; b; b = b->next_branch) { - if (!strcmp(name, b->name)) - return b; + while (cur_active_branches >= max_active_branches) { + unsigned long min_commit = ULONG_MAX; + struct branch *e, *l = NULL, *p = NULL; + + for (e = active_branches; e; e = e->active_next_branch) { + if (e->last_commit < min_commit) { + p = l; + min_commit = e->last_commit; + } + l = e; + } + + if (p) { + e = p->active_next_branch; + p->active_next_branch = e->active_next_branch; + } else { + e = active_branches; + active_branches = e->active_next_branch; + } + e->active_next_branch = NULL; + if (e->branch_tree.tree) { + release_tree_content(e->branch_tree.tree); + e->branch_tree.tree = NULL; + } + cur_active_branches--; } - die("No branch named '%s' has been declared", name); } -static struct tree* deep_copy_tree (struct tree *t) +static void load_branch(struct branch *b) { - struct tree *r = xmalloc(sizeof(struct tree)); - unsigned long i; - - if (t->last_tree.data) { - r->last_tree.data = xmalloc(t->last_tree.len); - r->last_tree.len = t->last_tree.len; - r->last_tree.depth = t->last_tree.depth; - memcpy(r->last_tree.data, t->last_tree.data, t->last_tree.len); - memcpy(r->last_tree.sha1, t->last_tree.sha1, sizeof(t->last_tree.sha1)); - } - - r->entry_count = t->entry_count; - r->entries = xmalloc(t->entry_count * sizeof(struct tree_entry*)); - for (i = 0; i < t->entry_count; i++) { - struct tree_entry *a = t->entries[i]; - struct tree_entry *b; - - b = xmalloc(sizeof(struct tree_entry) + strlen(a->name) + 1); - b->tree = a->tree ? deep_copy_tree(a->tree) : 0; - b->mode = a->mode; - memcpy(b->sha1, a->sha1, sizeof(a->sha1)); - strcpy(b->name, a->name); - r->entries[i] = b; - } - - return r; + load_tree(&b->branch_tree); + b->active_next_branch = active_branches; + active_branches = b; + cur_active_branches++; } -static void store_tree (struct tree_entry *e) +static void file_change_m(struct branch *b) { - struct tree *t = e->tree; - unsigned long maxlen, i; - char *buf, *c; + const char *path = read_path(); + char hexsha1[41]; + unsigned char sha1[20]; - if (memcmp(null_sha1, e->sha1, sizeof(e->sha1))) - return; + yread(0, hexsha1, 40); + hexsha1[40] = 0; - maxlen = t->entry_count * 32; - for (i = 0; i < t->entry_count; i++) - maxlen += strlen(t->entries[i]->name); + if (get_sha1_hex(hexsha1, sha1)) + die("Invalid sha1 %s for %s", hexsha1, path); - buf = c = xmalloc(maxlen); - for (i = 0; i < t->entry_count; i++) { - struct tree_entry *e = t->entries[i]; - c += sprintf(c, "%o %s", e->mode, e->name) + 1; - if (e->tree) - store_tree(e); - memcpy(c, e->sha1, sizeof(e->sha1)); - c += sizeof(e->sha1); + tree_content_set(&b->branch_tree, path, sha1, 0100644); +} + +static void file_change_d(struct branch *b) +{ + tree_content_remove(&b->branch_tree, read_path()); +} + +static void cmd_new_commit() +{ + static const unsigned int max_hdr_len = 94; + const char *name = read_path(); + struct branch *b = lookup_branch(name); + unsigned int acmsglen; + char *body, *c; + + if (!b) + die("Branch not declared: %s", name); + if (!b->branch_tree.tree) { + unload_one_branch(); + load_branch(b); } - if (!store_object(OBJ_TREE, buf, c - buf, &t->last_tree, e->sha1)) - free(buf); -} + /* author_committer_msg */ + yread(0, &acmsglen, 4); + body = xmalloc(acmsglen + max_hdr_len); + c = body + max_hdr_len; + yread(0, c, acmsglen); -static void new_branch() -{ - struct branch *nb = xcalloc(1, sizeof(struct branch)); - const char *source_name; - - nb->name = strdup(read_required_string()); - source_name = read_string(); - if (source_name) { - struct branch *sb = lookup_branch(source_name); - nb->tree.tree = deep_copy_tree(sb->tree.tree); - memcpy(nb->tree.sha1, sb->tree.sha1, sizeof(sb->tree.sha1)); - memcpy(nb->sha1, sb->sha1, sizeof(sb->sha1)); - } else { - nb->tree.tree = xcalloc(1, sizeof(struct tree)); - nb->tree.tree->entries = xmalloc(8*sizeof(struct tree_entry*)); + /* file_change* */ + for (;;) { + unsigned char cmd; + yread(0, &cmd, 1); + if (cmd == '0') + break; + else if (cmd == 'M') + file_change_m(b); + else if (cmd == 'D') + file_change_d(b); + else + die("Unsupported file_change: %c", cmd); } - nb->next_branch = branches; - branches = nb; - branch_count++; + + if (memcmp(b->sha1, null_sha1, 20)) { + sprintf(c - 48, "parent %s", sha1_to_hex(b->sha1)); + *(c - 1) = '\n'; + c -= 48; + } + store_tree(&b->branch_tree); + sprintf(c - 46, "tree %s", sha1_to_hex(b->branch_tree.sha1)); + *(c - 1) = '\n'; + c -= 46; + + store_object(OBJ_COMMIT, + c, (body + max_hdr_len + acmsglen) - c, + NULL, b->sha1); + free(body); + b->last_commit = object_count_by_type[OBJ_COMMIT]; } -static void set_branch() +static void cmd_new_branch() { - current_branch = lookup_branch(read_required_string()); -} + struct branch *b = new_branch(read_path()); + const char *base = read_path(); + struct branch *s = lookup_branch(base); -static void commit() -{ - store_tree(¤t_branch->tree); + if (!strcmp(b->name, base)) + die("Can't create a branch from itself: %s", base); + else if (s) { + memcpy(b->sha1, s->sha1, 20); + memcpy(b->branch_tree.sha1, s->branch_tree.sha1, 20); + } + else if (!get_sha1(base, b->sha1)) { + if (!memcmp(b->sha1, null_sha1, 20)) + memcpy(b->branch_tree.sha1, null_sha1, 20); + else { + unsigned long size; + char *buf; + + buf = read_object_with_reference(b->sha1, + type_names[OBJ_COMMIT], &size, b->sha1); + if (!buf || size < 46) + die("Not a valid commit: %s", base); + if (memcmp("tree ", buf, 5) + || get_sha1_hex(buf + 5, b->branch_tree.sha1)) + die("The commit %s is corrupt", sha1_to_hex(b->sha1)); + free(buf); + } + } else + die("Not a SHA1 or branch: %s", base); } int main(int argc, const char **argv) @@ -515,6 +1057,9 @@ int main(int argc, const char **argv) char *idx_name; struct stat sb; + setup_ident(); + git_config(git_default_config); + pack_name = xmalloc(strlen(base_name) + 6); sprintf(pack_name, "%s.pack", base_name); idx_name = xmalloc(strlen(base_name) + 5); @@ -525,17 +1070,21 @@ int main(int argc, const char **argv) die("Can't create %s: %s", pack_name, strerror(errno)); alloc_objects(est_obj_cnt); + + atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*)); + branch_table = xcalloc(branch_table_sz, sizeof(struct branch*)); + avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*)); + init_pack_header(); for (;;) { unsigned long cmd; - if (yread(0, &cmd, 4) != 4) + if (optional_read(0, &cmd, 4)) break; - switch (cmd) { - case 'blob': new_blob(); break; - case 'newb': new_branch(); break; - case 'setb': set_branch(); break; - case 'comt': commit(); break; + switch (ntohl(cmd)) { + case 'blob': cmd_new_blob(); break; + case 'comt': cmd_new_commit(); break; + case 'brch': cmd_new_branch(); break; default: die("Invalid command %lu", cmd); } @@ -543,6 +1092,7 @@ int main(int argc, const char **argv) fixup_header_footer(); close(pack_fd); write_index(idx_name); + dump_branches(); fprintf(stderr, "%s statistics:\n", argv[0]); fprintf(stderr, "---------------------------------------------------\n"); @@ -553,6 +1103,8 @@ int main(int argc, const char **argv) fprintf(stderr, " commits: %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT]); fprintf(stderr, " tags : %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG]); fprintf(stderr, "Total branches: %10lu\n", branch_count); + fprintf(stderr, "Total atoms: %10u\n", atom_cnt); + fprintf(stderr, "Memory pools: %10lu MiB\n", total_allocd/(1024*1024)); fprintf(stderr, "---------------------------------------------------\n"); stat(pack_name, &sb); From 7111feede9c5905199ba48645fadc369faca5711 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 14 Aug 2006 02:50:18 -0400 Subject: [PATCH 166/548] Implement blob ID validation in fast-import. When accepting revision SHA1 IDs from the frontend verify the SHA1 actually refers to a blob and is known to exist. Its an error to use a SHA1 in a tree if the blob doesn't exist as this would cause git-fsck-objects to report a missing blob should the pack get closed without the blob being appended into it or a subsequent pack. So right now we'll just ask that the frontend "pre-declare" any blobs it wants to use in a tree before it can use them. Signed-off-by: Shawn O. Pearce --- fast-import.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/fast-import.c b/fast-import.c index 4605b7469b..95b84f57e5 100644 --- a/fast-import.c +++ b/fast-import.c @@ -70,6 +70,7 @@ Format of STDIN stream: struct object_entry { struct object_entry *next; + enum object_type type; unsigned long offset; unsigned char sha1[20]; }; @@ -528,6 +529,7 @@ static int store_object( duplicate_count_by_type[type]++; return 1; } + e->type = type; e->offset = pack_offset; object_count++; object_count_by_type[type]++; @@ -713,7 +715,7 @@ static int tree_content_set( } if (!S_ISDIR(e->mode)) { e->tree = new_tree_content(8); - e->mode = 040000; + e->mode = S_IFDIR; } if (!e->tree) load_tree(e); @@ -732,7 +734,7 @@ static int tree_content_set( t->entries[t->entry_count++] = e; if (slash1) { e->tree = new_tree_content(8); - e->mode = 040000; + e->mode = S_IFDIR; tree_content_set(e, slash1 + 1, sha1, mode); } else { e->tree = NULL; @@ -948,16 +950,28 @@ static void load_branch(struct branch *b) static void file_change_m(struct branch *b) { const char *path = read_path(); + struct object_entry *oe; char hexsha1[41]; unsigned char sha1[20]; + char type[20]; yread(0, hexsha1, 40); hexsha1[40] = 0; if (get_sha1_hex(hexsha1, sha1)) die("Invalid sha1 %s for %s", hexsha1, path); + oe = find_object(sha1); + if (oe) { + if (oe->type != OBJ_BLOB) + die("%s is a %s not a blob (for %s)", hexsha1, type_names[oe->type], path); + } else { + if (sha1_object_info(sha1, type, NULL)) + die("No blob %s for %s", hexsha1, path); + if (strcmp(blob_type, type)) + die("%s is a %s not a blob (for %s)", hexsha1, type, path); + } - tree_content_set(&b->branch_tree, path, sha1, 0100644); + tree_content_set(&b->branch_tree, path, sha1, S_IFREG | 0644); } static void file_change_d(struct branch *b) @@ -986,6 +1000,10 @@ static void cmd_new_commit() c = body + max_hdr_len; yread(0, c, acmsglen); + /* oddly enough this is all that fsck-objects cares about */ + if (memcmp(c, "author ", 7)) + die("Invalid commit format on branch %s", name); + /* file_change* */ for (;;) { unsigned char cmd; @@ -1104,7 +1122,9 @@ int main(int argc, const char **argv) fprintf(stderr, " tags : %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG]); fprintf(stderr, "Total branches: %10lu\n", branch_count); fprintf(stderr, "Total atoms: %10u\n", atom_cnt); - fprintf(stderr, "Memory pools: %10lu MiB\n", total_allocd/(1024*1024)); + fprintf(stderr, "Memory total: %10lu KiB\n", (total_allocd + alloc_count*sizeof(struct object_entry))/1024); + fprintf(stderr, " pools: %10lu KiB\n", total_allocd/1024); + fprintf(stderr, " objects: %10lu KiB\n", (alloc_count*sizeof(struct object_entry))/1024); fprintf(stderr, "---------------------------------------------------\n"); stat(pack_name, &sb); From c44cdc7eef212ec09901eb2e0996476e0468ed88 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 14 Aug 2006 20:16:28 -0400 Subject: [PATCH 167/548] Converted fast-import to a text based protocol. Frontend clients can now send a text stream to fast-import rather than a binary stream. This should facilitate developing frontend software as the data stream is easier to view, manipulate and debug my hand and Mark-I eyeball. Signed-off-by: Shawn O. Pearce --- fast-import.c | 470 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 318 insertions(+), 152 deletions(-) diff --git a/fast-import.c b/fast-import.c index 95b84f57e5..2953e80cde 100644 --- a/fast-import.c +++ b/fast-import.c @@ -4,57 +4,93 @@ Format of STDIN stream: stream ::= cmd*; cmd ::= new_blob - | new_commit | new_branch + | new_commit | new_tag ; - new_blob ::= 'blob' blob_data; + new_blob ::= 'blob' lf + mark? + file_content; + file_content ::= data; - new_commit ::= 'comt' ref_name author_committer_msg + new_branch ::= 'branch' sp ref_str lf + ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? + lf; + + new_commit ::= 'commit' sp ref_str lf + mark? + ('author' sp name '<' email '>' ts tz lf)? + 'committer' sp name '<' email '>' ts tz lf + commit_msg file_change* - '0'; + lf; + commit_msg ::= data; - new_branch ::= 'brch' dst_ref_name src_ref_name; - dst_ref_name ::= ref_name; - src_ref_name ::= ref_name | sha1_exp; - - new_tag ::= 'tagg' ref_name tag_name tagger_msg; - - file_change ::= 'M' path_name hexsha1 - | 'D' path_name + file_change ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf + | 'D' sp path_str lf ; + mode ::= '644' | '755'; - author_committer_msg ::= len32 - 'author' sp name '<' email '>' ts tz lf - 'committer' sp name '<' email '>' ts tz lf - lf - binary_data; + new_tag ::= 'tag' sp tag_str lf + 'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf + 'tagger' sp name '<' email '>' ts tz lf + tag_msg; + tag_msg ::= data; - tagger_msg ::= len32 - 'tagger' sp name '<' email '>' ts tz lf - lf - binary_data; + # note: the first idnum in a stream should be 1 and subsequent + # idnums should not have gaps between values as this will cause + # the stream parser to reserve space for the gapped values. An + # idnum can be updated in the future to a new object by issuing + # a new mark directive with the old idnum. + # + mark ::= 'mark' sp idnum lf; - blob_data ::= len32 binary_data; # max len is 2^32-1 - path_name ::= len32 path; # max len is PATH_MAX-1 - ref_name ::= len32 ref; # max len is PATH_MAX-1 - tag_name ::= len32 tag; # max len is PATH_MAX-1 - sha1_exp ::= len32 sha1exp; # max len is PATH_MAX-1 + # note: declen indicates the length of binary_data in bytes. + # declen does not include the lf preceeding or trailing the + # binary data. + # + data ::= 'data' sp declen lf + binary_data + lf; - len32 ::= # unsigned 32 bit value, native format; + # note: quoted strings are C-style quoting supporting \c for + # common escapes of 'c' (e..g \n, \t, \\, \") or \nnn where nnn + # is the signed byte value in octal. Note that the only + # characters which must actually be escaped to protect the + # stream formatting is: \, " and LF. Otherwise these values + # are UTF8. + # + ref_str ::= ref | '"' quoted(ref) '"' ; + sha1exp_str ::= sha1exp | '"' quoted(sha1exp) '"' ; + tag_str ::= tag | '"' quoted(tag) '"' ; + path_str ::= path | '"' quoted(path) '"' ; + + declen ::= # unsigned 32 bit value, ascii base10 notation; binary_data ::= # file content, not interpreted; + sp ::= # ASCII space character; lf ::= # ASCII newline (LF) character; - path ::= # GIT style file path, e.g. "a/b/c"; - ref ::= # GIT ref name, e.g. "refs/heads/MOZ_GECKO_EXPERIMENT"; - tag ::= # GIT tag name, e.g. "FIREFOX_1_5"; + + # note: a colon (':') must precede the numerical value assigned to + # an idnum. This is to distinguish it from a ref or tag name as + # GIT does not permit ':' in ref or tag strings. + # + idnum ::= ':' declen; + path ::= # GIT style file path, e.g. "a/b/c"; + ref ::= # GIT ref name, e.g. "refs/heads/MOZ_GECKO_EXPERIMENT"; + tag ::= # GIT tag name, e.g. "FIREFOX_1_5"; sha1exp ::= # Any valid GIT SHA1 expression; hexsha1 ::= # SHA1 in hexadecimal format; - name ::= # valid GIT author/committer name; + + # note: name and email are UTF8 strings, however name must not + # contain '<' or lf and email must not contain any of the + # following: '<', '>', lf. + # + name ::= # valid GIT author/committer name; email ::= # valid GIT author/committer email; - ts ::= # time since the epoch in seconds, ascii decimal; - tz ::= # GIT style timezone; + ts ::= # time since the epoch in seconds, ascii base10 notation; + tz ::= # GIT style timezone; */ #include "builtin.h" @@ -66,6 +102,8 @@ Format of STDIN stream: #include "pack.h" #include "refs.h" #include "csum-file.h" +#include "strbuf.h" +#include "quote.h" struct object_entry { @@ -153,7 +191,7 @@ static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool); static size_t total_allocd; static struct mem_pool *mem_pool; -/* atom management */ +/* Atom management */ static unsigned int atom_table_sz = 4451; static unsigned int atom_cnt; static struct atom_str **atom_table; @@ -184,6 +222,10 @@ static unsigned int branch_table_sz = 1039; static struct branch **branch_table; static struct branch *active_branches; +/* Input stream parsing */ +static struct strbuf command_buf; +static unsigned long command_mark; + static void alloc_objects(int cnt) { @@ -330,6 +372,8 @@ static struct branch* new_branch(const char *name) if (b) die("Invalid attempt to create duplicate branch: %s", name); + if (check_ref_format(name)) + die("Branch name doesn't conform to GIT standards: %s", name); b = pool_calloc(1, sizeof(struct branch)); b->name = pool_strdup(name); @@ -433,22 +477,6 @@ static void yread(int fd, void *buffer, size_t length) } } -static int optional_read(int fd, void *buffer, size_t length) -{ - ssize_t ret = 0; - while (ret < length) { - ssize_t size = xread(fd, (char *) buffer + ret, length - ret); - if (!size && !ret) - return 1; - if (!size) - die("Read from descriptor %i: end of stream", fd); - if (size < 0) - die("Read from descriptor %i: %s", fd, strerror(errno)); - ret += size; - } - return 0; -} - static void ywrite(int fd, void *buffer, size_t length) { ssize_t ret = 0; @@ -462,24 +490,9 @@ static void ywrite(int fd, void *buffer, size_t length) } } -static const char* read_path() -{ - static char sn[PATH_MAX]; - unsigned long slen; - - yread(0, &slen, 4); - if (!slen) - die("Expected string command parameter, didn't find one"); - if (slen > (PATH_MAX - 1)) - die("Can't handle excessive string length %lu", slen); - yread(0, sn, slen); - sn[slen] = 0; - return sn; -} - -static unsigned long encode_header( +static size_t encode_header( enum object_type type, - unsigned long size, + size_t size, unsigned char *hdr) { int n = 1; @@ -503,7 +516,7 @@ static unsigned long encode_header( static int store_object( enum object_type type, void *dat, - unsigned long datlen, + size_t datlen, struct last_object *last, unsigned char *sha1out) { @@ -896,15 +909,57 @@ static void dump_branches() } } +static void read_next_command() +{ + read_line(&command_buf, stdin, '\n'); +} + +static void cmd_mark() +{ + if (!strncmp("mark :", command_buf.buf, 6)) { + command_mark = strtoul(command_buf.buf + 6, NULL, 10); + read_next_command(); + } + else + command_mark = 0; +} + +static void* cmd_data (size_t *size) +{ + size_t n = 0; + void *buffer; + size_t length; + + if (strncmp("data ", command_buf.buf, 5)) + die("Expected 'data n' command, found: %s", command_buf.buf); + + length = strtoul(command_buf.buf + 5, NULL, 10); + buffer = xmalloc(length); + + while (n < length) { + size_t s = fread((char*)buffer + n, 1, length - n, stdin); + if (!s && feof(stdin)) + die("EOF in data (%lu bytes remaining)", length - n); + n += s; + } + + if (fgetc(stdin) != '\n') + die("An lf did not trail the binary data as expected."); + + *size = length; + return buffer; +} + static void cmd_new_blob() { - unsigned long datlen; - unsigned char sha1[20]; + size_t datlen; void *dat; + unsigned char sha1[20]; + + read_next_command(); + cmd_mark(); + dat = cmd_data(&datlen); - yread(0, &datlen, 4); - dat = xmalloc(datlen); - yread(0, dat, datlen); if (store_object(OBJ_BLOB, dat, datlen, &last_blob, sha1)) free(dat); } @@ -949,122 +1004,231 @@ static void load_branch(struct branch *b) static void file_change_m(struct branch *b) { - const char *path = read_path(); + const char *p = command_buf.buf + 2; + char *p_uq; + const char *endp; struct object_entry *oe; - char hexsha1[41]; unsigned char sha1[20]; + unsigned int mode; char type[20]; - yread(0, hexsha1, 40); - hexsha1[40] = 0; + p = get_mode(p, &mode); + if (!p) + die("Corrupt mode: %s", command_buf.buf); + switch (mode) { + case S_IFREG | 0644: + case S_IFREG | 0755: + case 0644: + case 0755: + /* ok */ + break; + default: + die("Corrupt mode: %s", command_buf.buf); + } + + if (get_sha1_hex(p, sha1)) + die("Invalid SHA1: %s", command_buf.buf); + p += 40; + if (*p++ != ' ') + die("Missing space after SHA1: %s", command_buf.buf); + + p_uq = unquote_c_style(p, &endp); + if (p_uq) { + if (*endp) + die("Garbage after path in: %s", command_buf.buf); + p = p_uq; + } - if (get_sha1_hex(hexsha1, sha1)) - die("Invalid sha1 %s for %s", hexsha1, path); oe = find_object(sha1); if (oe) { if (oe->type != OBJ_BLOB) - die("%s is a %s not a blob (for %s)", hexsha1, type_names[oe->type], path); + die("Not a blob (actually a %s): %s", + command_buf.buf, type_names[oe->type]); } else { if (sha1_object_info(sha1, type, NULL)) - die("No blob %s for %s", hexsha1, path); + die("Blob not found: %s", command_buf.buf); if (strcmp(blob_type, type)) - die("%s is a %s not a blob (for %s)", hexsha1, type, path); + die("Not a blob (actually a %s): %s", + command_buf.buf, type); } - tree_content_set(&b->branch_tree, path, sha1, S_IFREG | 0644); + tree_content_set(&b->branch_tree, p, sha1, S_IFREG | mode); + + if (p_uq) + free(p_uq); } static void file_change_d(struct branch *b) { - tree_content_remove(&b->branch_tree, read_path()); + const char *p = command_buf.buf + 2; + char *p_uq; + const char *endp; + + p_uq = unquote_c_style(p, &endp); + if (p_uq) { + if (*endp) + die("Garbage after path in: %s", command_buf.buf); + p = p_uq; + } + tree_content_remove(&b->branch_tree, p); + if (p_uq) + free(p_uq); } static void cmd_new_commit() { - static const unsigned int max_hdr_len = 94; - const char *name = read_path(); - struct branch *b = lookup_branch(name); - unsigned int acmsglen; - char *body, *c; + struct branch *b; + void *msg; + size_t msglen; + char *str_uq; + const char *endp; + char *sp; + char *author = NULL; + char *committer = NULL; + char *body; + /* Obtain the branch name from the rest of our command */ + sp = strchr(command_buf.buf, ' ') + 1; + str_uq = unquote_c_style(sp, &endp); + if (str_uq) { + if (*endp) + die("Garbage after ref in: %s", command_buf.buf); + sp = str_uq; + } + b = lookup_branch(sp); if (!b) - die("Branch not declared: %s", name); + die("Branch not declared: %s", sp); + if (str_uq) + free(str_uq); + + read_next_command(); + cmd_mark(); + if (!strncmp("author ", command_buf.buf, 7)) { + author = strdup(command_buf.buf); + read_next_command(); + } + if (!strncmp("committer ", command_buf.buf, 10)) { + committer = strdup(command_buf.buf); + read_next_command(); + } + if (!committer) + die("Expected committer but didn't get one"); + msg = cmd_data(&msglen); + + /* ensure the branch is active/loaded */ if (!b->branch_tree.tree) { unload_one_branch(); load_branch(b); } - /* author_committer_msg */ - yread(0, &acmsglen, 4); - body = xmalloc(acmsglen + max_hdr_len); - c = body + max_hdr_len; - yread(0, c, acmsglen); - - /* oddly enough this is all that fsck-objects cares about */ - if (memcmp(c, "author ", 7)) - die("Invalid commit format on branch %s", name); - /* file_change* */ for (;;) { - unsigned char cmd; - yread(0, &cmd, 1); - if (cmd == '0') + read_next_command(); + if (1 == command_buf.len) break; - else if (cmd == 'M') + else if (!strncmp("M ", command_buf.buf, 2)) file_change_m(b); - else if (cmd == 'D') + else if (!strncmp("D ", command_buf.buf, 2)) file_change_d(b); else - die("Unsupported file_change: %c", cmd); + die("Unsupported file_change: %s", command_buf.buf); } - if (memcmp(b->sha1, null_sha1, 20)) { - sprintf(c - 48, "parent %s", sha1_to_hex(b->sha1)); - *(c - 1) = '\n'; - c -= 48; - } + /* build the tree and the commit */ store_tree(&b->branch_tree); - sprintf(c - 46, "tree %s", sha1_to_hex(b->branch_tree.sha1)); - *(c - 1) = '\n'; - c -= 46; + body = xmalloc(97 + msglen + + (author + ? strlen(author) + strlen(committer) + : 2 * strlen(committer))); + sp = body; + sp += sprintf(sp, "tree %s\n", sha1_to_hex(b->branch_tree.sha1)); + if (memcmp(b->sha1, null_sha1, 20)) + sp += sprintf(sp, "parent %s\n", sha1_to_hex(b->sha1)); + if (author) + sp += sprintf(sp, "%s\n", author); + else + sp += sprintf(sp, "author %s\n", committer + 10); + sp += sprintf(sp, "%s\n\n", committer); + memcpy(sp, msg, msglen); + sp += msglen; + if (author) + free(author); + free(committer); + free(msg); - store_object(OBJ_COMMIT, - c, (body + max_hdr_len + acmsglen) - c, - NULL, b->sha1); + store_object(OBJ_COMMIT, body, sp - body, NULL, b->sha1); free(body); b->last_commit = object_count_by_type[OBJ_COMMIT]; } static void cmd_new_branch() { - struct branch *b = new_branch(read_path()); - const char *base = read_path(); - struct branch *s = lookup_branch(base); + struct branch *b; + char *str_uq; + const char *endp; + char *sp; - if (!strcmp(b->name, base)) - die("Can't create a branch from itself: %s", base); - else if (s) { - memcpy(b->sha1, s->sha1, 20); - memcpy(b->branch_tree.sha1, s->branch_tree.sha1, 20); + /* Obtain the new branch name from the rest of our command */ + sp = strchr(command_buf.buf, ' ') + 1; + str_uq = unquote_c_style(sp, &endp); + if (str_uq) { + if (*endp) + die("Garbage after ref in: %s", command_buf.buf); + sp = str_uq; } - else if (!get_sha1(base, b->sha1)) { - if (!memcmp(b->sha1, null_sha1, 20)) - memcpy(b->branch_tree.sha1, null_sha1, 20); - else { - unsigned long size; - char *buf; + b = new_branch(sp); + if (str_uq) + free(str_uq); + read_next_command(); - buf = read_object_with_reference(b->sha1, - type_names[OBJ_COMMIT], &size, b->sha1); - if (!buf || size < 46) - die("Not a valid commit: %s", base); - if (memcmp("tree ", buf, 5) - || get_sha1_hex(buf + 5, b->branch_tree.sha1)) - die("The commit %s is corrupt", sha1_to_hex(b->sha1)); - free(buf); + /* from ... */ + if (!strncmp("from ", command_buf.buf, 5)) { + const char *from; + struct branch *s; + + from = strchr(command_buf.buf, ' ') + 1; + str_uq = unquote_c_style(from, &endp); + if (str_uq) { + if (*endp) + die("Garbage after string in: %s", command_buf.buf); + from = str_uq; } - } else - die("Not a SHA1 or branch: %s", base); + + s = lookup_branch(from); + if (b == s) + die("Can't create a branch from itself: %s", b->name); + else if (s) { + memcpy(b->sha1, s->sha1, 20); + memcpy(b->branch_tree.sha1, s->branch_tree.sha1, 20); + } else if (!get_sha1(from, b->sha1)) { + if (!memcmp(b->sha1, null_sha1, 20)) + memcpy(b->branch_tree.sha1, null_sha1, 20); + else { + unsigned long size; + char *buf; + + buf = read_object_with_reference(b->sha1, + type_names[OBJ_COMMIT], &size, b->sha1); + if (!buf || size < 46) + die("Not a valid commit: %s", from); + if (memcmp("tree ", buf, 5) + || get_sha1_hex(buf + 5, b->branch_tree.sha1)) + die("The commit %s is corrupt", sha1_to_hex(b->sha1)); + free(buf); + } + } else + die("Invalid ref name or SHA1 expression: %s", from); + + if (str_uq) + free(str_uq); + read_next_command(); + } else { + memcpy(b->sha1, null_sha1, 20); + memcpy(b->branch_tree.sha1, null_sha1, 20); + } + + if (command_buf.eof || command_buf.len > 1) + die("An lf did not terminate the branch command as expected."); } int main(int argc, const char **argv) @@ -1087,26 +1251,28 @@ int main(int argc, const char **argv) if (pack_fd < 0) die("Can't create %s: %s", pack_name, strerror(errno)); + init_pack_header(); alloc_objects(est_obj_cnt); + strbuf_init(&command_buf); atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*)); branch_table = xcalloc(branch_table_sz, sizeof(struct branch*)); avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*)); - init_pack_header(); for (;;) { - unsigned long cmd; - if (optional_read(0, &cmd, 4)) + read_next_command(); + if (command_buf.eof) break; - - switch (ntohl(cmd)) { - case 'blob': cmd_new_blob(); break; - case 'comt': cmd_new_commit(); break; - case 'brch': cmd_new_branch(); break; - default: - die("Invalid command %lu", cmd); - } + else if (!strcmp("blob", command_buf.buf)) + cmd_new_blob(); + else if (!strncmp("branch ", command_buf.buf, 7)) + cmd_new_branch(); + else if (!strncmp("commit ", command_buf.buf, 7)) + cmd_new_commit(); + else + die("Unsupported command: %s", command_buf.buf); } + fixup_header_footer(); close(pack_fd); write_index(idx_name); From c90be46abdbd102ab8e9af0303d33976d552ae58 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 16 Aug 2006 01:57:57 -0400 Subject: [PATCH 168/548] Changed fast-import's pack header creation to use pack.h Signed-off-by: Shawn O. Pearce Signed-off-by: Shawn O. Pearce --- fast-import.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/fast-import.c b/fast-import.c index 2953e80cde..d5651693ba 100644 --- a/fast-import.c +++ b/fast-import.c @@ -800,15 +800,14 @@ del_entry: static void init_pack_header() { - const char* magic = "PACK"; - unsigned long version = 3; - unsigned long zero = 0; + struct pack_header hdr; - version = htonl(version); - ywrite(pack_fd, (char*)magic, 4); - ywrite(pack_fd, &version, 4); - ywrite(pack_fd, &zero, 4); - pack_offset = 4 * 3; + hdr.hdr_signature = htonl(PACK_SIGNATURE); + hdr.hdr_version = htonl(2); + hdr.hdr_entries = 0; + + ywrite(pack_fd, &hdr, sizeof(hdr)); + pack_offset = sizeof(hdr); } static void fixup_header_footer() From ace4a9d1ae5efd056c5e57cc76aacee3057a73f7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 21 Aug 2006 03:29:13 -0400 Subject: [PATCH 169/548] Allow symlink blobs in trees during fast-import. If a frontend is smart enough to import a symlink then we should let them do so. We'll assume that they were smart enough to first generate a blob to hold the link target, as that's how symlinks get represented in GIT. Signed-off-by: Shawn O. Pearce --- fast-import.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fast-import.c b/fast-import.c index d5651693ba..7d1ee1dad9 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1017,6 +1017,7 @@ static void file_change_m(struct branch *b) switch (mode) { case S_IFREG | 0644: case S_IFREG | 0755: + case S_IFLNK: case 0644: case 0755: /* ok */ From afde8dd96dbb81688d7cb22330e4fffcfc7def21 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 23 Aug 2006 01:33:47 -0400 Subject: [PATCH 170/548] Fixed segfault in fast-import after growing a tree. Growing a tree caused all subtrees to be deallocated and put back into the free list yet those subtree's contents were still actively in use. Consequently they were doled out again and got stomped on elsewhere. Releasing a tree is now performed in two parts, either releasing only the content array or releasing the content array and recursively releasing the subtree(s). Signed-off-by: Shawn O. Pearce --- fast-import.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/fast-import.c b/fast-import.c index 7d1ee1dad9..4c2431f0b0 100644 --- a/fast-import.c +++ b/fast-import.c @@ -420,11 +420,16 @@ static void release_tree_content(struct tree_content *t) { struct avail_tree_content *f = (struct avail_tree_content*)t; unsigned int hc = hc_entries(f->entry_capacity); + f->next_avail = avail_tree_table[hc]; + avail_tree_table[hc] = f; +} + +static void release_tree_content_recursive(struct tree_content *t) +{ unsigned int i; for (i = 0; i < t->entry_count; i++) release_tree_entry(t->entries[i]); - f->next_avail = avail_tree_table[hc]; - avail_tree_table[hc] = f; + release_tree_content(t); } static struct tree_content* grow_tree_content( @@ -459,7 +464,7 @@ static struct tree_entry* new_tree_entry() static void release_tree_entry(struct tree_entry *e) { if (e->tree) - release_tree_content(e->tree); + release_tree_content_recursive(e->tree); *((void**)e) = avail_tree_entry; avail_tree_entry = e; } @@ -720,7 +725,7 @@ static int tree_content_set( e->mode = mode; memcpy(e->sha1, sha1, 20); if (e->tree) { - release_tree_content(e->tree); + release_tree_content_recursive(e->tree); e->tree = NULL; } memcpy(root->sha1, null_sha1, 20); @@ -986,7 +991,7 @@ static void unload_one_branch() } e->active_next_branch = NULL; if (e->branch_tree.tree) { - release_tree_content(e->branch_tree.tree); + release_tree_content_recursive(e->branch_tree.tree); e->branch_tree.tree = NULL; } cur_active_branches--; From d5c57b284e847a56cc1d98b783be95ba94285afe Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 23 Aug 2006 02:00:31 -0400 Subject: [PATCH 171/548] Converted fast-import to accept standard command line parameters. The following command line options are now accepted before the pack name: --objects=n # replaces the object count after the pack name --depth=n # delta chain depth to use (default is 10) --active-branches=n # maximum number of branches to keep in memory Signed-off-by: Shawn O. Pearce --- fast-import.c | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/fast-import.c b/fast-import.c index 4c2431f0b0..8598493651 100644 --- a/fast-import.c +++ b/fast-import.c @@ -178,7 +178,7 @@ struct branch /* Stats and misc. counters */ -static int max_depth = 10; +static unsigned long max_depth = 10; static unsigned long alloc_count; static unsigned long branch_count; static unsigned long object_count; @@ -216,9 +216,9 @@ static unsigned int avail_tree_table_sz = 100; static struct avail_tree_content **avail_tree_table; /* Branch data */ -static unsigned int max_active_branches = 5; -static unsigned int cur_active_branches; -static unsigned int branch_table_sz = 1039; +static unsigned long max_active_branches = 5; +static unsigned long cur_active_branches; +static unsigned long branch_table_sz = 1039; static struct branch **branch_table; static struct branch *active_branches; @@ -1236,10 +1236,14 @@ static void cmd_new_branch() die("An lf did not terminate the branch command as expected."); } +static const char fast_import_usage[] = +"git-fast-import [--objects=n] [--depth=n] [--active-branches=n] temp.pack"; + int main(int argc, const char **argv) { - const char *base_name = argv[1]; - int est_obj_cnt = atoi(argv[2]); + const char *base_name; + int i; + unsigned long est_obj_cnt = 1000; char *pack_name; char *idx_name; struct stat sb; @@ -1247,6 +1251,24 @@ int main(int argc, const char **argv) setup_ident(); git_config(git_default_config); + for (i = 1; i < argc; i++) { + const char *a = argv[i]; + + if (*a != '-' || !strcmp(a, "--")) + break; + else if (!strncmp(a, "--objects=", 10)) + est_obj_cnt = strtoul(a + 10, NULL, 0); + else if (!strncmp(a, "--depth=", 8)) + max_depth = strtoul(a + 8, NULL, 0); + else if (!strncmp(a, "--active-branches=", 18)) + max_active_branches = strtoul(a + 18, NULL, 0); + else + die("unknown option %s", a); + } + if ((i+1) != argc) + usage(fast_import_usage); + base_name = argv[i]; + pack_name = xmalloc(strlen(base_name) + 6); sprintf(pack_name, "%s.pack", base_name); idx_name = xmalloc(strlen(base_name) + 5); From d83971688ba42e4cd37908f4d776801a997ca421 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 23 Aug 2006 04:17:45 -0400 Subject: [PATCH 172/548] Added mark store/find to fast-import. Marks are now saved when the mark directive gets used by the frontend and may be used in place of a SHA1 expression to locate a previous SHA1 which fast-import may have generated. This is particularly useful with commits where the frontend does not (easily) have the ability to compute the SHA1 for an arbitrary commit but needs it to generate a branch or tag from that commit. Signed-off-by: Shawn O. Pearce --- fast-import.c | 104 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 87 insertions(+), 17 deletions(-) diff --git a/fast-import.c b/fast-import.c index 8598493651..6e2f106a1a 100644 --- a/fast-import.c +++ b/fast-import.c @@ -121,6 +121,15 @@ struct object_entry_pool struct object_entry entries[FLEX_ARRAY]; /* more */ }; +struct mark_set +{ + int shift; + union { + struct object_entry *marked[1024]; + struct mark_set *sets[1024]; + } data; +}; + struct last_object { void *data; @@ -183,6 +192,7 @@ static unsigned long alloc_count; static unsigned long branch_count; static unsigned long object_count; static unsigned long duplicate_count; +static unsigned long marks_set_count; static unsigned long object_count_by_type[9]; static unsigned long duplicate_count_by_type[9]; @@ -205,6 +215,7 @@ static unsigned char pack_sha1[20]; static unsigned int object_entry_alloc = 1000; static struct object_entry_pool *blocks; static struct object_entry *object_table[1 << 16]; +static struct mark_set *marks; /* Our last blob */ static struct last_object last_blob; @@ -224,7 +235,7 @@ static struct branch *active_branches; /* Input stream parsing */ static struct strbuf command_buf; -static unsigned long command_mark; +static unsigned long next_mark; static void alloc_objects(int cnt) @@ -335,6 +346,48 @@ static char* pool_strdup(const char *s) return r; } +static void insert_mark(unsigned long idnum, struct object_entry *oe) +{ + struct mark_set *s = marks; + while ((idnum >> s->shift) >= 1024) { + s = pool_calloc(1, sizeof(struct mark_set)); + s->shift = marks->shift + 10; + s->data.sets[0] = marks; + marks = s; + } + while (s->shift) { + unsigned long i = idnum >> s->shift; + idnum -= i << s->shift; + if (!s->data.sets[i]) { + s->data.sets[i] = pool_calloc(1, sizeof(struct mark_set)); + s->data.sets[i]->shift = s->shift - 10; + } + s = s->data.sets[i]; + } + if (!s->data.marked[idnum]) + marks_set_count++; + s->data.marked[idnum] = oe; +} + +static struct object_entry* find_mark(unsigned long idnum) +{ + unsigned long orig_idnum = idnum; + struct mark_set *s = marks; + struct object_entry *oe = NULL; + if ((idnum >> s->shift) < 1024) { + while (s && s->shift) { + unsigned long i = idnum >> s->shift; + idnum -= i << s->shift; + s = s->data.sets[i]; + } + if (s) + oe = s->data.marked[idnum]; + } + if (!oe) + die("mark :%lu not declared", orig_idnum); + return oe; +} + static struct atom_str* to_atom(const char *s, size_t len) { unsigned int hc = hc_str(s, len) % atom_table_sz; @@ -523,7 +576,8 @@ static int store_object( void *dat, size_t datlen, struct last_object *last, - unsigned char *sha1out) + unsigned char *sha1out, + unsigned long mark) { void *out, *delta; struct object_entry *e; @@ -542,6 +596,8 @@ static int store_object( memcpy(sha1out, sha1, sizeof(sha1)); e = insert_object(sha1); + if (mark) + insert_mark(mark, e); if (e->offset) { duplicate_count++; duplicate_count_by_type[type]++; @@ -695,7 +751,7 @@ static void store_tree(struct tree_entry *root) memcpy(c, e->sha1, 20); c += 20; } - store_object(OBJ_TREE, buf, c - buf, NULL, root->sha1); + store_object(OBJ_TREE, buf, c - buf, NULL, root->sha1, 0); free(buf); } @@ -921,11 +977,11 @@ static void read_next_command() static void cmd_mark() { if (!strncmp("mark :", command_buf.buf, 6)) { - command_mark = strtoul(command_buf.buf + 6, NULL, 10); + next_mark = strtoul(command_buf.buf + 6, NULL, 10); read_next_command(); } else - command_mark = 0; + next_mark = 0; } static void* cmd_data (size_t *size) @@ -956,16 +1012,15 @@ static void* cmd_data (size_t *size) static void cmd_new_blob() { - size_t datlen; - void *dat; - unsigned char sha1[20]; + size_t l; + void *d; read_next_command(); cmd_mark(); - dat = cmd_data(&datlen); + d = cmd_data(&l); - if (store_object(OBJ_BLOB, dat, datlen, &last_blob, sha1)) - free(dat); + if (store_object(OBJ_BLOB, d, l, &last_blob, NULL, next_mark)) + free(d); } static void unload_one_branch() @@ -1031,9 +1086,16 @@ static void file_change_m(struct branch *b) die("Corrupt mode: %s", command_buf.buf); } - if (get_sha1_hex(p, sha1)) - die("Invalid SHA1: %s", command_buf.buf); - p += 40; + if (*p == ':') { + char *x; + oe = find_mark(strtoul(p + 1, &x, 10)); + p = x; + } else { + if (get_sha1_hex(p, sha1)) + die("Invalid SHA1: %s", command_buf.buf); + oe = find_object(sha1); + p += 40; + } if (*p++ != ' ') die("Missing space after SHA1: %s", command_buf.buf); @@ -1044,7 +1106,6 @@ static void file_change_m(struct branch *b) p = p_uq; } - oe = find_object(sha1); if (oe) { if (oe->type != OBJ_BLOB) die("Not a blob (actually a %s): %s", @@ -1161,7 +1222,7 @@ static void cmd_new_commit() free(committer); free(msg); - store_object(OBJ_COMMIT, body, sp - body, NULL, b->sha1); + store_object(OBJ_COMMIT, body, sp - body, NULL, b->sha1, next_mark); free(body); b->last_commit = object_count_by_type[OBJ_COMMIT]; } @@ -1205,6 +1266,13 @@ static void cmd_new_branch() else if (s) { memcpy(b->sha1, s->sha1, 20); memcpy(b->branch_tree.sha1, s->branch_tree.sha1, 20); + } else if (*from == ':') { + unsigned long idnum = strtoul(from + 1, NULL, 10); + struct object_entry *oe = find_mark(idnum); + if (oe->type != OBJ_COMMIT) + die("Mark :%lu not a commit", idnum); + memcpy(b->sha1, oe->sha1, 20); + memcpy(b->branch_tree.sha1, null_sha1, 20); } else if (!get_sha1(from, b->sha1)) { if (!memcmp(b->sha1, null_sha1, 20)) memcpy(b->branch_tree.sha1, null_sha1, 20); @@ -1285,6 +1353,7 @@ int main(int argc, const char **argv) atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*)); branch_table = xcalloc(branch_table_sz, sizeof(struct branch*)); avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*)); + marks = pool_calloc(1, sizeof(struct mark_set)); for (;;) { read_next_command(); @@ -1314,7 +1383,8 @@ int main(int argc, const char **argv) fprintf(stderr, " commits: %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT]); fprintf(stderr, " tags : %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG]); fprintf(stderr, "Total branches: %10lu\n", branch_count); - fprintf(stderr, "Total atoms: %10u\n", atom_cnt); + fprintf(stderr, " atoms: %10u\n", atom_cnt); + fprintf(stderr, " marks: %10u (%10lu unique )\n", (1 << marks->shift) * 1024, marks_set_count); fprintf(stderr, "Memory total: %10lu KiB\n", (total_allocd + alloc_count*sizeof(struct object_entry))/1024); fprintf(stderr, " pools: %10lu KiB\n", total_allocd/1024); fprintf(stderr, " objects: %10lu KiB\n", (alloc_count*sizeof(struct object_entry))/1024); From d6c7eb2c160fc40c48fd25fdae15c193eec13bb7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 23 Aug 2006 04:31:12 -0400 Subject: [PATCH 173/548] Added branch load counter to fast-import. If the branch load count exceeds the number of branches created then the frontend is causing fast-import to page branches into and out of memory due to the way its ordering its commits. Performance can likely be increased if the frontend were to alter its commit sequence such that it stays on one branch before switching to another branch, then never returns to the prior branch. Signed-off-by: Shawn O. Pearce --- fast-import.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fast-import.c b/fast-import.c index 6e2f106a1a..50171d69ca 100644 --- a/fast-import.c +++ b/fast-import.c @@ -190,6 +190,7 @@ struct branch static unsigned long max_depth = 10; static unsigned long alloc_count; static unsigned long branch_count; +static unsigned long branch_load_count; static unsigned long object_count; static unsigned long duplicate_count; static unsigned long marks_set_count; @@ -1059,6 +1060,7 @@ static void load_branch(struct branch *b) b->active_next_branch = active_branches; active_branches = b; cur_active_branches++; + branch_load_count++; } static void file_change_m(struct branch *b) @@ -1382,9 +1384,9 @@ int main(int argc, const char **argv) fprintf(stderr, " trees : %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE]); fprintf(stderr, " commits: %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT]); fprintf(stderr, " tags : %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG]); - fprintf(stderr, "Total branches: %10lu\n", branch_count); - fprintf(stderr, " atoms: %10u\n", atom_cnt); + fprintf(stderr, "Total branches: %10lu (%10lu loads )\n", branch_count, branch_load_count); fprintf(stderr, " marks: %10u (%10lu unique )\n", (1 << marks->shift) * 1024, marks_set_count); + fprintf(stderr, " atoms: %10u\n", atom_cnt); fprintf(stderr, "Memory total: %10lu KiB\n", (total_allocd + alloc_count*sizeof(struct object_entry))/1024); fprintf(stderr, " pools: %10lu KiB\n", total_allocd/1024); fprintf(stderr, " objects: %10lu KiB\n", (alloc_count*sizeof(struct object_entry))/1024); From 72303d44e9d8f3fc9bef039b472a2bd259509420 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Aug 2006 03:12:13 -0400 Subject: [PATCH 174/548] Implemented 'tag' command in fast-import. Tags received from the frontend are generated in memory in a simple linked list in the order that the tag commands were sent by the frontend. If multiple different tag objects for the same tag name get generated the last one sent by the frontend will be the one that gets written out at termination. Multiple tag objects for the same name will cause all older tags of the same name to be lost. Signed-off-by: Shawn O. Pearce --- fast-import.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/fast-import.c b/fast-import.c index 50171d69ca..e692f6b430 100644 --- a/fast-import.c +++ b/fast-import.c @@ -185,6 +185,13 @@ struct branch unsigned char sha1[20]; }; +struct tag +{ + struct tag *next_tag; + const char *name; + unsigned char sha1[20]; +}; + /* Stats and misc. counters */ static unsigned long max_depth = 10; @@ -234,6 +241,10 @@ static unsigned long branch_table_sz = 1039; static struct branch **branch_table; static struct branch *active_branches; +/* Tag data */ +static struct tag *first_tag; +static struct tag *last_tag; + /* Input stream parsing */ static struct strbuf command_buf; static unsigned long next_mark; @@ -970,6 +981,21 @@ static void dump_branches() } } +static void dump_tags() +{ + static const char *msg = "fast-import"; + struct tag *t; + struct ref_lock *lock; + char path[PATH_MAX]; + + for (t = first_tag; t; t = t->next_tag) { + sprintf(path, "refs/tags/%s", t->name); + lock = lock_any_ref_for_update(path, NULL, 0); + if (!lock || write_ref_sha1(lock, t->sha1, msg) < 0) + die("Can't write %s", path); + } +} + static void read_next_command() { read_line(&command_buf, stdin, '\n'); @@ -1306,6 +1332,102 @@ static void cmd_new_branch() die("An lf did not terminate the branch command as expected."); } +static void cmd_new_tag() +{ + char *str_uq; + const char *endp; + char *sp; + const char *from; + char *tagger; + struct branch *s; + void *msg; + size_t msglen; + char *body; + struct tag *t; + unsigned char sha1[20]; + + /* Obtain the new tag name from the rest of our command */ + sp = strchr(command_buf.buf, ' ') + 1; + str_uq = unquote_c_style(sp, &endp); + if (str_uq) { + if (*endp) + die("Garbage after tag name in: %s", command_buf.buf); + sp = str_uq; + } + t = pool_alloc(sizeof(struct tag)); + t->next_tag = NULL; + t->name = pool_strdup(sp); + if (last_tag) + last_tag->next_tag = t; + else + first_tag = t; + last_tag = t; + if (str_uq) + free(str_uq); + read_next_command(); + + /* from ... */ + if (strncmp("from ", command_buf.buf, 5)) + die("Expected from command, got %s", command_buf.buf); + + from = strchr(command_buf.buf, ' ') + 1; + str_uq = unquote_c_style(from, &endp); + if (str_uq) { + if (*endp) + die("Garbage after string in: %s", command_buf.buf); + from = str_uq; + } + + s = lookup_branch(from); + if (s) { + memcpy(sha1, s->sha1, 20); + } else if (*from == ':') { + unsigned long idnum = strtoul(from + 1, NULL, 10); + struct object_entry *oe = find_mark(idnum); + if (oe->type != OBJ_COMMIT) + die("Mark :%lu not a commit", idnum); + memcpy(sha1, oe->sha1, 20); + } else if (!get_sha1(from, sha1)) { + unsigned long size; + char *buf; + + buf = read_object_with_reference(sha1, + type_names[OBJ_COMMIT], &size, sha1); + if (!buf || size < 46) + die("Not a valid commit: %s", from); + free(buf); + } else + die("Invalid ref name or SHA1 expression: %s", from); + + if (str_uq) + free(str_uq); + read_next_command(); + + /* tagger ... */ + if (strncmp("tagger ", command_buf.buf, 7)) + die("Expected tagger command, got %s", command_buf.buf); + tagger = strdup(command_buf.buf); + + /* tag payload/message */ + read_next_command(); + msg = cmd_data(&msglen); + + /* build the tag object */ + body = xmalloc(67 + strlen(t->name) + strlen(tagger) + msglen); + sp = body; + sp += sprintf(sp, "object %s\n", sha1_to_hex(sha1)); + sp += sprintf(sp, "type %s\n", type_names[OBJ_COMMIT]); + sp += sprintf(sp, "tag %s\n", t->name); + sp += sprintf(sp, "%s\n\n", tagger); + memcpy(sp, msg, msglen); + sp += msglen; + free(tagger); + free(msg); + + store_object(OBJ_TAG, body, sp - body, NULL, t->sha1, 0); + free(body); +} + static const char fast_import_usage[] = "git-fast-import [--objects=n] [--depth=n] [--active-branches=n] temp.pack"; @@ -1367,6 +1489,8 @@ int main(int argc, const char **argv) cmd_new_branch(); else if (!strncmp("commit ", command_buf.buf, 7)) cmd_new_commit(); + else if (!strncmp("tag ", command_buf.buf, 4)) + cmd_new_tag(); else die("Unsupported command: %s", command_buf.buf); } @@ -1375,6 +1499,7 @@ int main(int argc, const char **argv) close(pack_fd); write_index(idx_name); dump_branches(); + dump_tags(); fprintf(stderr, "%s statistics:\n", argv[0]); fprintf(stderr, "---------------------------------------------------\n"); From 41e5257fcf4db31dfa2576aac1f50b140f2bb058 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Aug 2006 04:37:35 -0400 Subject: [PATCH 175/548] Implemented tree reloading in fast-import. Tree reloading allows fast-import to swap out the least-recently used branch by simply deallocating the data structures from memory that were associated with that branch. Later if the branch becomes active again it can lazily recreate those structures on demand by reloading the necessary trees from the pack file it originally wrote them to. The reloading process is implemented by mmap'ing the pack into memory and using a much tighter variant of the pack reading code contained in sha1_file.c. This was a blatent copy from sha1_file.c but the unpacking functions were significantly simplified and are actually now in a form that should make it easier to map only the necessary regions of a pack rather than the entire file. Signed-off-by: Shawn O. Pearce --- fast-import.c | 162 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 149 insertions(+), 13 deletions(-) diff --git a/fast-import.c b/fast-import.c index e692f6b430..1c74b90c84 100644 --- a/fast-import.c +++ b/fast-import.c @@ -198,6 +198,7 @@ static unsigned long max_depth = 10; static unsigned long alloc_count; static unsigned long branch_count; static unsigned long branch_load_count; +static unsigned long remap_count; static unsigned long object_count; static unsigned long duplicate_count; static unsigned long marks_set_count; @@ -216,8 +217,10 @@ static struct atom_str **atom_table; /* The .pack file being generated */ static int pack_fd; -static unsigned long pack_offset; +static unsigned long pack_size; static unsigned char pack_sha1[20]; +static void* pack_base; +static size_t pack_mlen; /* Table of objects we've written. */ static unsigned int object_entry_alloc = 1000; @@ -616,7 +619,7 @@ static int store_object( return 1; } e->type = type; - e->offset = pack_offset; + e->offset = pack_size; object_count++; object_count_by_type[type]++; @@ -637,7 +640,7 @@ static int store_object( hdrlen = encode_header(OBJ_DELTA, deltalen, hdr); ywrite(pack_fd, hdr, hdrlen); ywrite(pack_fd, last->sha1, sizeof(sha1)); - pack_offset += hdrlen + sizeof(sha1); + pack_size += hdrlen + sizeof(sha1); } else { if (last) last->depth = 0; @@ -645,7 +648,7 @@ static int store_object( s.avail_in = datlen; hdrlen = encode_header(type, datlen, hdr); ywrite(pack_fd, hdr, hdrlen); - pack_offset += hdrlen; + pack_size += hdrlen; } s.avail_out = deflateBound(&s, s.avail_in); @@ -655,7 +658,7 @@ static int store_object( deflateEnd(&s); ywrite(pack_fd, out, s.total_out); - pack_offset += s.total_out; + pack_size += s.total_out; free(out); if (delta) @@ -670,6 +673,127 @@ static int store_object( return 0; } +static void* map_pack(unsigned long offset) +{ + if (offset >= pack_size) + die("object offset outside of pack file"); + if (offset >= pack_mlen) { + if (pack_base) + munmap(pack_base, pack_mlen); + /* round out how much we map to 16 MB units */ + pack_mlen = pack_size; + if (pack_mlen & ((1 << 24) - 1)) + pack_mlen = ((pack_mlen >> 24) + 1) << 24; + pack_base = mmap(NULL,pack_mlen,PROT_READ,MAP_SHARED,pack_fd,0); + if (pack_base == MAP_FAILED) + die("Failed to map generated pack: %s", strerror(errno)); + remap_count++; + } + return (char*)pack_base + offset; +} + +static unsigned long unpack_object_header(unsigned long offset, + enum object_type *type, + unsigned long *sizep) +{ + unsigned shift; + unsigned char c; + unsigned long size; + + c = *(unsigned char*)map_pack(offset++); + *type = (c >> 4) & 7; + size = c & 15; + shift = 4; + while (c & 0x80) { + c = *(unsigned char*)map_pack(offset++); + size += (c & 0x7f) << shift; + shift += 7; + } + *sizep = size; + return offset; +} + +static void *unpack_non_delta_entry(unsigned long o, unsigned long sz) +{ + z_stream stream; + unsigned char *result; + + result = xmalloc(sz + 1); + result[sz] = 0; + + memset(&stream, 0, sizeof(stream)); + stream.next_in = map_pack(o); + stream.avail_in = pack_mlen - o; + stream.next_out = result; + stream.avail_out = sz; + + inflateInit(&stream); + for (;;) { + int st = inflate(&stream, Z_FINISH); + if (st == Z_STREAM_END) + break; + if (st == Z_OK) { + o = stream.next_in - (unsigned char*)pack_base; + stream.next_in = map_pack(o); + stream.avail_in = pack_mlen - o; + continue; + } + die("Error from zlib during inflate."); + } + inflateEnd(&stream); + if (stream.total_out != sz) + die("Error after inflate: sizes mismatch"); + return result; +} + +static void *unpack_entry(unsigned long offset, unsigned long *sizep); + +static void *unpack_delta_entry(unsigned long offset, + unsigned long delta_size, + unsigned long *sizep) +{ + struct object_entry *base_oe; + unsigned char *base_sha1; + void *delta_data, *base, *result; + unsigned long base_size, result_size; + + base_sha1 = (unsigned char*)map_pack(offset + 20) - 20; + base_oe = find_object(base_sha1); + if (!base_oe) + die("I'm broken; I can't find a base I know must be here."); + base = unpack_entry(base_oe->offset, &base_size); + delta_data = unpack_non_delta_entry(offset + 20, delta_size); + result = patch_delta(base, base_size, + delta_data, delta_size, + &result_size); + if (!result) + die("failed to apply delta"); + free(delta_data); + free(base); + *sizep = result_size; + return result; +} + +static void *unpack_entry(unsigned long offset, unsigned long *sizep) +{ + unsigned long size; + enum object_type kind; + + offset = unpack_object_header(offset, &kind, &size); + switch (kind) { + case OBJ_DELTA: + return unpack_delta_entry(offset, size, sizep); + case OBJ_COMMIT: + case OBJ_TREE: + case OBJ_BLOB: + case OBJ_TAG: + *sizep = size; + return unpack_non_delta_entry(offset, size); + default: + die("I created an object I can't read!"); + } +} + static const char *get_mode(const char *str, unsigned int *modep) { unsigned char c; @@ -691,7 +815,6 @@ static void load_tree(struct tree_entry *root) unsigned long size; char *buf; const char *c; - char type[20]; root->tree = t = new_tree_content(8); if (!memcmp(root->sha1, null_sha1, 20)) @@ -699,11 +822,14 @@ static void load_tree(struct tree_entry *root) myoe = find_object(root->sha1); if (myoe) { - die("FIXME"); + if (myoe->type != OBJ_TREE) + die("Not a tree: %s", sha1_to_hex(root->sha1)); + buf = unpack_entry(myoe->offset, &size); } else { + char type[20]; buf = read_sha1_file(root->sha1, type, &size); - if (!buf || strcmp(type, tree_type)) - die("Can't load existing tree %s", sha1_to_hex(root->sha1)); + if (!buf || !strcmp(type, tree_type)) + die("Can't load tree %s", sha1_to_hex(root->sha1)); } c = buf; @@ -880,7 +1006,7 @@ static void init_pack_header() hdr.hdr_entries = 0; ywrite(pack_fd, &hdr, sizeof(hdr)); - pack_offset = sizeof(hdr); + pack_size = sizeof(hdr); } static void fixup_header_footer() @@ -1052,7 +1178,8 @@ static void cmd_new_blob() static void unload_one_branch() { - while (cur_active_branches >= max_active_branches) { + while (cur_active_branches + && cur_active_branches >= max_active_branches) { unsigned long min_commit = ULONG_MAX; struct branch *e, *l = NULL, *p = NULL; @@ -1210,7 +1337,7 @@ static void cmd_new_commit() msg = cmd_data(&msglen); /* ensure the branch is active/loaded */ - if (!b->branch_tree.tree) { + if (!b->branch_tree.tree || !max_active_branches) { unload_one_branch(); load_branch(b); } @@ -1297,10 +1424,18 @@ static void cmd_new_branch() } else if (*from == ':') { unsigned long idnum = strtoul(from + 1, NULL, 10); struct object_entry *oe = find_mark(idnum); + unsigned long size; + char *buf; if (oe->type != OBJ_COMMIT) die("Mark :%lu not a commit", idnum); memcpy(b->sha1, oe->sha1, 20); - memcpy(b->branch_tree.sha1, null_sha1, 20); + buf = unpack_entry(oe->offset, &size); + if (!buf || size < 46) + die("Not a valid commit: %s", from); + if (memcmp("tree ", buf, 5) + || get_sha1_hex(buf + 5, b->branch_tree.sha1)) + die("The commit %s is corrupt", sha1_to_hex(b->sha1)); + free(buf); } else if (!get_sha1(from, b->sha1)) { if (!memcmp(b->sha1, null_sha1, 20)) memcpy(b->branch_tree.sha1, null_sha1, 20); @@ -1515,6 +1650,7 @@ int main(int argc, const char **argv) fprintf(stderr, "Memory total: %10lu KiB\n", (total_allocd + alloc_count*sizeof(struct object_entry))/1024); fprintf(stderr, " pools: %10lu KiB\n", total_allocd/1024); fprintf(stderr, " objects: %10lu KiB\n", (alloc_count*sizeof(struct object_entry))/1024); + fprintf(stderr, "Pack remaps: %10lu\n", remap_count); fprintf(stderr, "---------------------------------------------------\n"); stat(pack_name, &sb); From 8d8928b0511313ba1740d39c3920f8f12f36a10a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Aug 2006 04:46:29 -0400 Subject: [PATCH 176/548] Round out memory pool allocations in fast-import to pointer sizes. Some architectures (e.g. SPARC) would require that we access pointers only on pointer-sized alignments. So ensure the pool allocator rounds out non-pointer sized allocations to the next pointer so we don't generate bad memory addresses. This could have occurred if we had previously allocated an atom whose string was not a whole multiple of the pointer size, for example. Signed-off-by: Shawn O. Pearce --- fast-import.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fast-import.c b/fast-import.c index 1c74b90c84..e42bdbd3a3 100644 --- a/fast-import.c +++ b/fast-import.c @@ -342,6 +342,9 @@ static void* pool_alloc(size_t len) } r = p->next_free; + /* round out to a pointer alignment */ + if (len & (sizeof(void*) - 1)) + len += sizeof(void*) - (len & (sizeof(void*) - 1)); p->next_free += len; return r; } From 00e2b8842c58e451fcf8038287c8420423bab50a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Aug 2006 18:45:26 -0400 Subject: [PATCH 177/548] Remove branch creation command from fast-import. Jon Smirl was finding it difficult to alter cvs2svn to generate branch commands prior to the first commit of the same branch. This change moves the 'from' command to be an optional parameter of the 'commit' command, thereby allowing a new branch to be defined at the moment it gets used to create the first commit on that branch. This change makes it impossible to create a branch with no commits on it as at least one commit is needed to register the branch. Signed-off-by: Shawn O. Pearce --- fast-import.c | 169 +++++++++++++++++++++----------------------------- 1 file changed, 71 insertions(+), 98 deletions(-) diff --git a/fast-import.c b/fast-import.c index e42bdbd3a3..3e527edf70 100644 --- a/fast-import.c +++ b/fast-import.c @@ -4,7 +4,6 @@ Format of STDIN stream: stream ::= cmd*; cmd ::= new_blob - | new_branch | new_commit | new_tag ; @@ -14,15 +13,12 @@ Format of STDIN stream: file_content; file_content ::= data; - new_branch ::= 'branch' sp ref_str lf - ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? - lf; - new_commit ::= 'commit' sp ref_str lf - mark? - ('author' sp name '<' email '>' ts tz lf)? - 'committer' sp name '<' email '>' ts tz lf - commit_msg + ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? + mark? + ('author' sp name '<' email '>' ts tz lf)? + 'committer' sp name '<' email '>' ts tz lf + commit_msg file_change* lf; commit_msg ::= data; @@ -831,7 +827,7 @@ static void load_tree(struct tree_entry *root) } else { char type[20]; buf = read_sha1_file(root->sha1, type, &size); - if (!buf || !strcmp(type, tree_type)) + if (!buf || strcmp(type, tree_type)) die("Can't load tree %s", sha1_to_hex(root->sha1)); } @@ -1299,6 +1295,69 @@ static void file_change_d(struct branch *b) free(p_uq); } +static void cmd_from(struct branch *b) +{ + const char *from, *endp; + char *str_uq; + struct branch *s; + + if (strncmp("from ", command_buf.buf, 5)) + return; + + if (b->last_commit) + die("Can't reinitailize branch %s", b->name); + + from = strchr(command_buf.buf, ' ') + 1; + str_uq = unquote_c_style(from, &endp); + if (str_uq) { + if (*endp) + die("Garbage after string in: %s", command_buf.buf); + from = str_uq; + } + + s = lookup_branch(from); + if (b == s) + die("Can't create a branch from itself: %s", b->name); + else if (s) { + memcpy(b->sha1, s->sha1, 20); + memcpy(b->branch_tree.sha1, s->branch_tree.sha1, 20); + } else if (*from == ':') { + unsigned long idnum = strtoul(from + 1, NULL, 10); + struct object_entry *oe = find_mark(idnum); + unsigned long size; + char *buf; + if (oe->type != OBJ_COMMIT) + die("Mark :%lu not a commit", idnum); + memcpy(b->sha1, oe->sha1, 20); + buf = unpack_entry(oe->offset, &size); + if (!buf || size < 46) + die("Not a valid commit: %s", from); + if (memcmp("tree ", buf, 5) + || get_sha1_hex(buf + 5, b->branch_tree.sha1)) + die("The commit %s is corrupt", sha1_to_hex(b->sha1)); + free(buf); + } else if (!get_sha1(from, b->sha1)) { + if (!memcmp(b->sha1, null_sha1, 20)) + memcpy(b->branch_tree.sha1, null_sha1, 20); + else { + unsigned long size; + char *buf; + + buf = read_object_with_reference(b->sha1, + type_names[OBJ_COMMIT], &size, b->sha1); + if (!buf || size < 46) + die("Not a valid commit: %s", from); + if (memcmp("tree ", buf, 5) + || get_sha1_hex(buf + 5, b->branch_tree.sha1)) + die("The commit %s is corrupt", sha1_to_hex(b->sha1)); + free(buf); + } + } else + die("Invalid ref name or SHA1 expression: %s", from); + + read_next_command(); +} + static void cmd_new_commit() { struct branch *b; @@ -1321,11 +1380,12 @@ static void cmd_new_commit() } b = lookup_branch(sp); if (!b) - die("Branch not declared: %s", sp); + b = new_branch(sp); if (str_uq) free(str_uq); read_next_command(); + cmd_from(b); cmd_mark(); if (!strncmp("author ", command_buf.buf, 7)) { author = strdup(command_buf.buf); @@ -1385,91 +1445,6 @@ static void cmd_new_commit() b->last_commit = object_count_by_type[OBJ_COMMIT]; } -static void cmd_new_branch() -{ - struct branch *b; - char *str_uq; - const char *endp; - char *sp; - - /* Obtain the new branch name from the rest of our command */ - sp = strchr(command_buf.buf, ' ') + 1; - str_uq = unquote_c_style(sp, &endp); - if (str_uq) { - if (*endp) - die("Garbage after ref in: %s", command_buf.buf); - sp = str_uq; - } - b = new_branch(sp); - if (str_uq) - free(str_uq); - read_next_command(); - - /* from ... */ - if (!strncmp("from ", command_buf.buf, 5)) { - const char *from; - struct branch *s; - - from = strchr(command_buf.buf, ' ') + 1; - str_uq = unquote_c_style(from, &endp); - if (str_uq) { - if (*endp) - die("Garbage after string in: %s", command_buf.buf); - from = str_uq; - } - - s = lookup_branch(from); - if (b == s) - die("Can't create a branch from itself: %s", b->name); - else if (s) { - memcpy(b->sha1, s->sha1, 20); - memcpy(b->branch_tree.sha1, s->branch_tree.sha1, 20); - } else if (*from == ':') { - unsigned long idnum = strtoul(from + 1, NULL, 10); - struct object_entry *oe = find_mark(idnum); - unsigned long size; - char *buf; - if (oe->type != OBJ_COMMIT) - die("Mark :%lu not a commit", idnum); - memcpy(b->sha1, oe->sha1, 20); - buf = unpack_entry(oe->offset, &size); - if (!buf || size < 46) - die("Not a valid commit: %s", from); - if (memcmp("tree ", buf, 5) - || get_sha1_hex(buf + 5, b->branch_tree.sha1)) - die("The commit %s is corrupt", sha1_to_hex(b->sha1)); - free(buf); - } else if (!get_sha1(from, b->sha1)) { - if (!memcmp(b->sha1, null_sha1, 20)) - memcpy(b->branch_tree.sha1, null_sha1, 20); - else { - unsigned long size; - char *buf; - - buf = read_object_with_reference(b->sha1, - type_names[OBJ_COMMIT], &size, b->sha1); - if (!buf || size < 46) - die("Not a valid commit: %s", from); - if (memcmp("tree ", buf, 5) - || get_sha1_hex(buf + 5, b->branch_tree.sha1)) - die("The commit %s is corrupt", sha1_to_hex(b->sha1)); - free(buf); - } - } else - die("Invalid ref name or SHA1 expression: %s", from); - - if (str_uq) - free(str_uq); - read_next_command(); - } else { - memcpy(b->sha1, null_sha1, 20); - memcpy(b->branch_tree.sha1, null_sha1, 20); - } - - if (command_buf.eof || command_buf.len > 1) - die("An lf did not terminate the branch command as expected."); -} - static void cmd_new_tag() { char *str_uq; @@ -1623,8 +1598,6 @@ int main(int argc, const char **argv) break; else if (!strcmp("blob", command_buf.buf)) cmd_new_blob(); - else if (!strncmp("branch ", command_buf.buf, 7)) - cmd_new_branch(); else if (!strncmp("commit ", command_buf.buf, 7)) cmd_new_commit(); else if (!strncmp("tag ", command_buf.buf, 4)) From 02f3389d9647378ed864ff1cdfb6f0238b64ee91 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Aug 2006 22:38:13 -0400 Subject: [PATCH 178/548] Moved from command to after data to help cvs2svn. cvs2svn has three phases: begin_commit, middle_commit, end_commit. The ancester is computed in the middle_commit phase. So its easier to generate a stream if the from command appears after the commit message itself but before the file change commands. Signed-off-by: Shawn O. Pearce --- fast-import.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fast-import.c b/fast-import.c index 3e527edf70..1842d0738b 100644 --- a/fast-import.c +++ b/fast-import.c @@ -14,11 +14,11 @@ Format of STDIN stream: file_content ::= data; new_commit ::= 'commit' sp ref_str lf - ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? mark? ('author' sp name '<' email '>' ts tz lf)? 'committer' sp name '<' email '>' ts tz lf commit_msg + ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? file_change* lf; commit_msg ::= data; @@ -1385,7 +1385,6 @@ static void cmd_new_commit() free(str_uq); read_next_command(); - cmd_from(b); cmd_mark(); if (!strncmp("author ", command_buf.buf, 7)) { author = strdup(command_buf.buf); @@ -1398,6 +1397,8 @@ static void cmd_new_commit() if (!committer) die("Expected committer but didn't get one"); msg = cmd_data(&msglen); + read_next_command(); + cmd_from(b); /* ensure the branch is active/loaded */ if (!b->branch_tree.tree || !max_active_branches) { @@ -1407,7 +1408,6 @@ static void cmd_new_commit() /* file_change* */ for (;;) { - read_next_command(); if (1 == command_buf.len) break; else if (!strncmp("M ", command_buf.buf, 2)) @@ -1416,6 +1416,7 @@ static void cmd_new_commit() file_change_d(b); else die("Unsupported file_change: %s", command_buf.buf); + read_next_command(); } /* build the tree and the commit */ From 8435a9cb2662ca4326e96ea78d58b9376fb21f7e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 25 Aug 2006 14:53:32 -0400 Subject: [PATCH 179/548] Account for tree entry memory costs in fast-import. Signed-off-by: Shawn O. Pearce --- fast-import.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fast-import.c b/fast-import.c index 1842d0738b..311db4e6d5 100644 --- a/fast-import.c +++ b/fast-import.c @@ -516,6 +516,7 @@ static struct tree_entry* new_tree_entry() if (!avail_tree_entry) { unsigned int n = tree_entry_alloc; + total_allocd += n * sizeof(struct tree_entry); avail_tree_entry = e = xmalloc(n * sizeof(struct tree_entry)); while (n--) { *((void**)e) = e + 1; From a6a1a831d9bdcdc0adb9a23ce450db08779c2871 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 25 Aug 2006 16:03:04 -0400 Subject: [PATCH 180/548] Added option to export the marks table when fast-import terminates. The marks table can be used by the frontend to load any commit after the import and compare it to whatever data the frontend knows about that commit. If the mark idnums can be easily correlated to some reference source then its relatively trivial to compare the GIT tree to the reference to verify the accuracy of the import. Signed-off-by: Shawn O. Pearce --- fast-import.c | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/fast-import.c b/fast-import.c index 311db4e6d5..d61da3adec 100644 --- a/fast-import.c +++ b/fast-import.c @@ -223,6 +223,7 @@ static unsigned int object_entry_alloc = 1000; static struct object_entry_pool *blocks; static struct object_entry *object_table[1 << 16]; static struct mark_set *marks; +static const char* mark_file; /* Our last blob */ static struct last_object last_blob; @@ -1122,6 +1123,36 @@ static void dump_tags() } } +static void dump_marks_helper(FILE *f, + unsigned long base, + struct mark_set *m) +{ + int k; + if (m->shift) { + for (k = 0; k < 1024; k++) { + if (m->data.sets[k]) + dump_marks_helper(f, (base + k) << m->shift, + m->data.sets[k]); + } + } else { + for (k = 0; k < 1024; k++) { + if (m->data.marked[k]) + fprintf(f, "%lu,%s\n", base + k, + sha1_to_hex(m->data.marked[k]->sha1)); + } + } +} + +static void dump_marks() +{ + if (mark_file) + { + FILE *f = fopen(mark_file, "w"); + dump_marks_helper(f, 0, marks); + fclose(f); + } +} + static void read_next_command() { read_line(&command_buf, stdin, '\n'); @@ -1544,7 +1575,7 @@ static void cmd_new_tag() } static const char fast_import_usage[] = -"git-fast-import [--objects=n] [--depth=n] [--active-branches=n] temp.pack"; +"git-fast-import [--objects=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file] temp.pack"; int main(int argc, const char **argv) { @@ -1569,6 +1600,8 @@ int main(int argc, const char **argv) max_depth = strtoul(a + 8, NULL, 0); else if (!strncmp(a, "--active-branches=", 18)) max_active_branches = strtoul(a + 18, NULL, 0); + else if (!strncmp(a, "--export-marks=", 15)) + mark_file = a + 15; else die("unknown option %s", a); } @@ -1613,6 +1646,7 @@ int main(int argc, const char **argv) write_index(idx_name); dump_branches(); dump_tags(); + dump_marks(); fprintf(stderr, "%s statistics:\n", argv[0]); fprintf(stderr, "---------------------------------------------------\n"); From 264244a0429e23616a6065f6f52a15711981a8db Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 25 Aug 2006 23:07:06 -0400 Subject: [PATCH 181/548] Added --branch-log to option to fast-import. This option can be used to have a record of every commit, the mark (if supplied) and branch name of the commit recorded into a log file when the commit is generated. This log can be useful to verify the results of an import as the commits can be compared to some source repository matching commits through the mark value. Signed-off-by: Shawn O. Pearce --- fast-import.c | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/fast-import.c b/fast-import.c index d61da3adec..8328e004bb 100644 --- a/fast-import.c +++ b/fast-import.c @@ -248,6 +248,7 @@ static struct tag *last_tag; /* Input stream parsing */ static struct strbuf command_buf; static unsigned long next_mark; +static FILE* branch_log; static void alloc_objects(int cnt) @@ -1137,7 +1138,7 @@ static void dump_marks_helper(FILE *f, } else { for (k = 0; k < 1024; k++) { if (m->data.marked[k]) - fprintf(f, "%lu,%s\n", base + k, + fprintf(f, ":%lu %s\n", base + k, sha1_to_hex(m->data.marked[k]->sha1)); } } @@ -1476,6 +1477,18 @@ static void cmd_new_commit() store_object(OBJ_COMMIT, body, sp - body, NULL, b->sha1, next_mark); free(body); b->last_commit = object_count_by_type[OBJ_COMMIT]; + + if (branch_log) { + int need_dq = quote_c_style(b->name, NULL, NULL, 0); + fprintf(branch_log, "commit "); + if (need_dq) { + fputc('"', branch_log); + quote_c_style(b->name, NULL, branch_log, 0); + fputc('"', branch_log); + } else + fprintf(branch_log, "%s", b->name); + fprintf(branch_log," :%lu %s\n",next_mark,sha1_to_hex(b->sha1)); + } } static void cmd_new_tag() @@ -1490,6 +1503,7 @@ static void cmd_new_tag() size_t msglen; char *body; struct tag *t; + unsigned long from_mark = 0; unsigned char sha1[20]; /* Obtain the new tag name from the rest of our command */ @@ -1528,10 +1542,10 @@ static void cmd_new_tag() if (s) { memcpy(sha1, s->sha1, 20); } else if (*from == ':') { - unsigned long idnum = strtoul(from + 1, NULL, 10); - struct object_entry *oe = find_mark(idnum); + from_mark = strtoul(from + 1, NULL, 10); + struct object_entry *oe = find_mark(from_mark); if (oe->type != OBJ_COMMIT) - die("Mark :%lu not a commit", idnum); + die("Mark :%lu not a commit", from_mark); memcpy(sha1, oe->sha1, 20); } else if (!get_sha1(from, sha1)) { unsigned long size; @@ -1572,10 +1586,22 @@ static void cmd_new_tag() store_object(OBJ_TAG, body, sp - body, NULL, t->sha1, 0); free(body); + + if (branch_log) { + int need_dq = quote_c_style(t->name, NULL, NULL, 0); + fprintf(branch_log, "tag "); + if (need_dq) { + fputc('"', branch_log); + quote_c_style(t->name, NULL, branch_log, 0); + fputc('"', branch_log); + } else + fprintf(branch_log, "%s", t->name); + fprintf(branch_log," :%lu %s\n",from_mark,sha1_to_hex(t->sha1)); + } } static const char fast_import_usage[] = -"git-fast-import [--objects=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file] temp.pack"; +"git-fast-import [--objects=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file] [--branch-log=log] temp.pack"; int main(int argc, const char **argv) { @@ -1602,6 +1628,11 @@ int main(int argc, const char **argv) max_active_branches = strtoul(a + 18, NULL, 0); else if (!strncmp(a, "--export-marks=", 15)) mark_file = a + 15; + else if (!strncmp(a, "--branch-log=", 13)) { + branch_log = fopen(a + 13, "w"); + if (!branch_log) + die("Can't create %s: %s", a + 13, strerror(errno)); + } else die("unknown option %s", a); } @@ -1647,6 +1678,7 @@ int main(int argc, const char **argv) dump_branches(); dump_tags(); dump_marks(); + fclose(branch_log); fprintf(stderr, "%s statistics:\n", argv[0]); fprintf(stderr, "---------------------------------------------------\n"); From 2eb26d8454de77f45bbbfc32eed2a6c3133fe963 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 Aug 2006 22:38:02 -0400 Subject: [PATCH 182/548] Fixed GPF in fast-import caused by unterminated linked list. fast-import was encounting a GPF when it ran out of free tree_entry objects but didn't know this was the cause because the last tree_entry wasn't terminated with a NULL pointer. The missing NULL pointer occurred when we allocated additional entries via xmalloc but didn't set the last tree_entry's "next" pointer to NULL. Signed-off-by: Shawn O. Pearce --- fast-import.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fast-import.c b/fast-import.c index 8328e004bb..194116be6f 100644 --- a/fast-import.c +++ b/fast-import.c @@ -520,10 +520,11 @@ static struct tree_entry* new_tree_entry() unsigned int n = tree_entry_alloc; total_allocd += n * sizeof(struct tree_entry); avail_tree_entry = e = xmalloc(n * sizeof(struct tree_entry)); - while (n--) { + while (n-- > 1) { *((void**)e) = e + 1; e++; } + *((void*)e) = NULL; } e = avail_tree_entry; From 35ef237cf630418c2e45752eb527268693a2895b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 Aug 2006 23:37:31 -0400 Subject: [PATCH 183/548] Fixed compile error in fast-import. Signed-off-by: Shawn O. Pearce --- fast-import.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fast-import.c b/fast-import.c index 194116be6f..309f4d353b 100644 --- a/fast-import.c +++ b/fast-import.c @@ -524,7 +524,7 @@ static struct tree_entry* new_tree_entry() *((void**)e) = e + 1; e++; } - *((void*)e) = NULL; + *((void**)e) = NULL; } e = avail_tree_entry; From 53dbce78a2a018bd2828d3ecc4123015f88ae36f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 27 Aug 2006 05:53:48 -0400 Subject: [PATCH 184/548] Map only part of the generated pack file at any point in time. When generating a very large pack file (for example close to 1 GB in size) it may be impossible for the kernel to find a contiguous free range within a 32 bit address space for the mapping to be located at. This is especially problematic on large imports where there is a lot of malloc activity occuring within the same process and the malloc'd regions may straddle the previously mapped regions, thereby creating large holes in the address space. So instead we map only 128 MB of the pack at any given time. This will likely increase the number of times the file gets mapped (with additional system time required to update the page tables more frequently) but will allow the program to handle packs up to 4 GB in size. Signed-off-by: Shawn O. Pearce --- fast-import.c | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/fast-import.c b/fast-import.c index 309f4d353b..f3376c60ef 100644 --- a/fast-import.c +++ b/fast-import.c @@ -215,8 +215,10 @@ static struct atom_str **atom_table; static int pack_fd; static unsigned long pack_size; static unsigned char pack_sha1[20]; -static void* pack_base; -static size_t pack_mlen; +static unsigned char* pack_base; +static unsigned long pack_moff; +static unsigned long pack_mlen = 128*1024*1024; +static unsigned long page_size; /* Table of objects we've written. */ static unsigned int object_entry_alloc = 1000; @@ -676,23 +678,26 @@ static int store_object( return 0; } -static void* map_pack(unsigned long offset) +static unsigned char* map_pack(unsigned long offset, unsigned int *left) { if (offset >= pack_size) die("object offset outside of pack file"); - if (offset >= pack_mlen) { + if (!pack_base + || offset < pack_moff + || (offset + 20) >= (pack_moff + pack_mlen)) { if (pack_base) munmap(pack_base, pack_mlen); - /* round out how much we map to 16 MB units */ - pack_mlen = pack_size; - if (pack_mlen & ((1 << 24) - 1)) - pack_mlen = ((pack_mlen >> 24) + 1) << 24; - pack_base = mmap(NULL,pack_mlen,PROT_READ,MAP_SHARED,pack_fd,0); + pack_moff = (offset / page_size) * page_size; + pack_base = mmap(NULL,pack_mlen,PROT_READ,MAP_SHARED, + pack_fd,pack_moff); if (pack_base == MAP_FAILED) die("Failed to map generated pack: %s", strerror(errno)); remap_count++; } - return (char*)pack_base + offset; + offset -= pack_moff; + if (left) + *left = pack_mlen - offset; + return pack_base + offset; } static unsigned long unpack_object_header(unsigned long offset, @@ -703,12 +708,12 @@ static unsigned long unpack_object_header(unsigned long offset, unsigned char c; unsigned long size; - c = *(unsigned char*)map_pack(offset++); + c = *map_pack(offset++, NULL); *type = (c >> 4) & 7; size = c & 15; shift = 4; while (c & 0x80) { - c = *(unsigned char*)map_pack(offset++); + c = *map_pack(offset++, NULL); size += (c & 0x7f) << shift; shift += 7; } @@ -725,8 +730,7 @@ static void *unpack_non_delta_entry(unsigned long o, unsigned long sz) result[sz] = 0; memset(&stream, 0, sizeof(stream)); - stream.next_in = map_pack(o); - stream.avail_in = pack_mlen - o; + stream.next_in = map_pack(o, &stream.avail_in); stream.next_out = result; stream.avail_out = sz; @@ -735,13 +739,12 @@ static void *unpack_non_delta_entry(unsigned long o, unsigned long sz) int st = inflate(&stream, Z_FINISH); if (st == Z_STREAM_END) break; - if (st == Z_OK) { - o = stream.next_in - (unsigned char*)pack_base; - stream.next_in = map_pack(o); - stream.avail_in = pack_mlen - o; + if (st == Z_OK || st == Z_BUF_ERROR) { + o = stream.next_in - pack_base + pack_moff; + stream.next_in = map_pack(o, &stream.avail_in); continue; } - die("Error from zlib during inflate."); + die("Error %i from zlib during inflate.", st); } inflateEnd(&stream); if (stream.total_out != sz) @@ -760,7 +763,7 @@ static void *unpack_delta_entry(unsigned long offset, void *delta_data, *base, *result; unsigned long base_size, result_size; - base_sha1 = (unsigned char*)map_pack(offset + 20) - 20; + base_sha1 = map_pack(offset, NULL); base_oe = find_object(base_sha1); if (!base_oe) die("I'm broken; I can't find a base I know must be here."); @@ -1615,6 +1618,7 @@ int main(int argc, const char **argv) setup_ident(); git_config(git_default_config); + page_size = getpagesize(); for (i = 1; i < argc; i++) { const char *a = argv[i]; From 5fced8dc6f4844997b6e25a67a00f428775c5233 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 27 Aug 2006 06:20:49 -0400 Subject: [PATCH 185/548] Added 'reset' command to clear a branch's tree. Sometimes an import frontend may need to work with a temporary branch which will actually contain many different branches over the life of the import. This is especially useful when the frontend needs to create a tag from a set of file versions which are otherwise never a commit. Signed-off-by: Shawn O. Pearce --- fast-import.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/fast-import.c b/fast-import.c index f3376c60ef..778b8bfdd4 100644 --- a/fast-import.c +++ b/fast-import.c @@ -6,6 +6,7 @@ Format of STDIN stream: cmd ::= new_blob | new_commit | new_tag + | reset_branch ; new_blob ::= 'blob' lf @@ -34,6 +35,8 @@ Format of STDIN stream: tag_msg; tag_msg ::= data; + reset_branch ::= 'reset' sp ref_str lf; + # note: the first idnum in a stream should be 1 and subsequent # idnums should not have gaps between values as this will cause # the stream parser to reserve space for the gapped values. An @@ -1604,6 +1607,33 @@ static void cmd_new_tag() } } +static void cmd_reset_branch() +{ + struct branch *b; + char *str_uq; + const char *endp; + char *sp; + + /* Obtain the branch name from the rest of our command */ + sp = strchr(command_buf.buf, ' ') + 1; + str_uq = unquote_c_style(sp, &endp); + if (str_uq) { + if (*endp) + die("Garbage after ref in: %s", command_buf.buf); + sp = str_uq; + } + b = lookup_branch(sp); + if (b) { + b->last_commit = 0; + if (b->branch_tree.tree) { + release_tree_content_recursive(b->branch_tree.tree); + b->branch_tree.tree = NULL; + } + } + if (str_uq) + free(str_uq); +} + static const char fast_import_usage[] = "git-fast-import [--objects=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file] [--branch-log=log] temp.pack"; @@ -1673,6 +1703,8 @@ int main(int argc, const char **argv) cmd_new_commit(); else if (!strncmp("tag ", command_buf.buf, 4)) cmd_new_tag(); + else if (!strncmp("reset ", command_buf.buf, 6)) + cmd_reset_branch(); else die("Unsupported command: %s", command_buf.buf); } From 08d7e892a714dec8471cd45add2b1da24f66b3e7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 27 Aug 2006 20:13:44 -0400 Subject: [PATCH 186/548] Don't crash fast-import if no branch log was requested. Signed-off-by: Shawn O. Pearce --- fast-import.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fast-import.c b/fast-import.c index 778b8bfdd4..5376b5e15c 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1715,7 +1715,8 @@ int main(int argc, const char **argv) dump_branches(); dump_tags(); dump_marks(); - fclose(branch_log); + if (branch_log) + fclose(branch_log); fprintf(stderr, "%s statistics:\n", argv[0]); fprintf(stderr, "---------------------------------------------------\n"); From 445b85999a309c8e5c7f928484c57325c280152e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Aug 2006 10:46:58 -0400 Subject: [PATCH 187/548] Converted hash memcpy/memcmp to new hashcpy/hashcmp/hashclr. Signed-off-by: Shawn O. Pearce --- fast-import.c | 52 +++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/fast-import.c b/fast-import.c index 5376b5e15c..b1b2382560 100644 --- a/fast-import.c +++ b/fast-import.c @@ -277,7 +277,7 @@ static struct object_entry* new_object(unsigned char *sha1) alloc_objects(object_entry_alloc); e = blocks->next_free++; - memcpy(e->sha1, sha1, sizeof(e->sha1)); + hashcpy(e->sha1, sha1); return e; } @@ -286,7 +286,7 @@ static struct object_entry* find_object(unsigned char *sha1) unsigned int h = sha1[0] << 8 | sha1[1]; struct object_entry *e; for (e = object_table[h]; e; e = e->next) - if (!memcmp(sha1, e->sha1, sizeof(e->sha1))) + if (!hashcmp(sha1, e->sha1)) return e; return NULL; } @@ -298,7 +298,7 @@ static struct object_entry* insert_object(unsigned char *sha1) struct object_entry *p = NULL; while (e) { - if (!memcmp(sha1, e->sha1, sizeof(e->sha1))) + if (!hashcmp(sha1, e->sha1)) return e; p = e; e = e->next; @@ -616,7 +616,7 @@ static int store_object( SHA1_Update(&c, dat, datlen); SHA1_Final(sha1, &c); if (sha1out) - memcpy(sha1out, sha1, sizeof(sha1)); + hashcpy(sha1out, sha1); e = insert_object(sha1); if (mark) @@ -676,7 +676,7 @@ static int store_object( free(last->data); last->data = dat; last->len = datlen; - memcpy(last->sha1, sha1, sizeof(sha1)); + hashcpy(last->sha1, sha1); } return 0; } @@ -826,7 +826,7 @@ static void load_tree(struct tree_entry *root) const char *c; root->tree = t = new_tree_content(8); - if (!memcmp(root->sha1, null_sha1, 20)) + if (is_null_sha1(root->sha1)) return; myoe = find_object(root->sha1); @@ -855,7 +855,7 @@ static void load_tree(struct tree_entry *root) die("Corrupt mode in %s", sha1_to_hex(root->sha1)); e->name = to_atom(c, strlen(c)); c += e->name->str_len + 1; - memcpy(e->sha1, c, sizeof(e->sha1)); + hashcpy(e->sha1, c); c += 20; } free(buf); @@ -877,7 +877,7 @@ static void store_tree(struct tree_entry *root) size_t maxlen; char *buf, *c; - if (memcmp(root->sha1, null_sha1, 20)) + if (!is_null_sha1(root->sha1)) return; maxlen = 0; @@ -895,7 +895,7 @@ static void store_tree(struct tree_entry *root) *c++ = ' '; strcpy(c, e->name->str_dat); c += e->name->str_len + 1; - memcpy(c, e->sha1, 20); + hashcpy(c, e->sha1); c += 20; } store_object(OBJ_TREE, buf, c - buf, NULL, root->sha1, 0); @@ -923,15 +923,15 @@ static int tree_content_set( e = t->entries[i]; if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) { if (!slash1) { - if (e->mode == mode && !memcmp(e->sha1, sha1, 20)) + if (e->mode == mode && !hashcmp(e->sha1, sha1)) return 0; e->mode = mode; - memcpy(e->sha1, sha1, 20); + hashcpy(e->sha1, sha1); if (e->tree) { release_tree_content_recursive(e->tree); e->tree = NULL; } - memcpy(root->sha1, null_sha1, 20); + hashclr(root->sha1); return 1; } if (!S_ISDIR(e->mode)) { @@ -941,7 +941,7 @@ static int tree_content_set( if (!e->tree) load_tree(e); if (tree_content_set(e, slash1 + 1, sha1, mode)) { - memcpy(root->sha1, null_sha1, 20); + hashclr(root->sha1); return 1; } return 0; @@ -960,9 +960,9 @@ static int tree_content_set( } else { e->tree = NULL; e->mode = mode; - memcpy(e->sha1, sha1, 20); + hashcpy(e->sha1, sha1); } - memcpy(root->sha1, null_sha1, 20); + hashclr(root->sha1); return 1; } @@ -989,7 +989,7 @@ static int tree_content_remove(struct tree_entry *root, const char *p) if (tree_content_remove(e, slash1 + 1)) { if (!e->tree->entry_count) goto del_entry; - memcpy(root->sha1, null_sha1, 20); + hashclr(root->sha1); return 1; } return 0; @@ -1002,7 +1002,7 @@ del_entry: t->entries[i-1] = t->entries[i]; t->entry_count--; release_tree_entry(e); - memcpy(root->sha1, null_sha1, 20); + hashclr(root->sha1); return 1; } @@ -1054,7 +1054,7 @@ static int oecmp (const void *_a, const void *_b) { struct object_entry *a = *((struct object_entry**)_a); struct object_entry *b = *((struct object_entry**)_b); - return memcmp(a->sha1, b->sha1, sizeof(a->sha1)); + return hashcmp(a->sha1, b->sha1); } static void write_index(const char *idx_name) @@ -1359,8 +1359,8 @@ static void cmd_from(struct branch *b) if (b == s) die("Can't create a branch from itself: %s", b->name); else if (s) { - memcpy(b->sha1, s->sha1, 20); - memcpy(b->branch_tree.sha1, s->branch_tree.sha1, 20); + hashcpy(b->sha1, s->sha1); + hashcpy(b->branch_tree.sha1, s->branch_tree.sha1); } else if (*from == ':') { unsigned long idnum = strtoul(from + 1, NULL, 10); struct object_entry *oe = find_mark(idnum); @@ -1368,7 +1368,7 @@ static void cmd_from(struct branch *b) char *buf; if (oe->type != OBJ_COMMIT) die("Mark :%lu not a commit", idnum); - memcpy(b->sha1, oe->sha1, 20); + hashcpy(b->sha1, oe->sha1); buf = unpack_entry(oe->offset, &size); if (!buf || size < 46) die("Not a valid commit: %s", from); @@ -1377,8 +1377,8 @@ static void cmd_from(struct branch *b) die("The commit %s is corrupt", sha1_to_hex(b->sha1)); free(buf); } else if (!get_sha1(from, b->sha1)) { - if (!memcmp(b->sha1, null_sha1, 20)) - memcpy(b->branch_tree.sha1, null_sha1, 20); + if (is_null_sha1(b->sha1)) + hashclr(b->branch_tree.sha1); else { unsigned long size; char *buf; @@ -1467,7 +1467,7 @@ static void cmd_new_commit() : 2 * strlen(committer))); sp = body; sp += sprintf(sp, "tree %s\n", sha1_to_hex(b->branch_tree.sha1)); - if (memcmp(b->sha1, null_sha1, 20)) + if (!is_null_sha1(b->sha1)) sp += sprintf(sp, "parent %s\n", sha1_to_hex(b->sha1)); if (author) sp += sprintf(sp, "%s\n", author); @@ -1547,13 +1547,13 @@ static void cmd_new_tag() s = lookup_branch(from); if (s) { - memcpy(sha1, s->sha1, 20); + hashcpy(sha1, s->sha1); } else if (*from == ':') { from_mark = strtoul(from + 1, NULL, 10); struct object_entry *oe = find_mark(from_mark); if (oe->type != OBJ_COMMIT) die("Mark :%lu not a commit", from_mark); - memcpy(sha1, oe->sha1, 20); + hashcpy(sha1, oe->sha1); } else if (!get_sha1(from, sha1)) { unsigned long size; char *buf; From 4cabf8583f934260697a065186f3dce135834ede Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Aug 2006 12:22:50 -0400 Subject: [PATCH 188/548] Implemented tree delta compression in fast-import. We now store for every tree entry two modes and two sha1 values; the base (aka "version 0") and the current/new (aka "version 1"). When we generate a tree object we also regenerate the prior version object and use that as our base object for a delta. This strategy saves a significant amount of memory as we can continue to use the atom pool for file/directory names and only increases each tree entry by an additional 24 bytes of memory. Branches should automatically delta against their ancestor tree, unless the ancestor tree is already at the delta chain limit. Signed-off-by: Shawn O. Pearce --- fast-import.c | 228 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 159 insertions(+), 69 deletions(-) diff --git a/fast-import.c b/fast-import.c index b1b2382560..6b01120415 100644 --- a/fast-import.c +++ b/fast-import.c @@ -132,7 +132,7 @@ struct mark_set struct last_object { void *data; - unsigned int len; + unsigned long len; unsigned int depth; unsigned char sha1[20]; }; @@ -157,14 +157,18 @@ struct tree_entry { struct tree_content *tree; struct atom_str* name; - unsigned int mode; - unsigned char sha1[20]; + struct tree_entry_ms + { + unsigned int mode; + unsigned char sha1[20]; + } versions[2]; }; struct tree_content { unsigned int entry_capacity; /* must match avail_tree_content */ unsigned int entry_count; + unsigned int delta_depth; struct tree_entry *entries[FLEX_ARRAY]; /* more */ }; @@ -203,6 +207,7 @@ static unsigned long duplicate_count; static unsigned long marks_set_count; static unsigned long object_count_by_type[9]; static unsigned long duplicate_count_by_type[9]; +static unsigned long delta_count_by_type[9]; /* Memory pools */ static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool); @@ -224,7 +229,7 @@ static unsigned long pack_mlen = 128*1024*1024; static unsigned long page_size; /* Table of objects we've written. */ -static unsigned int object_entry_alloc = 1000; +static unsigned int object_entry_alloc = 5000; static struct object_entry_pool *blocks; static struct object_entry *object_table[1 << 16]; static struct mark_set *marks; @@ -486,6 +491,7 @@ static struct tree_content* new_tree_content(unsigned int cnt) t = (struct tree_content*)f; t->entry_count = 0; + t->delta_depth = 0; return t; } @@ -512,6 +518,7 @@ static struct tree_content* grow_tree_content( { struct tree_content *r = new_tree_content(t->entry_count + amt); r->entry_count = t->entry_count; + r->delta_depth = t->delta_depth; memcpy(r->entries,t->entries,t->entry_count*sizeof(t->entries[0])); release_tree_content(t); return r; @@ -642,6 +649,7 @@ static int store_object( deflateInit(&s, zlib_compression_level); if (delta) { + delta_count_by_type[type]++; last->depth++; s.next_in = delta; s.avail_in = deltalen; @@ -755,11 +763,14 @@ static void *unpack_non_delta_entry(unsigned long o, unsigned long sz) return result; } -static void *unpack_entry(unsigned long offset, unsigned long *sizep); +static void *unpack_entry(unsigned long offset, + unsigned long *sizep, + unsigned int *delta_depth); static void *unpack_delta_entry(unsigned long offset, unsigned long delta_size, - unsigned long *sizep) + unsigned long *sizep, + unsigned int *delta_depth) { struct object_entry *base_oe; unsigned char *base_sha1; @@ -770,7 +781,7 @@ static void *unpack_delta_entry(unsigned long offset, base_oe = find_object(base_sha1); if (!base_oe) die("I'm broken; I can't find a base I know must be here."); - base = unpack_entry(base_oe->offset, &base_size); + base = unpack_entry(base_oe->offset, &base_size, delta_depth); delta_data = unpack_non_delta_entry(offset + 20, delta_size); result = patch_delta(base, base_size, delta_data, delta_size, @@ -780,10 +791,13 @@ static void *unpack_delta_entry(unsigned long offset, free(delta_data); free(base); *sizep = result_size; + (*delta_depth)++; return result; } -static void *unpack_entry(unsigned long offset, unsigned long *sizep) +static void *unpack_entry(unsigned long offset, + unsigned long *sizep, + unsigned int *delta_depth) { unsigned long size; enum object_type kind; @@ -791,12 +805,13 @@ static void *unpack_entry(unsigned long offset, unsigned long *sizep) offset = unpack_object_header(offset, &kind, &size); switch (kind) { case OBJ_DELTA: - return unpack_delta_entry(offset, size, sizep); + return unpack_delta_entry(offset, size, sizep, delta_depth); case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: case OBJ_TAG: *sizep = size; + *delta_depth = 0; return unpack_non_delta_entry(offset, size); default: die("I created an object I can't read!"); @@ -819,6 +834,7 @@ static const char *get_mode(const char *str, unsigned int *modep) static void load_tree(struct tree_entry *root) { + unsigned char* sha1 = root->versions[1].sha1; struct object_entry *myoe; struct tree_content *t; unsigned long size; @@ -826,19 +842,19 @@ static void load_tree(struct tree_entry *root) const char *c; root->tree = t = new_tree_content(8); - if (is_null_sha1(root->sha1)) + if (is_null_sha1(sha1)) return; - myoe = find_object(root->sha1); + myoe = find_object(sha1); if (myoe) { if (myoe->type != OBJ_TREE) - die("Not a tree: %s", sha1_to_hex(root->sha1)); - buf = unpack_entry(myoe->offset, &size); + die("Not a tree: %s", sha1_to_hex(sha1)); + buf = unpack_entry(myoe->offset, &size, &t->delta_depth); } else { char type[20]; - buf = read_sha1_file(root->sha1, type, &size); + buf = read_sha1_file(sha1, type, &size); if (!buf || strcmp(type, tree_type)) - die("Can't load tree %s", sha1_to_hex(root->sha1)); + die("Can't load tree %s", sha1_to_hex(sha1)); } c = buf; @@ -850,56 +866,116 @@ static void load_tree(struct tree_entry *root) t->entries[t->entry_count++] = e; e->tree = NULL; - c = get_mode(c, &e->mode); + c = get_mode(c, &e->versions[1].mode); if (!c) - die("Corrupt mode in %s", sha1_to_hex(root->sha1)); + die("Corrupt mode in %s", sha1_to_hex(sha1)); + e->versions[0].mode = e->versions[1].mode; e->name = to_atom(c, strlen(c)); c += e->name->str_len + 1; - hashcpy(e->sha1, c); + hashcpy(e->versions[0].sha1, (unsigned char*)c); + hashcpy(e->versions[1].sha1, (unsigned char*)c); c += 20; } free(buf); } -static int tecmp (const void *_a, const void *_b) +static int tecmp0 (const void *_a, const void *_b) { struct tree_entry *a = *((struct tree_entry**)_a); struct tree_entry *b = *((struct tree_entry**)_b); return base_name_compare( - a->name->str_dat, a->name->str_len, a->mode, - b->name->str_dat, b->name->str_len, b->mode); + a->name->str_dat, a->name->str_len, a->versions[0].mode, + b->name->str_dat, b->name->str_len, b->versions[0].mode); +} + +static int tecmp1 (const void *_a, const void *_b) +{ + struct tree_entry *a = *((struct tree_entry**)_a); + struct tree_entry *b = *((struct tree_entry**)_b); + return base_name_compare( + a->name->str_dat, a->name->str_len, a->versions[1].mode, + b->name->str_dat, b->name->str_len, b->versions[1].mode); +} + +static void* mktree(struct tree_content *t, int v, unsigned long *szp) +{ + size_t maxlen = 0; + unsigned int i; + char *buf, *c; + + if (!v) + qsort(t->entries,t->entry_count,sizeof(t->entries[0]),tecmp0); + else + qsort(t->entries,t->entry_count,sizeof(t->entries[0]),tecmp1); + + for (i = 0; i < t->entry_count; i++) { + if (t->entries[i]->versions[v].mode) + maxlen += t->entries[i]->name->str_len + 34; + } + + buf = c = xmalloc(maxlen); + for (i = 0; i < t->entry_count; i++) { + struct tree_entry *e = t->entries[i]; + if (!e->versions[v].mode) + continue; + c += sprintf(c, "%o", e->versions[v].mode); + *c++ = ' '; + strcpy(c, e->name->str_dat); + c += e->name->str_len + 1; + hashcpy((unsigned char*)c, e->versions[v].sha1); + c += 20; + } + + *szp = c - buf; + return buf; } static void store_tree(struct tree_entry *root) { struct tree_content *t = root->tree; - unsigned int i; - size_t maxlen; - char *buf, *c; + unsigned int i, j, del; + unsigned long vers1len; + void **vers1dat; + struct last_object lo; - if (!is_null_sha1(root->sha1)) + if (!is_null_sha1(root->versions[1].sha1)) return; - maxlen = 0; for (i = 0; i < t->entry_count; i++) { - maxlen += t->entries[i]->name->str_len + 34; if (t->entries[i]->tree) store_tree(t->entries[i]); } - qsort(t->entries, t->entry_count, sizeof(t->entries[0]), tecmp); - buf = c = xmalloc(maxlen); - for (i = 0; i < t->entry_count; i++) { - struct tree_entry *e = t->entries[i]; - c += sprintf(c, "%o", e->mode); - *c++ = ' '; - strcpy(c, e->name->str_dat); - c += e->name->str_len + 1; - hashcpy(c, e->sha1); - c += 20; + if (is_null_sha1(root->versions[0].sha1) + || !find_object(root->versions[0].sha1)) { + lo.data = NULL; + lo.depth = 0; + } else { + lo.data = mktree(t, 0, &lo.len); + lo.depth = t->delta_depth; + hashcpy(lo.sha1, root->versions[0].sha1); } - store_object(OBJ_TREE, buf, c - buf, NULL, root->sha1, 0); - free(buf); + vers1dat = mktree(t, 1, &vers1len); + + store_object(OBJ_TREE, vers1dat, vers1len, + &lo, root->versions[1].sha1, 0); + /* note: lo.dat (if created) was freed by store_object */ + free(vers1dat); + + t->delta_depth = lo.depth; + hashcpy(root->versions[0].sha1, root->versions[1].sha1); + for (i = 0, j = 0, del = 0; i < t->entry_count; i++) { + struct tree_entry *e = t->entries[i]; + if (e->versions[1].mode) { + e->versions[0].mode = e->versions[1].mode; + hashcpy(e->versions[0].sha1, e->versions[1].sha1); + t->entries[j++] = e; + } else { + release_tree_entry(e); + del++; + } + } + t->entry_count -= del; } static int tree_content_set( @@ -923,25 +999,26 @@ static int tree_content_set( e = t->entries[i]; if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) { if (!slash1) { - if (e->mode == mode && !hashcmp(e->sha1, sha1)) + if (e->versions[1].mode == mode + && !hashcmp(e->versions[1].sha1, sha1)) return 0; - e->mode = mode; - hashcpy(e->sha1, sha1); + e->versions[1].mode = mode; + hashcpy(e->versions[1].sha1, sha1); if (e->tree) { release_tree_content_recursive(e->tree); e->tree = NULL; } - hashclr(root->sha1); + hashclr(root->versions[1].sha1); return 1; } - if (!S_ISDIR(e->mode)) { + if (!S_ISDIR(e->versions[1].mode)) { e->tree = new_tree_content(8); - e->mode = S_IFDIR; + e->versions[1].mode = S_IFDIR; } if (!e->tree) load_tree(e); if (tree_content_set(e, slash1 + 1, sha1, mode)) { - hashclr(root->sha1); + hashclr(root->versions[1].sha1); return 1; } return 0; @@ -952,17 +1029,19 @@ static int tree_content_set( root->tree = t = grow_tree_content(t, 8); e = new_tree_entry(); e->name = to_atom(p, n); + e->versions[0].mode = 0; + hashclr(e->versions[0].sha1); t->entries[t->entry_count++] = e; if (slash1) { e->tree = new_tree_content(8); - e->mode = S_IFDIR; + e->versions[1].mode = S_IFDIR; tree_content_set(e, slash1 + 1, sha1, mode); } else { e->tree = NULL; - e->mode = mode; - hashcpy(e->sha1, sha1); + e->versions[1].mode = mode; + hashcpy(e->versions[1].sha1, sha1); } - hashclr(root->sha1); + hashclr(root->versions[1].sha1); return 1; } @@ -982,14 +1061,14 @@ static int tree_content_remove(struct tree_entry *root, const char *p) for (i = 0; i < t->entry_count; i++) { e = t->entries[i]; if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) { - if (!slash1 || !S_ISDIR(e->mode)) + if (!slash1 || !S_ISDIR(e->versions[1].mode)) goto del_entry; if (!e->tree) load_tree(e); if (tree_content_remove(e, slash1 + 1)) { if (!e->tree->entry_count) goto del_entry; - hashclr(root->sha1); + hashclr(root->versions[1].sha1); return 1; } return 0; @@ -998,11 +1077,13 @@ static int tree_content_remove(struct tree_entry *root, const char *p) return 0; del_entry: - for (i++; i < t->entry_count; i++) - t->entries[i-1] = t->entries[i]; - t->entry_count--; - release_tree_entry(e); - hashclr(root->sha1); + if (e->tree) { + release_tree_content_recursive(e->tree); + e->tree = NULL; + } + e->versions[1].mode = 0; + hashclr(e->versions[1].sha1); + hashclr(root->versions[1].sha1); return 1; } @@ -1359,27 +1440,33 @@ static void cmd_from(struct branch *b) if (b == s) die("Can't create a branch from itself: %s", b->name); else if (s) { + unsigned char *t = s->branch_tree.versions[1].sha1; hashcpy(b->sha1, s->sha1); - hashcpy(b->branch_tree.sha1, s->branch_tree.sha1); + hashcpy(b->branch_tree.versions[0].sha1, t); + hashcpy(b->branch_tree.versions[1].sha1, t); } else if (*from == ':') { unsigned long idnum = strtoul(from + 1, NULL, 10); struct object_entry *oe = find_mark(idnum); unsigned long size; + unsigned int depth; char *buf; if (oe->type != OBJ_COMMIT) die("Mark :%lu not a commit", idnum); hashcpy(b->sha1, oe->sha1); - buf = unpack_entry(oe->offset, &size); + buf = unpack_entry(oe->offset, &size, &depth); if (!buf || size < 46) die("Not a valid commit: %s", from); if (memcmp("tree ", buf, 5) - || get_sha1_hex(buf + 5, b->branch_tree.sha1)) + || get_sha1_hex(buf + 5, b->branch_tree.versions[1].sha1)) die("The commit %s is corrupt", sha1_to_hex(b->sha1)); free(buf); + hashcpy(b->branch_tree.versions[0].sha1, + b->branch_tree.versions[1].sha1); } else if (!get_sha1(from, b->sha1)) { - if (is_null_sha1(b->sha1)) - hashclr(b->branch_tree.sha1); - else { + if (is_null_sha1(b->sha1)) { + hashclr(b->branch_tree.versions[0].sha1); + hashclr(b->branch_tree.versions[1].sha1); + } else { unsigned long size; char *buf; @@ -1388,9 +1475,11 @@ static void cmd_from(struct branch *b) if (!buf || size < 46) die("Not a valid commit: %s", from); if (memcmp("tree ", buf, 5) - || get_sha1_hex(buf + 5, b->branch_tree.sha1)) + || get_sha1_hex(buf + 5, b->branch_tree.versions[1].sha1)) die("The commit %s is corrupt", sha1_to_hex(b->sha1)); free(buf); + hashcpy(b->branch_tree.versions[0].sha1, + b->branch_tree.versions[1].sha1); } } else die("Invalid ref name or SHA1 expression: %s", from); @@ -1466,7 +1555,8 @@ static void cmd_new_commit() ? strlen(author) + strlen(committer) : 2 * strlen(committer))); sp = body; - sp += sprintf(sp, "tree %s\n", sha1_to_hex(b->branch_tree.sha1)); + sp += sprintf(sp, "tree %s\n", + sha1_to_hex(b->branch_tree.versions[1].sha1)); if (!is_null_sha1(b->sha1)) sp += sprintf(sp, "parent %s\n", sha1_to_hex(b->sha1)); if (author) @@ -1722,10 +1812,10 @@ int main(int argc, const char **argv) fprintf(stderr, "---------------------------------------------------\n"); fprintf(stderr, "Alloc'd objects: %10lu (%10lu overflow )\n", alloc_count, alloc_count - est_obj_cnt); fprintf(stderr, "Total objects: %10lu (%10lu duplicates)\n", object_count, duplicate_count); - fprintf(stderr, " blobs : %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB]); - fprintf(stderr, " trees : %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE]); - fprintf(stderr, " commits: %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT]); - fprintf(stderr, " tags : %10lu (%10lu duplicates)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG]); + fprintf(stderr, " blobs : %10lu (%10lu duplicates %10lu deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]); + fprintf(stderr, " trees : %10lu (%10lu duplicates %10lu deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]); + fprintf(stderr, " commits: %10lu (%10lu duplicates %10lu deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]); + fprintf(stderr, " tags : %10lu (%10lu duplicates %10lu deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]); fprintf(stderr, "Total branches: %10lu (%10lu loads )\n", branch_count, branch_load_count); fprintf(stderr, " marks: %10u (%10lu unique )\n", (1 << marks->shift) * 1024, marks_set_count); fprintf(stderr, " atoms: %10u\n", atom_cnt); From e2eb469d1ff9595882c8329ad415b1d7246769d0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Aug 2006 13:02:51 -0400 Subject: [PATCH 189/548] Recycle data buffers for tree generation in fast-import. We only ever generate at most two tree streams at a time. Since most trees are around the same size we can simply recycle the buffers from one tree generation to the next rather than constantly xmalloc'ing and free'ing them. This should perform slightly better when handling a large number of trees as malloc has less work to do. Signed-off-by: Shawn O. Pearce --- fast-import.c | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/fast-import.c b/fast-import.c index 6b01120415..8d15a05739 100644 --- a/fast-import.c +++ b/fast-import.c @@ -134,6 +134,7 @@ struct last_object void *data; unsigned long len; unsigned int depth; + int no_free; unsigned char sha1[20]; }; @@ -195,6 +196,12 @@ struct tag unsigned char sha1[20]; }; +struct dbuf +{ + void *buffer; + size_t capacity; +}; + /* Stats and misc. counters */ static unsigned long max_depth = 10; @@ -243,6 +250,8 @@ static unsigned int tree_entry_alloc = 1000; static void *avail_tree_entry; static unsigned int avail_tree_table_sz = 100; static struct avail_tree_content **avail_tree_table; +static struct dbuf old_tree; +static struct dbuf new_tree; /* Branch data */ static unsigned long max_active_branches = 5; @@ -680,7 +689,7 @@ static int store_object( if (delta) free(delta); if (last) { - if (last->data) + if (last->data && !last->no_free) free(last->data); last->data = dat; last->len = datlen; @@ -897,11 +906,14 @@ static int tecmp1 (const void *_a, const void *_b) b->name->str_dat, b->name->str_len, b->versions[1].mode); } -static void* mktree(struct tree_content *t, int v, unsigned long *szp) +static void mktree(struct tree_content *t, + int v, + unsigned long *szp, + struct dbuf *b) { size_t maxlen = 0; unsigned int i; - char *buf, *c; + char *c; if (!v) qsort(t->entries,t->entry_count,sizeof(t->entries[0]),tecmp0); @@ -913,7 +925,16 @@ static void* mktree(struct tree_content *t, int v, unsigned long *szp) maxlen += t->entries[i]->name->str_len + 34; } - buf = c = xmalloc(maxlen); + if (b->buffer) { + if (b->capacity < maxlen) + b->capacity = ((maxlen / 1024) + 1) * 1024; + b->buffer = xrealloc(b->buffer, b->capacity); + } else { + b->capacity = ((maxlen / 1024) + 1) * 1024; + b->buffer = xmalloc(b->capacity); + } + + c = b->buffer; for (i = 0; i < t->entry_count; i++) { struct tree_entry *e = t->entries[i]; if (!e->versions[v].mode) @@ -925,17 +946,14 @@ static void* mktree(struct tree_content *t, int v, unsigned long *szp) hashcpy((unsigned char*)c, e->versions[v].sha1); c += 20; } - - *szp = c - buf; - return buf; + *szp = c - (char*)b->buffer; } static void store_tree(struct tree_entry *root) { struct tree_content *t = root->tree; unsigned int i, j, del; - unsigned long vers1len; - void **vers1dat; + unsigned long new_len; struct last_object lo; if (!is_null_sha1(root->versions[1].sha1)) @@ -951,16 +969,16 @@ static void store_tree(struct tree_entry *root) lo.data = NULL; lo.depth = 0; } else { - lo.data = mktree(t, 0, &lo.len); + mktree(t, 0, &lo.len, &old_tree); + lo.data = old_tree.buffer; lo.depth = t->delta_depth; + lo.no_free = 1; hashcpy(lo.sha1, root->versions[0].sha1); } - vers1dat = mktree(t, 1, &vers1len); + mktree(t, 1, &new_len, &new_tree); - store_object(OBJ_TREE, vers1dat, vers1len, + store_object(OBJ_TREE, new_tree.buffer, new_len, &lo, root->versions[1].sha1, 0); - /* note: lo.dat (if created) was freed by store_object */ - free(vers1dat); t->delta_depth = lo.depth; hashcpy(root->versions[0].sha1, root->versions[1].sha1); From 243f801d1d08753cd4eff2a23e245f7575c37ad5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Aug 2006 13:15:48 -0400 Subject: [PATCH 190/548] Reuse the same buffer for all commits/tags in fast-import. Since most commits and tag objects are around the same size and we only generate one at a time we can reuse the same buffer rather than xmalloc'ing and free'ing the buffer every time we generate a commit. Signed-off-by: Shawn O. Pearce --- fast-import.c | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/fast-import.c b/fast-import.c index 8d15a05739..3d99102005 100644 --- a/fast-import.c +++ b/fast-import.c @@ -267,6 +267,7 @@ static struct tag *last_tag; /* Input stream parsing */ static struct strbuf command_buf; static unsigned long next_mark; +static struct dbuf new_data; static FILE* branch_log; @@ -381,6 +382,17 @@ static char* pool_strdup(const char *s) return r; } +static void size_dbuf(struct dbuf *b, size_t maxlen) +{ + if (b->buffer) { + if (b->capacity >= maxlen) + return; + free(b->buffer); + } + b->capacity = ((maxlen / 1024) + 1) * 1024; + b->buffer = xmalloc(b->capacity); +} + static void insert_mark(unsigned long idnum, struct object_entry *oe) { struct mark_set *s = marks; @@ -925,15 +937,7 @@ static void mktree(struct tree_content *t, maxlen += t->entries[i]->name->str_len + 34; } - if (b->buffer) { - if (b->capacity < maxlen) - b->capacity = ((maxlen / 1024) + 1) * 1024; - b->buffer = xrealloc(b->buffer, b->capacity); - } else { - b->capacity = ((maxlen / 1024) + 1) * 1024; - b->buffer = xmalloc(b->capacity); - } - + size_dbuf(b, maxlen); c = b->buffer; for (i = 0; i < t->entry_count; i++) { struct tree_entry *e = t->entries[i]; @@ -1515,7 +1519,6 @@ static void cmd_new_commit() char *sp; char *author = NULL; char *committer = NULL; - char *body; /* Obtain the branch name from the rest of our command */ sp = strchr(command_buf.buf, ' ') + 1; @@ -1568,11 +1571,11 @@ static void cmd_new_commit() /* build the tree and the commit */ store_tree(&b->branch_tree); - body = xmalloc(97 + msglen + size_dbuf(&new_data, 97 + msglen + (author ? strlen(author) + strlen(committer) : 2 * strlen(committer))); - sp = body; + sp = new_data.buffer; sp += sprintf(sp, "tree %s\n", sha1_to_hex(b->branch_tree.versions[1].sha1)); if (!is_null_sha1(b->sha1)) @@ -1589,8 +1592,9 @@ static void cmd_new_commit() free(committer); free(msg); - store_object(OBJ_COMMIT, body, sp - body, NULL, b->sha1, next_mark); - free(body); + store_object(OBJ_COMMIT, + new_data.buffer, sp - (char*)new_data.buffer, + NULL, b->sha1, next_mark); b->last_commit = object_count_by_type[OBJ_COMMIT]; if (branch_log) { @@ -1616,7 +1620,6 @@ static void cmd_new_tag() struct branch *s; void *msg; size_t msglen; - char *body; struct tag *t; unsigned long from_mark = 0; unsigned char sha1[20]; @@ -1688,8 +1691,8 @@ static void cmd_new_tag() msg = cmd_data(&msglen); /* build the tag object */ - body = xmalloc(67 + strlen(t->name) + strlen(tagger) + msglen); - sp = body; + size_dbuf(&new_data, 67+strlen(t->name)+strlen(tagger)+msglen); + sp = new_data.buffer; sp += sprintf(sp, "object %s\n", sha1_to_hex(sha1)); sp += sprintf(sp, "type %s\n", type_names[OBJ_COMMIT]); sp += sprintf(sp, "tag %s\n", t->name); @@ -1699,8 +1702,8 @@ static void cmd_new_tag() free(tagger); free(msg); - store_object(OBJ_TAG, body, sp - body, NULL, t->sha1, 0); - free(body); + store_object(OBJ_TAG, new_data.buffer, sp - (char*)new_data.buffer, + NULL, t->sha1, 0); if (branch_log) { int need_dq = quote_c_style(t->name, NULL, NULL, 0); @@ -1749,7 +1752,7 @@ int main(int argc, const char **argv) { const char *base_name; int i; - unsigned long est_obj_cnt = 1000; + unsigned long est_obj_cnt = object_entry_alloc; char *pack_name; char *idx_name; struct stat sb; From 23bc886c966b4362555b61f33c6eef71552e4d0e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Aug 2006 13:54:01 -0400 Subject: [PATCH 191/548] Replace ywrite in fast-import with the standard write_or_die. Signed-off-by: Shawn O. Pearce --- fast-import.c | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/fast-import.c b/fast-import.c index 3d99102005..f94f307ee6 100644 --- a/fast-import.c +++ b/fast-import.c @@ -586,19 +586,6 @@ static void yread(int fd, void *buffer, size_t length) } } -static void ywrite(int fd, void *buffer, size_t length) -{ - ssize_t ret = 0; - while (ret < length) { - ssize_t size = xwrite(fd, (char *) buffer + ret, length - ret); - if (!size) - die("Write to descriptor %i: end of file", fd); - if (size < 0) - die("Write to descriptor %i: %s", fd, strerror(errno)); - ret += size; - } -} - static size_t encode_header( enum object_type type, size_t size, @@ -675,8 +662,8 @@ static int store_object( s.next_in = delta; s.avail_in = deltalen; hdrlen = encode_header(OBJ_DELTA, deltalen, hdr); - ywrite(pack_fd, hdr, hdrlen); - ywrite(pack_fd, last->sha1, sizeof(sha1)); + write_or_die(pack_fd, hdr, hdrlen); + write_or_die(pack_fd, last->sha1, sizeof(sha1)); pack_size += hdrlen + sizeof(sha1); } else { if (last) @@ -684,7 +671,7 @@ static int store_object( s.next_in = dat; s.avail_in = datlen; hdrlen = encode_header(type, datlen, hdr); - ywrite(pack_fd, hdr, hdrlen); + write_or_die(pack_fd, hdr, hdrlen); pack_size += hdrlen; } @@ -694,7 +681,7 @@ static int store_object( /* nothing */; deflateEnd(&s); - ywrite(pack_fd, out, s.total_out); + write_or_die(pack_fd, out, s.total_out); pack_size += s.total_out; free(out); @@ -1117,7 +1104,7 @@ static void init_pack_header() hdr.hdr_version = htonl(2); hdr.hdr_entries = 0; - ywrite(pack_fd, &hdr, sizeof(hdr)); + write_or_die(pack_fd, &hdr, sizeof(hdr)); pack_size = sizeof(hdr); } @@ -1138,7 +1125,7 @@ static void fixup_header_footer() cnt = htonl(object_count); SHA1_Update(&c, &cnt, 4); - ywrite(pack_fd, &cnt, 4); + write_or_die(pack_fd, &cnt, 4); buf = xmalloc(128 * 1024); for (;;) { @@ -1150,7 +1137,7 @@ static void fixup_header_footer() free(buf); SHA1_Final(pack_sha1, &c); - ywrite(pack_fd, pack_sha1, sizeof(pack_sha1)); + write_or_die(pack_fd, pack_sha1, sizeof(pack_sha1)); } static int oecmp (const void *_a, const void *_b) From b54d6422b1a277ee905819e01020f5690196a999 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Aug 2006 21:43:04 -0400 Subject: [PATCH 192/548] Correct tree corruption problems in fast-import. The new tree delta implementation caused blob SHA1s to be used instead of a tree SHA1 when a tree was written out. This really only appeared to happen when converting an existing file to a tree, but may have been possible in some other situations. Signed-off-by: Shawn O. Pearce --- fast-import.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/fast-import.c b/fast-import.c index f94f307ee6..34ff946fa3 100644 --- a/fast-import.c +++ b/fast-import.c @@ -956,7 +956,8 @@ static void store_tree(struct tree_entry *root) } if (is_null_sha1(root->versions[0].sha1) - || !find_object(root->versions[0].sha1)) { + || !find_object(root->versions[0].sha1) + || !S_ISDIR(root->versions[0].mode)) { lo.data = NULL; lo.depth = 0; } else { @@ -1023,6 +1024,7 @@ static int tree_content_set( if (!S_ISDIR(e->versions[1].mode)) { e->tree = new_tree_content(8); e->versions[1].mode = S_IFDIR; + hashclr(e->versions[1].sha1); } if (!e->tree) load_tree(e); @@ -1044,6 +1046,7 @@ static int tree_content_set( if (slash1) { e->tree = new_tree_content(8); e->versions[1].mode = S_IFDIR; + hashclr(e->versions[1].sha1); tree_content_set(e, slash1 + 1, sha1, mode); } else { e->tree = NULL; @@ -1075,10 +1078,13 @@ static int tree_content_remove(struct tree_entry *root, const char *p) if (!e->tree) load_tree(e); if (tree_content_remove(e, slash1 + 1)) { - if (!e->tree->entry_count) - goto del_entry; - hashclr(root->versions[1].sha1); - return 1; + for (n = 0; n < e->tree->entry_count; n++) { + if (e->tree->entries[n]->versions[1].mode) { + hashclr(root->versions[1].sha1); + return 1; + } + } + goto del_entry; } return 0; } From 8a8c55ea709d26ca397d6588e85579339885f507 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Aug 2006 22:06:13 -0400 Subject: [PATCH 193/548] Additional fast-import tree delta corruption cleanups. Signed-off-by: Shawn O. Pearce --- fast-import.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/fast-import.c b/fast-import.c index 34ff946fa3..e35a89f6cd 100644 --- a/fast-import.c +++ b/fast-import.c @@ -478,6 +478,8 @@ static struct branch* new_branch(const char *name) b = pool_calloc(1, sizeof(struct branch)); b->name = pool_strdup(name); b->table_next_branch = branch_table[hc]; + b->branch_tree.versions[0].mode = S_IFDIR; + b->branch_tree.versions[1].mode = S_IFDIR; branch_table[hc] = b; branch_count++; return b; @@ -955,9 +957,9 @@ static void store_tree(struct tree_entry *root) store_tree(t->entries[i]); } - if (is_null_sha1(root->versions[0].sha1) - || !find_object(root->versions[0].sha1) - || !S_ISDIR(root->versions[0].mode)) { + if (!S_ISDIR(root->versions[0].mode) + || is_null_sha1(root->versions[0].sha1) + || !find_object(root->versions[0].sha1)) { lo.data = NULL; lo.depth = 0; } else { @@ -967,13 +969,12 @@ static void store_tree(struct tree_entry *root) lo.no_free = 1; hashcpy(lo.sha1, root->versions[0].sha1); } - mktree(t, 1, &new_len, &new_tree); + mktree(t, 1, &new_len, &new_tree); store_object(OBJ_TREE, new_tree.buffer, new_len, &lo, root->versions[1].sha1, 0); t->delta_depth = lo.depth; - hashcpy(root->versions[0].sha1, root->versions[1].sha1); for (i = 0, j = 0, del = 0; i < t->entry_count; i++) { struct tree_entry *e = t->entries[i]; if (e->versions[1].mode) { @@ -1024,7 +1025,6 @@ static int tree_content_set( if (!S_ISDIR(e->versions[1].mode)) { e->tree = new_tree_content(8); e->versions[1].mode = S_IFDIR; - hashclr(e->versions[1].sha1); } if (!e->tree) load_tree(e); @@ -1046,7 +1046,6 @@ static int tree_content_set( if (slash1) { e->tree = new_tree_content(8); e->versions[1].mode = S_IFDIR; - hashclr(e->versions[1].sha1); tree_content_set(e, slash1 + 1, sha1, mode); } else { e->tree = NULL; @@ -1564,6 +1563,8 @@ static void cmd_new_commit() /* build the tree and the commit */ store_tree(&b->branch_tree); + hashcpy(b->branch_tree.versions[0].sha1, + b->branch_tree.versions[1].sha1); size_dbuf(&new_data, 97 + msglen + (author ? strlen(author) + strlen(committer) @@ -1823,9 +1824,9 @@ int main(int argc, const char **argv) fclose(branch_log); fprintf(stderr, "%s statistics:\n", argv[0]); - fprintf(stderr, "---------------------------------------------------\n"); + fprintf(stderr, "---------------------------------------------------------------------\n"); fprintf(stderr, "Alloc'd objects: %10lu (%10lu overflow )\n", alloc_count, alloc_count - est_obj_cnt); - fprintf(stderr, "Total objects: %10lu (%10lu duplicates)\n", object_count, duplicate_count); + fprintf(stderr, "Total objects: %10lu (%10lu duplicates )\n", object_count, duplicate_count); fprintf(stderr, " blobs : %10lu (%10lu duplicates %10lu deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]); fprintf(stderr, " trees : %10lu (%10lu duplicates %10lu deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]); fprintf(stderr, " commits: %10lu (%10lu duplicates %10lu deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]); @@ -1837,12 +1838,11 @@ int main(int argc, const char **argv) fprintf(stderr, " pools: %10lu KiB\n", total_allocd/1024); fprintf(stderr, " objects: %10lu KiB\n", (alloc_count*sizeof(struct object_entry))/1024); fprintf(stderr, "Pack remaps: %10lu\n", remap_count); - fprintf(stderr, "---------------------------------------------------\n"); - stat(pack_name, &sb); fprintf(stderr, "Pack size: %10lu KiB\n", (unsigned long)(sb.st_size/1024)); stat(idx_name, &sb); fprintf(stderr, "Index size: %10lu KiB\n", (unsigned long)(sb.st_size/1024)); + fprintf(stderr, "---------------------------------------------------------------------\n"); fprintf(stderr, "\n"); From cacbdd0afb481a6f3019e5e7db98f88e40941fd5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 11 Jan 2007 21:25:01 -0500 Subject: [PATCH 194/548] Fix repository corruption when using marks for modified blobs. Apparently we did not copy the blob SHA1 into the stack variable 'sha1' when a mark is used to refer to a prior blob. This code was not previously tested as the Mozilla CVS -> git-fast-import program always fed us full SHA1s for modified blobs and did not use the mark feature there. Signed-off-by: Shawn O. Pearce --- fast-import.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fast-import.c b/fast-import.c index e35a89f6cd..e9a46c6c3a 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1378,6 +1378,7 @@ static void file_change_m(struct branch *b) if (*p == ':') { char *x; oe = find_mark(strtoul(p + 1, &x, 10)); + hashcpy(sha1, oe->sha1); p = x; } else { if (get_sha1_hex(p, sha1)) From 62b6f48388faf0ac2432a07cfc53aa904c591f8f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 11 Jan 2007 22:21:38 -0500 Subject: [PATCH 195/548] Support creation of merge commits in fast-import. Some importers are able to determine when branch merges occurred within their source data. In these cases they will want to supply the correct commits to fast-import so that a proper merge commit will exist in Git. This is now supported by supplying a 'merge ' command after the commit message and optional from command. A merge is not actually performed by fast-import, its assumed that the frontend performed any sort of merging activity already and that fast-import should simply be storing its result. Signed-off-by: Shawn O. Pearce --- fast-import.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/fast-import.c b/fast-import.c index e9a46c6c3a..15db4b39d1 100644 --- a/fast-import.c +++ b/fast-import.c @@ -20,6 +20,7 @@ Format of STDIN stream: 'committer' sp name '<' email '>' ts tz lf commit_msg ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? + ('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)* file_change* lf; commit_msg ::= data; @@ -202,6 +203,11 @@ struct dbuf size_t capacity; }; +struct hash_list +{ + struct hash_list *next; + unsigned char sha1[20]; +}; /* Stats and misc. counters */ static unsigned long max_depth = 10; @@ -1502,6 +1508,48 @@ static void cmd_from(struct branch *b) read_next_command(); } +static struct hash_list* cmd_merge(unsigned int *count) +{ + struct hash_list *list = NULL, *n, *e; + const char *from, *endp; + char *str_uq; + struct branch *s; + + *count = 0; + while (!strncmp("merge ", command_buf.buf, 6)) { + from = strchr(command_buf.buf, ' ') + 1; + str_uq = unquote_c_style(from, &endp); + if (str_uq) { + if (*endp) + die("Garbage after string in: %s", command_buf.buf); + from = str_uq; + } + + n = xmalloc(sizeof(*n)); + s = lookup_branch(from); + if (s) + hashcpy(n->sha1, s->sha1); + else if (*from == ':') { + unsigned long idnum = strtoul(from + 1, NULL, 10); + struct object_entry *oe = find_mark(idnum); + if (oe->type != OBJ_COMMIT) + die("Mark :%lu not a commit", idnum); + hashcpy(n->sha1, oe->sha1); + } else if (get_sha1(from, n->sha1)) + die("Invalid ref name or SHA1 expression: %s", from); + + n->next = NULL; + if (list) + e->next = n; + else + list = n; + e = n; + *count++; + read_next_command(); + } + return list; +} + static void cmd_new_commit() { struct branch *b; @@ -1512,6 +1560,8 @@ static void cmd_new_commit() char *sp; char *author = NULL; char *committer = NULL; + struct hash_list *merge_list = NULL; + unsigned int merge_count; /* Obtain the branch name from the rest of our command */ sp = strchr(command_buf.buf, ' ') + 1; @@ -1542,6 +1592,7 @@ static void cmd_new_commit() msg = cmd_data(&msglen); read_next_command(); cmd_from(b); + merge_list = cmd_merge(&merge_count); /* ensure the branch is active/loaded */ if (!b->branch_tree.tree || !max_active_branches) { @@ -1567,6 +1618,7 @@ static void cmd_new_commit() hashcpy(b->branch_tree.versions[0].sha1, b->branch_tree.versions[1].sha1); size_dbuf(&new_data, 97 + msglen + + merge_count * 49 + (author ? strlen(author) + strlen(committer) : 2 * strlen(committer))); @@ -1575,6 +1627,12 @@ static void cmd_new_commit() sha1_to_hex(b->branch_tree.versions[1].sha1)); if (!is_null_sha1(b->sha1)) sp += sprintf(sp, "parent %s\n", sha1_to_hex(b->sha1)); + while (merge_list) { + struct hash_list *next = merge_list->next; + sp += sprintf(sp, "parent %s\n", sha1_to_hex(merge_list->sha1)); + free(merge_list); + merge_list = next; + } if (author) sp += sprintf(sp, "%s\n", author); else From 9938ffc53a15c755bbd3894c02492b940ea34c4c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 11 Jan 2007 22:28:39 -0500 Subject: [PATCH 196/548] Allow creating branches without committing in fast-import. Some importers may want to create a branch long before they actually commit to it, or in some cases they may never commit to the branch but they still need the ref to be created in the repository after the import is complete. This extends the 'reset ' command to automatically create a new branch if the supplied reference isn't already known as a branch. While I'm at it I also modified the syntax of the reset command to terminate with an empty line, like commit and tag operate. This just makes the command set more consistent. Signed-off-by: Shawn O. Pearce --- fast-import.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fast-import.c b/fast-import.c index 15db4b39d1..38e24bf6a6 100644 --- a/fast-import.c +++ b/fast-import.c @@ -36,7 +36,9 @@ Format of STDIN stream: tag_msg; tag_msg ::= data; - reset_branch ::= 'reset' sp ref_str lf; + reset_branch ::= 'reset' sp ref_str lf + ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? + lf; # note: the first idnum in a stream should be 1 and subsequent # idnums should not have gaps between values as this will cause @@ -1794,8 +1796,12 @@ static void cmd_reset_branch() b->branch_tree.tree = NULL; } } + else + b = new_branch(sp); if (str_uq) free(str_uq); + read_next_command(); + cmd_from(b); } static const char fast_import_usage[] = From d489bc14919cdd37d3978065591199d21d6719f8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 06:20:23 -0500 Subject: [PATCH 197/548] Improve reuse of sha1_file library within fast-import. Now that the sha1_file.c library routines use the sliding mmap routines to perform efficient access to portions of a packfile I can remove that code from fast-import.c and just invoke it. One benefit is we now have reloading support for any packfile which uses OBJ_OFS_DELTA. Another is we have significantly less code to maintain. This code reuse change *requires* that fast-import generate only an OBJ_OFS_DELTA format packfile, as there is absolutely no index available to perform OBJ_REF_DELTA lookup in while unpacking an object. This is probably reasonable to require as the delta offsets result in smaller packfiles and are faster to unpack, as no index searching is required. Its also only a temporary requirement as users could always repack without offsets before making the import available to older versions of Git. Signed-off-by: Shawn O. Pearce --- fast-import.c | 175 +++++++++----------------------------------------- 1 file changed, 31 insertions(+), 144 deletions(-) diff --git a/fast-import.c b/fast-import.c index 492a8594bf..f0f51a6899 100644 --- a/fast-import.c +++ b/fast-import.c @@ -136,9 +136,9 @@ struct last_object { void *data; unsigned long len; + unsigned long offset; unsigned int depth; - int no_free; - unsigned char sha1[20]; + unsigned no_free:1; }; struct mem_pool @@ -235,13 +235,10 @@ static unsigned int atom_cnt; static struct atom_str **atom_table; /* The .pack file being generated */ +static struct packed_git *pack_data; static int pack_fd; static unsigned long pack_size; static unsigned char pack_sha1[20]; -static unsigned char* pack_base; -static unsigned long pack_moff; -static unsigned long pack_mlen = 128*1024*1024; -static unsigned long page_size; /* Table of objects we've written. */ static unsigned int object_entry_alloc = 5000; @@ -667,14 +664,23 @@ static int store_object( deflateInit(&s, zlib_compression_level); if (delta) { + unsigned long ofs = e->offset - last->offset; + unsigned pos = sizeof(hdr) - 1; + delta_count_by_type[type]++; last->depth++; s.next_in = delta; s.avail_in = deltalen; - hdrlen = encode_header(OBJ_REF_DELTA, deltalen, hdr); + + hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr); write_or_die(pack_fd, hdr, hdrlen); - write_or_die(pack_fd, last->sha1, sizeof(sha1)); - pack_size += hdrlen + sizeof(sha1); + pack_size += hdrlen; + + hdr[pos] = ofs & 127; + while (ofs >>= 7) + hdr[--pos] = 128 | (--ofs & 127); + write_or_die(pack_fd, hdr + pos, sizeof(hdr) - pos); + pack_size += sizeof(hdr) - pos; } else { if (last) last->depth = 0; @@ -701,139 +707,17 @@ static int store_object( if (last->data && !last->no_free) free(last->data); last->data = dat; + last->offset = e->offset; last->len = datlen; - hashcpy(last->sha1, sha1); } return 0; } -static unsigned char* map_pack(unsigned long offset, unsigned int *left) +static void *gfi_unpack_entry(unsigned long ofs, unsigned long *sizep) { - if (offset >= pack_size) - die("object offset outside of pack file"); - if (!pack_base - || offset < pack_moff - || (offset + 20) >= (pack_moff + pack_mlen)) { - if (pack_base) - munmap(pack_base, pack_mlen); - pack_moff = (offset / page_size) * page_size; - pack_base = mmap(NULL,pack_mlen,PROT_READ,MAP_SHARED, - pack_fd,pack_moff); - if (pack_base == MAP_FAILED) - die("Failed to map generated pack: %s", strerror(errno)); - remap_count++; - } - offset -= pack_moff; - if (left) - *left = pack_mlen - offset; - return pack_base + offset; -} - -static unsigned long unpack_object_header(unsigned long offset, - enum object_type *type, - unsigned long *sizep) -{ - unsigned shift; - unsigned char c; - unsigned long size; - - c = *map_pack(offset++, NULL); - *type = (c >> 4) & 7; - size = c & 15; - shift = 4; - while (c & 0x80) { - c = *map_pack(offset++, NULL); - size += (c & 0x7f) << shift; - shift += 7; - } - *sizep = size; - return offset; -} - -static void *unpack_non_delta_entry(unsigned long o, unsigned long sz) -{ - z_stream stream; - unsigned char *result; - - result = xmalloc(sz + 1); - result[sz] = 0; - - memset(&stream, 0, sizeof(stream)); - stream.next_in = map_pack(o, &stream.avail_in); - stream.next_out = result; - stream.avail_out = sz; - - inflateInit(&stream); - for (;;) { - int st = inflate(&stream, Z_FINISH); - if (st == Z_STREAM_END) - break; - if (st == Z_OK || st == Z_BUF_ERROR) { - o = stream.next_in - pack_base + pack_moff; - stream.next_in = map_pack(o, &stream.avail_in); - continue; - } - die("Error %i from zlib during inflate.", st); - } - inflateEnd(&stream); - if (stream.total_out != sz) - die("Error after inflate: sizes mismatch"); - return result; -} - -static void *gfi_unpack_entry(unsigned long offset, - unsigned long *sizep, - unsigned int *delta_depth); - -static void *unpack_delta_entry(unsigned long offset, - unsigned long delta_size, - unsigned long *sizep, - unsigned int *delta_depth) -{ - struct object_entry *base_oe; - unsigned char *base_sha1; - void *delta_data, *base, *result; - unsigned long base_size, result_size; - - base_sha1 = map_pack(offset, NULL); - base_oe = find_object(base_sha1); - if (!base_oe) - die("I'm broken; I can't find a base I know must be here."); - base = gfi_unpack_entry(base_oe->offset, &base_size, delta_depth); - delta_data = unpack_non_delta_entry(offset + 20, delta_size); - result = patch_delta(base, base_size, - delta_data, delta_size, - &result_size); - if (!result) - die("failed to apply delta"); - free(delta_data); - free(base); - *sizep = result_size; - (*delta_depth)++; - return result; -} - -static void *gfi_unpack_entry(unsigned long offset, - unsigned long *sizep, - unsigned int *delta_depth) -{ - unsigned long size; - enum object_type kind; - - offset = unpack_object_header(offset, &kind, &size); - switch (kind) { - case OBJ_REF_DELTA: - return unpack_delta_entry(offset, size, sizep, delta_depth); - case OBJ_COMMIT: - case OBJ_TREE: - case OBJ_BLOB: - case OBJ_TAG: - *sizep = size; - *delta_depth = 0; - return unpack_non_delta_entry(offset, size); - default: - die("I created an object I can't read!"); - } + char type[20]; + pack_data->pack_size = pack_size + 20; + return unpack_entry(pack_data, ofs, type, sizep); } static const char *get_mode(const char *str, unsigned int *modep) @@ -867,7 +751,8 @@ static void load_tree(struct tree_entry *root) if (myoe) { if (myoe->type != OBJ_TREE) die("Not a tree: %s", sha1_to_hex(sha1)); - buf = gfi_unpack_entry(myoe->offset, &size, &t->delta_depth); + t->delta_depth = 0; + buf = gfi_unpack_entry(myoe->offset, &size); } else { char type[20]; buf = read_sha1_file(sha1, type, &size); @@ -956,6 +841,7 @@ static void store_tree(struct tree_entry *root) unsigned int i, j, del; unsigned long new_len; struct last_object lo; + struct object_entry *le; if (!is_null_sha1(root->versions[1].sha1)) return; @@ -965,17 +851,16 @@ static void store_tree(struct tree_entry *root) store_tree(t->entries[i]); } - if (!S_ISDIR(root->versions[0].mode) - || is_null_sha1(root->versions[0].sha1) - || !find_object(root->versions[0].sha1)) { + le = find_object(root->versions[0].sha1); + if (!S_ISDIR(root->versions[0].mode) || !le) { lo.data = NULL; lo.depth = 0; } else { mktree(t, 0, &lo.len, &old_tree); lo.data = old_tree.buffer; + lo.offset = le->offset; lo.depth = t->delta_depth; lo.no_free = 1; - hashcpy(lo.sha1, root->versions[0].sha1); } mktree(t, 1, &new_len, &new_tree); @@ -1471,12 +1356,11 @@ static void cmd_from(struct branch *b) unsigned long idnum = strtoul(from + 1, NULL, 10); struct object_entry *oe = find_mark(idnum); unsigned long size; - unsigned int depth; char *buf; if (oe->type != OBJ_COMMIT) die("Mark :%lu not a commit", idnum); hashcpy(b->sha1, oe->sha1); - buf = gfi_unpack_entry(oe->offset, &size, &depth); + buf = gfi_unpack_entry(oe->offset, &size); if (!buf || size < 46) die("Not a valid commit: %s", from); if (memcmp("tree ", buf, 5) @@ -1818,7 +1702,6 @@ int main(int argc, const char **argv) setup_ident(); git_config(git_default_config); - page_size = getpagesize(); for (i = 1; i < argc; i++) { const char *a = argv[i]; @@ -1854,6 +1737,10 @@ int main(int argc, const char **argv) if (pack_fd < 0) die("Can't create %s: %s", pack_name, strerror(errno)); + pack_data = xcalloc(1, sizeof(*pack_data) + strlen(pack_name) + 2); + strcpy(pack_data->pack_name, pack_name); + pack_data->pack_fd = pack_fd; + init_pack_header(); alloc_objects(est_obj_cnt); strbuf_init(&command_buf); From 03842d8e24face522fa0ca846283da33e747e4f0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 15 Jan 2007 00:16:23 -0500 Subject: [PATCH 198/548] Misc. type cleanups within fast-import. Signed-off-by: Shawn O. Pearce --- fast-import.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fast-import.c b/fast-import.c index f0f51a6899..3a98cb848f 100644 --- a/fast-import.c +++ b/fast-import.c @@ -110,8 +110,8 @@ Format of STDIN stream: struct object_entry { struct object_entry *next; - enum object_type type; unsigned long offset; + unsigned type : TYPE_BITS; unsigned char sha1[20]; }; @@ -220,9 +220,9 @@ static unsigned long remap_count; static unsigned long object_count; static unsigned long duplicate_count; static unsigned long marks_set_count; -static unsigned long object_count_by_type[9]; -static unsigned long duplicate_count_by_type[9]; -static unsigned long delta_count_by_type[9]; +static unsigned long object_count_by_type[1 << TYPE_BITS]; +static unsigned long duplicate_count_by_type[1 << TYPE_BITS]; +static unsigned long delta_count_by_type[1 << TYPE_BITS]; /* Memory pools */ static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool); @@ -276,7 +276,7 @@ static struct dbuf new_data; static FILE* branch_log; -static void alloc_objects(int cnt) +static void alloc_objects(unsigned int cnt) { struct object_entry_pool *b; From f70b653429ebc7fdde0b36a63e1deb4aadb450d3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 15 Jan 2007 04:39:05 -0500 Subject: [PATCH 199/548] Restructure fast-import to support creating multiple packfiles. Now that we are starting to see some really large projects (such as KDE or a fork of FreeBSD) get imported into Git we're running into the upper limit on packfile object count as well as overall byte length. The KDE and FreeBSD projects are both likely to require more than 4 GiB to store their current history, which means we really need multiple packfiles to handle their content. This is a fairly simple restructuring of the internal code to help us support creating multiple packfiles from within fast-import. We are now adding a 5 digit incrementing suffix to the end of the basename supplied to us by the caller, permitting up to 99,999 packs to be generated in a single fast-import run. Signed-off-by: Shawn O. Pearce --- fast-import.c | 245 +++++++++++++++++++++++++------------------------- 1 file changed, 124 insertions(+), 121 deletions(-) diff --git a/fast-import.c b/fast-import.c index 3a98cb848f..fc8567e9f6 100644 --- a/fast-import.c +++ b/fast-import.c @@ -216,7 +216,6 @@ static unsigned long max_depth = 10; static unsigned long alloc_count; static unsigned long branch_count; static unsigned long branch_load_count; -static unsigned long remap_count; static unsigned long object_count; static unsigned long duplicate_count; static unsigned long marks_set_count; @@ -235,6 +234,10 @@ static unsigned int atom_cnt; static struct atom_str **atom_table; /* The .pack file being generated */ +static const char *base_name; +static unsigned int pack_count; +static char *pack_name; +static char *idx_name; static struct packed_git *pack_data; static int pack_fd; static unsigned long pack_size; @@ -593,6 +596,124 @@ static void yread(int fd, void *buffer, size_t length) } } +static void start_packfile() +{ + struct pack_header hdr; + + pack_count++; + pack_name = xmalloc(strlen(base_name) + 11); + idx_name = xmalloc(strlen(base_name) + 11); + sprintf(pack_name, "%s%5.5i.pack", base_name, pack_count); + sprintf(idx_name, "%s%5.5i.idx", base_name, pack_count); + + pack_fd = open(pack_name, O_RDWR|O_CREAT|O_EXCL, 0666); + if (pack_fd < 0) + die("Can't create %s: %s", pack_name, strerror(errno)); + + pack_data = xcalloc(1, sizeof(*pack_data) + strlen(pack_name) + 2); + strcpy(pack_data->pack_name, pack_name); + pack_data->pack_fd = pack_fd; + + hdr.hdr_signature = htonl(PACK_SIGNATURE); + hdr.hdr_version = htonl(2); + hdr.hdr_entries = 0; + + write_or_die(pack_fd, &hdr, sizeof(hdr)); + pack_size = sizeof(hdr); + object_count = 0; +} + +static void fixup_header_footer() +{ + SHA_CTX c; + char hdr[8]; + unsigned long cnt; + char *buf; + + if (lseek(pack_fd, 0, SEEK_SET) != 0) + die("Failed seeking to start: %s", strerror(errno)); + + SHA1_Init(&c); + yread(pack_fd, hdr, 8); + SHA1_Update(&c, hdr, 8); + + cnt = htonl(object_count); + SHA1_Update(&c, &cnt, 4); + write_or_die(pack_fd, &cnt, 4); + + buf = xmalloc(128 * 1024); + for (;;) { + size_t n = xread(pack_fd, buf, 128 * 1024); + if (n <= 0) + break; + SHA1_Update(&c, buf, n); + } + free(buf); + + SHA1_Final(pack_sha1, &c); + write_or_die(pack_fd, pack_sha1, sizeof(pack_sha1)); +} + +static int oecmp (const void *a_, const void *b_) +{ + struct object_entry *a = *((struct object_entry**)a_); + struct object_entry *b = *((struct object_entry**)b_); + return hashcmp(a->sha1, b->sha1); +} + +static void write_index(const char *idx_name) +{ + struct sha1file *f; + struct object_entry **idx, **c, **last, *e; + struct object_entry_pool *o; + unsigned int array[256]; + int i; + + /* Build the sorted table of object IDs. */ + idx = xmalloc(object_count * sizeof(struct object_entry*)); + c = idx; + for (o = blocks; o; o = o->next_pool) + for (e = o->entries; e != o->next_free; e++) + *c++ = e; + last = idx + object_count; + qsort(idx, object_count, sizeof(struct object_entry*), oecmp); + + /* Generate the fan-out array. */ + c = idx; + for (i = 0; i < 256; i++) { + struct object_entry **next = c;; + while (next < last) { + if ((*next)->sha1[0] != i) + break; + next++; + } + array[i] = htonl(next - idx); + c = next; + } + + f = sha1create("%s", idx_name); + sha1write(f, array, 256 * sizeof(int)); + for (c = idx; c != last; c++) { + unsigned int offset = htonl((*c)->offset); + sha1write(f, &offset, 4); + sha1write(f, (*c)->sha1, sizeof((*c)->sha1)); + } + sha1write(f, pack_sha1, sizeof(pack_sha1)); + sha1close(f, NULL, 1); + free(idx); +} + +static void end_packfile() +{ + fixup_header_footer(); + close(pack_fd); + write_index(idx_name); + + free(pack_name); + free(idx_name); + free(pack_data); +} + static size_t encode_header( enum object_type type, size_t size, @@ -994,100 +1115,6 @@ del_entry: return 1; } -static void init_pack_header() -{ - struct pack_header hdr; - - hdr.hdr_signature = htonl(PACK_SIGNATURE); - hdr.hdr_version = htonl(2); - hdr.hdr_entries = 0; - - write_or_die(pack_fd, &hdr, sizeof(hdr)); - pack_size = sizeof(hdr); -} - -static void fixup_header_footer() -{ - SHA_CTX c; - char hdr[8]; - unsigned long cnt; - char *buf; - size_t n; - - if (lseek(pack_fd, 0, SEEK_SET) != 0) - die("Failed seeking to start: %s", strerror(errno)); - - SHA1_Init(&c); - yread(pack_fd, hdr, 8); - SHA1_Update(&c, hdr, 8); - - cnt = htonl(object_count); - SHA1_Update(&c, &cnt, 4); - write_or_die(pack_fd, &cnt, 4); - - buf = xmalloc(128 * 1024); - for (;;) { - n = xread(pack_fd, buf, 128 * 1024); - if (n <= 0) - break; - SHA1_Update(&c, buf, n); - } - free(buf); - - SHA1_Final(pack_sha1, &c); - write_or_die(pack_fd, pack_sha1, sizeof(pack_sha1)); -} - -static int oecmp (const void *_a, const void *_b) -{ - struct object_entry *a = *((struct object_entry**)_a); - struct object_entry *b = *((struct object_entry**)_b); - return hashcmp(a->sha1, b->sha1); -} - -static void write_index(const char *idx_name) -{ - struct sha1file *f; - struct object_entry **idx, **c, **last; - struct object_entry *e; - struct object_entry_pool *o; - unsigned int array[256]; - int i; - - /* Build the sorted table of object IDs. */ - idx = xmalloc(object_count * sizeof(struct object_entry*)); - c = idx; - for (o = blocks; o; o = o->next_pool) - for (e = o->entries; e != o->next_free; e++) - *c++ = e; - last = idx + object_count; - qsort(idx, object_count, sizeof(struct object_entry*), oecmp); - - /* Generate the fan-out array. */ - c = idx; - for (i = 0; i < 256; i++) { - struct object_entry **next = c;; - while (next < last) { - if ((*next)->sha1[0] != i) - break; - next++; - } - array[i] = htonl(next - idx); - c = next; - } - - f = sha1create("%s", idx_name); - sha1write(f, array, 256 * sizeof(int)); - for (c = idx; c != last; c++) { - unsigned int offset = htonl((*c)->offset); - sha1write(f, &offset, 4); - sha1write(f, (*c)->sha1, sizeof((*c)->sha1)); - } - sha1write(f, pack_sha1, sizeof(pack_sha1)); - sha1close(f, NULL, 1); - free(idx); -} - static void dump_branches() { static const char *msg = "fast-import"; @@ -1693,11 +1720,8 @@ static const char fast_import_usage[] = int main(int argc, const char **argv) { - const char *base_name; int i; unsigned long est_obj_cnt = object_entry_alloc; - char *pack_name; - char *idx_name; struct stat sb; setup_ident(); @@ -1728,20 +1752,6 @@ int main(int argc, const char **argv) usage(fast_import_usage); base_name = argv[i]; - pack_name = xmalloc(strlen(base_name) + 6); - sprintf(pack_name, "%s.pack", base_name); - idx_name = xmalloc(strlen(base_name) + 5); - sprintf(idx_name, "%s.idx", base_name); - - pack_fd = open(pack_name, O_RDWR|O_CREAT|O_EXCL, 0666); - if (pack_fd < 0) - die("Can't create %s: %s", pack_name, strerror(errno)); - - pack_data = xcalloc(1, sizeof(*pack_data) + strlen(pack_name) + 2); - strcpy(pack_data->pack_name, pack_name); - pack_data->pack_fd = pack_fd; - - init_pack_header(); alloc_objects(est_obj_cnt); strbuf_init(&command_buf); @@ -1750,6 +1760,7 @@ int main(int argc, const char **argv) avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*)); marks = pool_calloc(1, sizeof(struct mark_set)); + start_packfile(); for (;;) { read_next_command(); if (command_buf.eof) @@ -1765,10 +1776,8 @@ int main(int argc, const char **argv) else die("Unsupported command: %s", command_buf.buf); } + end_packfile(); - fixup_header_footer(); - close(pack_fd); - write_index(idx_name); dump_branches(); dump_tags(); dump_marks(); @@ -1789,13 +1798,7 @@ int main(int argc, const char **argv) fprintf(stderr, "Memory total: %10lu KiB\n", (total_allocd + alloc_count*sizeof(struct object_entry))/1024); fprintf(stderr, " pools: %10lu KiB\n", total_allocd/1024); fprintf(stderr, " objects: %10lu KiB\n", (alloc_count*sizeof(struct object_entry))/1024); - fprintf(stderr, "Pack remaps: %10lu\n", remap_count); - stat(pack_name, &sb); - fprintf(stderr, "Pack size: %10lu KiB\n", (unsigned long)(sb.st_size/1024)); - stat(idx_name, &sb); - fprintf(stderr, "Index size: %10lu KiB\n", (unsigned long)(sb.st_size/1024)); fprintf(stderr, "---------------------------------------------------------------------\n"); - fprintf(stderr, "\n"); return 0; From 80144727acc401070039434987692276dcb9273c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 15 Jan 2007 05:03:32 -0500 Subject: [PATCH 200/548] Remove unnecessary duplicate_count in fast-import. There is little reason to be keeping a global duplicate_count value when we also keep it per object type. The global counter can easily be computed at the end, once all processing has completed. This saves us a couple of machine instructions in an unimportant part of code. But it looks slightly better to me to not keep two counters around. Signed-off-by: Shawn O. Pearce --- fast-import.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fast-import.c b/fast-import.c index fc8567e9f6..12127168bd 100644 --- a/fast-import.c +++ b/fast-import.c @@ -217,7 +217,6 @@ static unsigned long alloc_count; static unsigned long branch_count; static unsigned long branch_load_count; static unsigned long object_count; -static unsigned long duplicate_count; static unsigned long marks_set_count; static unsigned long object_count_by_type[1 << TYPE_BITS]; static unsigned long duplicate_count_by_type[1 << TYPE_BITS]; @@ -765,7 +764,6 @@ static int store_object( if (mark) insert_mark(mark, e); if (e->offset) { - duplicate_count++; duplicate_count_by_type[type]++; return 1; } @@ -1722,7 +1720,7 @@ int main(int argc, const char **argv) { int i; unsigned long est_obj_cnt = object_entry_alloc; - struct stat sb; + unsigned long duplicate_count; setup_ident(); git_config(git_default_config); @@ -1784,6 +1782,9 @@ int main(int argc, const char **argv) if (branch_log) fclose(branch_log); + for (i = 0; i < ARRAY_SIZE(duplicate_count_by_type); i++) + duplicate_count += duplicate_count_by_type[i]; + fprintf(stderr, "%s statistics:\n", argv[0]); fprintf(stderr, "---------------------------------------------------------------------\n"); fprintf(stderr, "Alloc'd objects: %10lu (%10lu overflow )\n", alloc_count, alloc_count - est_obj_cnt); From 7bfe6e261378a30980886994dabc0e7e4c9ce3d8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 15 Jan 2007 06:35:41 -0500 Subject: [PATCH 201/548] Implemented manual packfile switching in fast-import. To help importers which are dealing with massive amounts of data fast-import needs to be able to close the packfile it is currently writing to and open a new packfile for any additional data that will be received. A new 'checkpoint' command has been introduced which can be used by the frontend import process to force this to occur at any time. This may be useful to ensure a very long running import doesn't lose any work due to unexpected failures. Signed-off-by: Shawn O. Pearce --- fast-import.c | 89 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/fast-import.c b/fast-import.c index 12127168bd..3f747be287 100644 --- a/fast-import.c +++ b/fast-import.c @@ -40,6 +40,9 @@ Format of STDIN stream: ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? lf; + checkpoint ::= 'checkpoint' lf + lf; + # note: the first idnum in a stream should be 1 and subsequent # idnums should not have gaps between values as this will cause # the stream parser to reserve space for the gapped values. An @@ -112,6 +115,7 @@ struct object_entry struct object_entry *next; unsigned long offset; unsigned type : TYPE_BITS; + unsigned pack_id : 16; unsigned char sha1[20]; }; @@ -234,10 +238,10 @@ static struct atom_str **atom_table; /* The .pack file being generated */ static const char *base_name; -static unsigned int pack_count; -static char *pack_name; +static unsigned int pack_id; static char *idx_name; static struct packed_git *pack_data; +static struct packed_git **all_packs; static int pack_fd; static unsigned long pack_size; static unsigned char pack_sha1[20]; @@ -299,6 +303,7 @@ static struct object_entry* new_object(unsigned char *sha1) alloc_objects(object_entry_alloc); e = blocks->next_free++; + e->pack_id = pack_id; hashcpy(e->sha1, sha1); return e; } @@ -597,29 +602,30 @@ static void yread(int fd, void *buffer, size_t length) static void start_packfile() { + struct packed_git *p; struct pack_header hdr; - pack_count++; - pack_name = xmalloc(strlen(base_name) + 11); idx_name = xmalloc(strlen(base_name) + 11); - sprintf(pack_name, "%s%5.5i.pack", base_name, pack_count); - sprintf(idx_name, "%s%5.5i.idx", base_name, pack_count); + p = xcalloc(1, sizeof(*p) + strlen(base_name) + 13); + sprintf(p->pack_name, "%s%5.5i.pack", base_name, pack_id + 1); + sprintf(idx_name, "%s%5.5i.idx", base_name, pack_id + 1); - pack_fd = open(pack_name, O_RDWR|O_CREAT|O_EXCL, 0666); + pack_fd = open(p->pack_name, O_RDWR|O_CREAT|O_EXCL, 0666); if (pack_fd < 0) - die("Can't create %s: %s", pack_name, strerror(errno)); - - pack_data = xcalloc(1, sizeof(*pack_data) + strlen(pack_name) + 2); - strcpy(pack_data->pack_name, pack_name); - pack_data->pack_fd = pack_fd; + die("Can't create %s: %s", p->pack_name, strerror(errno)); + p->pack_fd = pack_fd; hdr.hdr_signature = htonl(PACK_SIGNATURE); hdr.hdr_version = htonl(2); hdr.hdr_entries = 0; - write_or_die(pack_fd, &hdr, sizeof(hdr)); + + pack_data = p; pack_size = sizeof(hdr); object_count = 0; + + all_packs = xrealloc(all_packs, sizeof(*all_packs) * (pack_id + 1)); + all_packs[pack_id] = p; } static void fixup_header_footer() @@ -673,7 +679,8 @@ static void write_index(const char *idx_name) c = idx; for (o = blocks; o; o = o->next_pool) for (e = o->entries; e != o->next_free; e++) - *c++ = e; + if (pack_id == e->pack_id) + *c++ = e; last = idx + object_count; qsort(idx, object_count, sizeof(struct object_entry*), oecmp); @@ -704,13 +711,28 @@ static void write_index(const char *idx_name) static void end_packfile() { + struct packed_git *old_p = pack_data, *new_p; + fixup_header_footer(); - close(pack_fd); write_index(idx_name); - free(pack_name); + /* Register the packfile with core git's machinary. */ + new_p = add_packed_git(idx_name, strlen(idx_name), 1); + if (!new_p) + die("core git rejected index %s", idx_name); + new_p->windows = old_p->windows; + new_p->pack_fd = old_p->pack_fd; + all_packs[pack_id++] = new_p; + install_packed_git(new_p); + free(old_p); free(idx_name); - free(pack_data); + + /* We can't carry a delta across packfiles. */ + free(last_blob.data); + last_blob.data = NULL; + last_blob.len = 0; + last_blob.offset = 0; + last_blob.depth = 0; } static size_t encode_header( @@ -832,11 +854,15 @@ static int store_object( return 0; } -static void *gfi_unpack_entry(unsigned long ofs, unsigned long *sizep) +static void *gfi_unpack_entry( + struct object_entry *oe, + unsigned long *sizep) { - char type[20]; - pack_data->pack_size = pack_size + 20; - return unpack_entry(pack_data, ofs, type, sizep); + static char type[20]; + struct packed_git *p = all_packs[oe->pack_id]; + if (p == pack_data) + p->pack_size = pack_size + 20; + return unpack_entry(p, oe->offset, type, sizep); } static const char *get_mode(const char *str, unsigned int *modep) @@ -871,7 +897,7 @@ static void load_tree(struct tree_entry *root) if (myoe->type != OBJ_TREE) die("Not a tree: %s", sha1_to_hex(sha1)); t->delta_depth = 0; - buf = gfi_unpack_entry(myoe->offset, &size); + buf = gfi_unpack_entry(myoe, &size); } else { char type[20]; buf = read_sha1_file(sha1, type, &size); @@ -971,7 +997,9 @@ static void store_tree(struct tree_entry *root) } le = find_object(root->versions[0].sha1); - if (!S_ISDIR(root->versions[0].mode) || !le) { + if (!S_ISDIR(root->versions[0].mode) + || !le + || le->pack_id != pack_id) { lo.data = NULL; lo.depth = 0; } else { @@ -1385,7 +1413,7 @@ static void cmd_from(struct branch *b) if (oe->type != OBJ_COMMIT) die("Mark :%lu not a commit", idnum); hashcpy(b->sha1, oe->sha1); - buf = gfi_unpack_entry(oe->offset, &size); + buf = gfi_unpack_entry(oe, &size); if (!buf || size < 46) die("Not a valid commit: %s", from); if (memcmp("tree ", buf, 5) @@ -1713,6 +1741,15 @@ static void cmd_reset_branch() cmd_from(b); } +static void cmd_checkpoint() +{ + if (object_count) { + end_packfile(); + start_packfile(); + } + read_next_command(); +} + static const char fast_import_usage[] = "git-fast-import [--objects=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file] [--branch-log=log] temp.pack"; @@ -1771,6 +1808,8 @@ int main(int argc, const char **argv) cmd_new_tag(); else if (!strncmp("reset ", command_buf.buf, 6)) cmd_reset_branch(); + else if (!strcmp("checkpoint", command_buf.buf)) + cmd_checkpoint(); else die("Unsupported command: %s", command_buf.buf); } @@ -1800,6 +1839,8 @@ int main(int argc, const char **argv) fprintf(stderr, " pools: %10lu KiB\n", total_allocd/1024); fprintf(stderr, " objects: %10lu KiB\n", (alloc_count*sizeof(struct object_entry))/1024); fprintf(stderr, "---------------------------------------------------------------------\n"); + pack_report(); + fprintf(stderr, "---------------------------------------------------------------------\n"); fprintf(stderr, "\n"); return 0; From 3e005baf8542a3116e51c4b0a27b72c7e14d949b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 15 Jan 2007 06:39:39 -0500 Subject: [PATCH 202/548] Don't create a final empty packfile in fast-import. If the last packfile is going to be empty (has 0 objects) then it shouldn't be kept after the import has terminated, as there is no point to the packfile. So rather than hashing it and making the index file, just delete the packfile. Signed-off-by: Shawn O. Pearce --- fast-import.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/fast-import.c b/fast-import.c index 3f747be287..207acb3230 100644 --- a/fast-import.c +++ b/fast-import.c @@ -713,17 +713,23 @@ static void end_packfile() { struct packed_git *old_p = pack_data, *new_p; - fixup_header_footer(); - write_index(idx_name); + if (object_count) { + fixup_header_footer(); + write_index(idx_name); - /* Register the packfile with core git's machinary. */ - new_p = add_packed_git(idx_name, strlen(idx_name), 1); - if (!new_p) - die("core git rejected index %s", idx_name); - new_p->windows = old_p->windows; - new_p->pack_fd = old_p->pack_fd; - all_packs[pack_id++] = new_p; - install_packed_git(new_p); + /* Register the packfile with core git's machinary. */ + new_p = add_packed_git(idx_name, strlen(idx_name), 1); + if (!new_p) + die("core git rejected index %s", idx_name); + new_p->windows = old_p->windows; + new_p->pack_fd = old_p->pack_fd; + all_packs[pack_id++] = new_p; + install_packed_git(new_p); + } + else { + close(pack_fd); + unlink(old_p->pack_name); + } free(old_p); free(idx_name); From 2fce1f3c862845d23b2bd8305f97abb115623192 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 15 Jan 2007 06:51:58 -0500 Subject: [PATCH 203/548] Optimize index creation on large object sets in fast-import. When we are generating multiple packfiles at once we only need to scan the blocks of object_entry structs which contain objects for the current packfile. Because the most recent blocks are at the front of the linked list, and because all new objects going into the current file are allocated from the front of that list, we can stop scanning for objects as soon as we identify one which doesn't belong to the current packfile. Signed-off-by: Shawn O. Pearce --- fast-import.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fast-import.c b/fast-import.c index 207acb3230..cfadda0432 100644 --- a/fast-import.c +++ b/fast-import.c @@ -678,10 +678,15 @@ static void write_index(const char *idx_name) idx = xmalloc(object_count * sizeof(struct object_entry*)); c = idx; for (o = blocks; o; o = o->next_pool) - for (e = o->entries; e != o->next_free; e++) - if (pack_id == e->pack_id) - *c++ = e; + for (e = o->next_free; e-- != o->entries;) { + if (pack_id != e->pack_id) + goto sort_index; + *c++ = e; + } +sort_index: last = idx + object_count; + if (c != last) + die("internal consistency error creating the index"); qsort(idx, object_count, sizeof(struct object_entry*), oecmp); /* Generate the fan-out array. */ From d9ee53ce45b0f1c26285417b900b3c5735721f7e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 15 Jan 2007 08:00:49 -0500 Subject: [PATCH 204/548] Implemented automatic checkpoints within fast-import. When the number of objects or number of bytes gets close to the limit allowed by the packfile format (or configured on the command line by our caller) we should automatically checkpoint the current packfile and start a new one before writing the object out. This does however require that we abandon the delta (if we had one) as its not valid in a new packfile. I also added the simple rule that if we got a delta back but the delta itself is the same size as or larger than the uncompressed object to ignore the delta and just store the object data. This should avoid some really bad behavior caused by our current delta strategy. Signed-off-by: Shawn O. Pearce --- fast-import.c | 96 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 28 deletions(-) diff --git a/fast-import.c b/fast-import.c index cfadda0432..c19567f68c 100644 --- a/fast-import.c +++ b/fast-import.c @@ -217,6 +217,8 @@ struct hash_list /* Stats and misc. counters */ static unsigned long max_depth = 10; +static unsigned long max_objects = -1; +static unsigned long max_packsize = -1; static unsigned long alloc_count; static unsigned long branch_count; static unsigned long branch_load_count; @@ -303,7 +305,6 @@ static struct object_entry* new_object(unsigned char *sha1) alloc_objects(object_entry_alloc); e = blocks->next_free++; - e->pack_id = pack_id; hashcpy(e->sha1, sha1); return e; } @@ -678,12 +679,9 @@ static void write_index(const char *idx_name) idx = xmalloc(object_count * sizeof(struct object_entry*)); c = idx; for (o = blocks; o; o = o->next_pool) - for (e = o->next_free; e-- != o->entries;) { - if (pack_id != e->pack_id) - goto sort_index; - *c++ = e; - } -sort_index: + for (e = o->next_free; e-- != o->entries;) + if (pack_id == e->pack_id) + *c++ = e; last = idx + object_count; if (c != last) die("internal consistency error creating the index"); @@ -746,6 +744,12 @@ static void end_packfile() last_blob.depth = 0; } +static void checkpoint() +{ + end_packfile(); + start_packfile(); +} + static size_t encode_header( enum object_type type, size_t size, @@ -800,20 +804,64 @@ static int store_object( duplicate_count_by_type[type]++; return 1; } - e->type = type; - e->offset = pack_size; - object_count++; - object_count_by_type[type]++; - if (last && last->data && last->depth < max_depth) + if (last && last->data && last->depth < max_depth) { delta = diff_delta(last->data, last->len, dat, datlen, &deltalen, 0); - else - delta = 0; + if (delta && deltalen >= datlen) { + free(delta); + delta = NULL; + } + } else + delta = NULL; memset(&s, 0, sizeof(s)); deflateInit(&s, zlib_compression_level); + if (delta) { + s.next_in = delta; + s.avail_in = deltalen; + } else { + s.next_in = dat; + s.avail_in = datlen; + } + s.avail_out = deflateBound(&s, s.avail_in); + s.next_out = out = xmalloc(s.avail_out); + while (deflate(&s, Z_FINISH) == Z_OK) + /* nothing */; + deflateEnd(&s); + + /* Determine if we should auto-checkpoint. */ + if ((object_count + 1) > max_objects + || (object_count + 1) < object_count + || (pack_size + 60 + s.total_out) > max_packsize + || (pack_size + 60 + s.total_out) < pack_size) { + + /* This new object needs to *not* have the current pack_id. */ + e->pack_id = pack_id + 1; + checkpoint(); + + /* We cannot carry a delta into the new pack. */ + if (delta) { + free(delta); + delta = NULL; + } + memset(&s, 0, sizeof(s)); + deflateInit(&s, zlib_compression_level); + s.next_in = dat; + s.avail_in = datlen; + s.avail_out = deflateBound(&s, s.avail_in); + s.next_out = out; + while (deflate(&s, Z_FINISH) == Z_OK) + /* nothing */; + deflateEnd(&s); + } + + e->type = type; + e->pack_id = pack_id; + e->offset = pack_size; + object_count++; + object_count_by_type[type]++; if (delta) { unsigned long ofs = e->offset - last->offset; @@ -821,8 +869,6 @@ static int store_object( delta_count_by_type[type]++; last->depth++; - s.next_in = delta; - s.avail_in = deltalen; hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr); write_or_die(pack_fd, hdr, hdrlen); @@ -836,19 +882,11 @@ static int store_object( } else { if (last) last->depth = 0; - s.next_in = dat; - s.avail_in = datlen; hdrlen = encode_header(type, datlen, hdr); write_or_die(pack_fd, hdr, hdrlen); pack_size += hdrlen; } - s.avail_out = deflateBound(&s, s.avail_in); - s.next_out = out = xmalloc(s.avail_out); - while (deflate(&s, Z_FINISH) == Z_OK) - /* nothing */; - deflateEnd(&s); - write_or_die(pack_fd, out, s.total_out); pack_size += s.total_out; @@ -1754,10 +1792,8 @@ static void cmd_reset_branch() static void cmd_checkpoint() { - if (object_count) { - end_packfile(); - start_packfile(); - } + if (object_count) + checkpoint(); read_next_command(); } @@ -1780,6 +1816,10 @@ int main(int argc, const char **argv) break; else if (!strncmp(a, "--objects=", 10)) est_obj_cnt = strtoul(a + 10, NULL, 0); + else if (!strncmp(a, "--max-objects-per-pack=", 23)) + max_objects = strtoul(a + 23, NULL, 0); + else if (!strncmp(a, "--max-pack-size=", 16)) + max_packsize = strtoul(a + 16, NULL, 0) * 1024 * 1024; else if (!strncmp(a, "--depth=", 8)) max_depth = strtoul(a + 8, NULL, 0); else if (!strncmp(a, "--active-branches=", 18)) From 9d1b1b5ed7f4234ea4f2c1344ba67c6f89e2067c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 15 Jan 2007 08:03:38 -0500 Subject: [PATCH 205/548] Print the packfile names to stdout from fast-import. Caller scripts may want to know what packfiles the fast-import process just wrote out for them. This is now output to stdout, one packfile name per line, after we checkpoint each packfile. Signed-off-by: Shawn O. Pearce --- fast-import.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fast-import.c b/fast-import.c index c19567f68c..19d01e20ad 100644 --- a/fast-import.c +++ b/fast-import.c @@ -719,6 +719,8 @@ static void end_packfile() if (object_count) { fixup_header_footer(); write_index(idx_name); + fprintf(stdout, "%s\n", old_p->pack_name); + fflush(stdout); /* Register the packfile with core git's machinary. */ new_p = add_packed_git(idx_name, strlen(idx_name), 1); From 5d6f3ef6413172388ee5e6090afe9802a30a59f0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 15 Jan 2007 23:40:27 -0500 Subject: [PATCH 206/548] Corrected buffer overflow during automatic checkpoint in fast-import. If we previously were using a delta but we needed to checkpoint the current packfile and switch to a new packfile we need to throw away the delta and compress the raw object by itself, as delta chains cannot span non-thin packfiles. Unfortunately the output buffer in this case needs to grow, as the size of the compressed object may be quite a bit larger than the size of the compressed delta. I've also avoided recompressing the object if we are checkpointing and we didn't use a delta. In this case the output buffer is the correct size and has already been populated with the right data, we just need to close out the current packfile and open a new one. Signed-off-by: Shawn O. Pearce --- fast-import.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/fast-import.c b/fast-import.c index 19d01e20ad..57d857c386 100644 --- a/fast-import.c +++ b/fast-import.c @@ -847,16 +847,17 @@ static int store_object( if (delta) { free(delta); delta = NULL; + + memset(&s, 0, sizeof(s)); + deflateInit(&s, zlib_compression_level); + s.next_in = dat; + s.avail_in = datlen; + s.avail_out = deflateBound(&s, s.avail_in); + s.next_out = out = xrealloc(out, s.avail_out); + while (deflate(&s, Z_FINISH) == Z_OK) + /* nothing */; + deflateEnd(&s); } - memset(&s, 0, sizeof(s)); - deflateInit(&s, zlib_compression_level); - s.next_in = dat; - s.avail_in = datlen; - s.avail_out = deflateBound(&s, s.avail_in); - s.next_out = out; - while (deflate(&s, Z_FINISH) == Z_OK) - /* nothing */; - deflateEnd(&s); } e->type = type; From 0ea9f045f4eaa1d37c6b318d9d6849a4f447b997 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 16 Jan 2007 00:33:19 -0500 Subject: [PATCH 207/548] Use uintmax_t for marks in fast-import. If a frontend wants to use a mark per file revision and per commit and is doing a truly huge import (such as a 32 GiB SVN repository) we may need more than 2**32 unique mark values, especially if the frontend is unable (or unwilling) to recycle mark values. For mark idnums we should use the largest unsigned integer type available, hoping that will be at least 64 bits when we are compiled as a 64 bit executable. This way we may consume huge amounts of memory storing our mark table, but we'll at least be able to process the entire import without failing. Signed-off-by: Shawn O. Pearce --- fast-import.c | 91 ++++++++++++++++++++++++----------------------- git-compat-util.h | 1 + 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/fast-import.c b/fast-import.c index 57d857c386..ebffa7c904 100644 --- a/fast-import.c +++ b/fast-import.c @@ -215,18 +215,20 @@ struct hash_list unsigned char sha1[20]; }; -/* Stats and misc. counters */ +/* Configured limits on output */ static unsigned long max_depth = 10; -static unsigned long max_objects = -1; static unsigned long max_packsize = -1; -static unsigned long alloc_count; +static uintmax_t max_objects = -1; + +/* Stats and misc. counters */ +static uintmax_t alloc_count; +static uintmax_t object_count; +static uintmax_t marks_set_count; +static uintmax_t object_count_by_type[1 << TYPE_BITS]; +static uintmax_t duplicate_count_by_type[1 << TYPE_BITS]; +static uintmax_t delta_count_by_type[1 << TYPE_BITS]; static unsigned long branch_count; static unsigned long branch_load_count; -static unsigned long object_count; -static unsigned long marks_set_count; -static unsigned long object_count_by_type[1 << TYPE_BITS]; -static unsigned long duplicate_count_by_type[1 << TYPE_BITS]; -static unsigned long delta_count_by_type[1 << TYPE_BITS]; /* Memory pools */ static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool); @@ -279,7 +281,7 @@ static struct tag *last_tag; /* Input stream parsing */ static struct strbuf command_buf; -static unsigned long next_mark; +static uintmax_t next_mark; static struct dbuf new_data; static FILE* branch_log; @@ -406,7 +408,7 @@ static void size_dbuf(struct dbuf *b, size_t maxlen) b->buffer = xmalloc(b->capacity); } -static void insert_mark(unsigned long idnum, struct object_entry *oe) +static void insert_mark(uintmax_t idnum, struct object_entry *oe) { struct mark_set *s = marks; while ((idnum >> s->shift) >= 1024) { @@ -416,7 +418,7 @@ static void insert_mark(unsigned long idnum, struct object_entry *oe) marks = s; } while (s->shift) { - unsigned long i = idnum >> s->shift; + uintmax_t i = idnum >> s->shift; idnum -= i << s->shift; if (!s->data.sets[i]) { s->data.sets[i] = pool_calloc(1, sizeof(struct mark_set)); @@ -429,14 +431,14 @@ static void insert_mark(unsigned long idnum, struct object_entry *oe) s->data.marked[idnum] = oe; } -static struct object_entry* find_mark(unsigned long idnum) +static struct object_entry* find_mark(uintmax_t idnum) { - unsigned long orig_idnum = idnum; + uintmax_t orig_idnum = idnum; struct mark_set *s = marks; struct object_entry *oe = NULL; if ((idnum >> s->shift) < 1024) { while (s && s->shift) { - unsigned long i = idnum >> s->shift; + uintmax_t i = idnum >> s->shift; idnum -= i << s->shift; s = s->data.sets[i]; } @@ -444,7 +446,7 @@ static struct object_entry* find_mark(unsigned long idnum) oe = s->data.marked[idnum]; } if (!oe) - die("mark :%lu not declared", orig_idnum); + die("mark :%ju not declared", orig_idnum); return oe; } @@ -781,7 +783,7 @@ static int store_object( size_t datlen, struct last_object *last, unsigned char *sha1out, - unsigned long mark) + uintmax_t mark) { void *out, *delta; struct object_entry *e; @@ -1225,10 +1227,10 @@ static void dump_tags() } static void dump_marks_helper(FILE *f, - unsigned long base, + uintmax_t base, struct mark_set *m) { - int k; + uintmax_t k; if (m->shift) { for (k = 0; k < 1024; k++) { if (m->data.sets[k]) @@ -1238,7 +1240,7 @@ static void dump_marks_helper(FILE *f, } else { for (k = 0; k < 1024; k++) { if (m->data.marked[k]) - fprintf(f, ":%lu %s\n", base + k, + fprintf(f, ":%ju %s\n", base + k, sha1_to_hex(m->data.marked[k]->sha1)); } } @@ -1262,7 +1264,7 @@ static void read_next_command() static void cmd_mark() { if (!strncmp("mark :", command_buf.buf, 6)) { - next_mark = strtoul(command_buf.buf + 6, NULL, 10); + next_mark = strtoumax(command_buf.buf + 6, NULL, 10); read_next_command(); } else @@ -1375,7 +1377,7 @@ static void file_change_m(struct branch *b) if (*p == ':') { char *x; - oe = find_mark(strtoul(p + 1, &x, 10)); + oe = find_mark(strtoumax(p + 1, &x, 10)); hashcpy(sha1, oe->sha1); p = x; } else { @@ -1458,12 +1460,12 @@ static void cmd_from(struct branch *b) hashcpy(b->branch_tree.versions[0].sha1, t); hashcpy(b->branch_tree.versions[1].sha1, t); } else if (*from == ':') { - unsigned long idnum = strtoul(from + 1, NULL, 10); + uintmax_t idnum = strtoumax(from + 1, NULL, 10); struct object_entry *oe = find_mark(idnum); unsigned long size; char *buf; if (oe->type != OBJ_COMMIT) - die("Mark :%lu not a commit", idnum); + die("Mark :%ju not a commit", idnum); hashcpy(b->sha1, oe->sha1); buf = gfi_unpack_entry(oe, &size); if (!buf || size < 46) @@ -1521,10 +1523,10 @@ static struct hash_list* cmd_merge(unsigned int *count) if (s) hashcpy(n->sha1, s->sha1); else if (*from == ':') { - unsigned long idnum = strtoul(from + 1, NULL, 10); + uintmax_t idnum = strtoumax(from + 1, NULL, 10); struct object_entry *oe = find_mark(idnum); if (oe->type != OBJ_COMMIT) - die("Mark :%lu not a commit", idnum); + die("Mark :%ju not a commit", idnum); hashcpy(n->sha1, oe->sha1); } else if (get_sha1(from, n->sha1)) die("Invalid ref name or SHA1 expression: %s", from); @@ -1650,7 +1652,7 @@ static void cmd_new_commit() fputc('"', branch_log); } else fprintf(branch_log, "%s", b->name); - fprintf(branch_log," :%lu %s\n",next_mark,sha1_to_hex(b->sha1)); + fprintf(branch_log," :%ju %s\n",next_mark,sha1_to_hex(b->sha1)); } } @@ -1665,7 +1667,7 @@ static void cmd_new_tag() void *msg; size_t msglen; struct tag *t; - unsigned long from_mark = 0; + uintmax_t from_mark = 0; unsigned char sha1[20]; /* Obtain the new tag name from the rest of our command */ @@ -1704,10 +1706,10 @@ static void cmd_new_tag() if (s) { hashcpy(sha1, s->sha1); } else if (*from == ':') { - from_mark = strtoul(from + 1, NULL, 10); + from_mark = strtoumax(from + 1, NULL, 10); struct object_entry *oe = find_mark(from_mark); if (oe->type != OBJ_COMMIT) - die("Mark :%lu not a commit", from_mark); + die("Mark :%ju not a commit", from_mark); hashcpy(sha1, oe->sha1); } else if (!get_sha1(from, sha1)) { unsigned long size; @@ -1758,7 +1760,7 @@ static void cmd_new_tag() fputc('"', branch_log); } else fprintf(branch_log, "%s", t->name); - fprintf(branch_log," :%lu %s\n",from_mark,sha1_to_hex(t->sha1)); + fprintf(branch_log," :%ju %s\n",from_mark,sha1_to_hex(t->sha1)); } } @@ -1806,8 +1808,8 @@ static const char fast_import_usage[] = int main(int argc, const char **argv) { int i; - unsigned long est_obj_cnt = object_entry_alloc; - unsigned long duplicate_count; + uintmax_t est_obj_cnt = object_entry_alloc; + uintmax_t duplicate_count; setup_ident(); git_config(git_default_config); @@ -1818,11 +1820,11 @@ int main(int argc, const char **argv) if (*a != '-' || !strcmp(a, "--")) break; else if (!strncmp(a, "--objects=", 10)) - est_obj_cnt = strtoul(a + 10, NULL, 0); + est_obj_cnt = strtoumax(a + 10, NULL, 0); else if (!strncmp(a, "--max-objects-per-pack=", 23)) - max_objects = strtoul(a + 23, NULL, 0); + max_objects = strtoumax(a + 23, NULL, 0); else if (!strncmp(a, "--max-pack-size=", 16)) - max_packsize = strtoul(a + 16, NULL, 0) * 1024 * 1024; + max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024; else if (!strncmp(a, "--depth=", 8)) max_depth = strtoul(a + 8, NULL, 0); else if (!strncmp(a, "--active-branches=", 18)) @@ -1875,23 +1877,24 @@ int main(int argc, const char **argv) if (branch_log) fclose(branch_log); + duplicate_count = 0; for (i = 0; i < ARRAY_SIZE(duplicate_count_by_type); i++) duplicate_count += duplicate_count_by_type[i]; fprintf(stderr, "%s statistics:\n", argv[0]); fprintf(stderr, "---------------------------------------------------------------------\n"); - fprintf(stderr, "Alloc'd objects: %10lu (%10lu overflow )\n", alloc_count, alloc_count - est_obj_cnt); - fprintf(stderr, "Total objects: %10lu (%10lu duplicates )\n", object_count, duplicate_count); - fprintf(stderr, " blobs : %10lu (%10lu duplicates %10lu deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]); - fprintf(stderr, " trees : %10lu (%10lu duplicates %10lu deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]); - fprintf(stderr, " commits: %10lu (%10lu duplicates %10lu deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]); - fprintf(stderr, " tags : %10lu (%10lu duplicates %10lu deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]); + fprintf(stderr, "Alloc'd objects: %10ju (%10ju overflow )\n", alloc_count, alloc_count - est_obj_cnt); + fprintf(stderr, "Total objects: %10ju (%10ju duplicates )\n", object_count, duplicate_count); + fprintf(stderr, " blobs : %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]); + fprintf(stderr, " trees : %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]); + fprintf(stderr, " commits: %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]); + fprintf(stderr, " tags : %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]); fprintf(stderr, "Total branches: %10lu (%10lu loads )\n", branch_count, branch_load_count); - fprintf(stderr, " marks: %10u (%10lu unique )\n", (1 << marks->shift) * 1024, marks_set_count); + fprintf(stderr, " marks: %10ju (%10ju unique )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count); fprintf(stderr, " atoms: %10u\n", atom_cnt); - fprintf(stderr, "Memory total: %10lu KiB\n", (total_allocd + alloc_count*sizeof(struct object_entry))/1024); + fprintf(stderr, "Memory total: %10ju KiB\n", (total_allocd + alloc_count*sizeof(struct object_entry))/1024); fprintf(stderr, " pools: %10lu KiB\n", total_allocd/1024); - fprintf(stderr, " objects: %10lu KiB\n", (alloc_count*sizeof(struct object_entry))/1024); + fprintf(stderr, " objects: %10ju KiB\n", (alloc_count*sizeof(struct object_entry))/1024); fprintf(stderr, "---------------------------------------------------------------------\n"); pack_report(); fprintf(stderr, "---------------------------------------------------------------------\n"); diff --git a/git-compat-util.h b/git-compat-util.h index 8781e8e22d..614583e56a 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include From 6cf092619376f5bf60987f146d142497ded2f718 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 16 Jan 2007 00:35:41 -0500 Subject: [PATCH 208/548] Replace redundant yread() with read_in_full() in fast-import. Prior to git having read_in_full() fast-import used its own private function yread to perform the header reading task. No sense in keeping that around now that read_in_full is a public, stable function. Signed-off-by: Shawn O. Pearce --- fast-import.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/fast-import.c b/fast-import.c index ebffa7c904..938707c5bd 100644 --- a/fast-import.c +++ b/fast-import.c @@ -590,19 +590,6 @@ static void release_tree_entry(struct tree_entry *e) avail_tree_entry = e; } -static void yread(int fd, void *buffer, size_t length) -{ - ssize_t ret = 0; - while (ret < length) { - ssize_t size = xread(fd, (char *) buffer + ret, length - ret); - if (!size) - die("Read from descriptor %i: end of stream", fd); - if (size < 0) - die("Read from descriptor %i: %s", fd, strerror(errno)); - ret += size; - } -} - static void start_packfile() { struct packed_git *p; @@ -642,7 +629,8 @@ static void fixup_header_footer() die("Failed seeking to start: %s", strerror(errno)); SHA1_Init(&c); - yread(pack_fd, hdr, 8); + if (read_in_full(pack_fd, hdr, 8) != 8) + die("Unable to reread header of %s", pack_data->pack_name); SHA1_Update(&c, hdr, 8); cnt = htonl(object_count); From 09543c96bbe41d312bc002c293a193aa328c839d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 16 Jan 2007 00:44:48 -0500 Subject: [PATCH 209/548] Reuse sha1 in packed_git in fast-import. Rather than maintaing our own packfile level sha1 variable we can make use of the one already available in struct packed_git. Its meant for the SHA1 of the index but it can also hold the SHA1 of the packfile itself between final checksumming of the packfile and creation of the index. Signed-off-by: Shawn O. Pearce --- fast-import.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fast-import.c b/fast-import.c index 938707c5bd..5767e808c6 100644 --- a/fast-import.c +++ b/fast-import.c @@ -248,7 +248,6 @@ static struct packed_git *pack_data; static struct packed_git **all_packs; static int pack_fd; static unsigned long pack_size; -static unsigned char pack_sha1[20]; /* Table of objects we've written. */ static unsigned int object_entry_alloc = 5000; @@ -646,8 +645,8 @@ static void fixup_header_footer() } free(buf); - SHA1_Final(pack_sha1, &c); - write_or_die(pack_fd, pack_sha1, sizeof(pack_sha1)); + SHA1_Final(pack_data->sha1, &c); + write_or_die(pack_fd, pack_data->sha1, sizeof(pack_data->sha1)); } static int oecmp (const void *a_, const void *b_) @@ -697,8 +696,8 @@ static void write_index(const char *idx_name) sha1write(f, &offset, 4); sha1write(f, (*c)->sha1, sizeof((*c)->sha1)); } - sha1write(f, pack_sha1, sizeof(pack_sha1)); - sha1close(f, NULL, 1); + sha1write(f, pack_data->sha1, sizeof(pack_data->sha1)); + sha1close(f, pack_data->sha1, 1); free(idx); } From 8455e48476634eeff6fd2cd4f245cadfef14bbc8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 16 Jan 2007 01:15:31 -0500 Subject: [PATCH 210/548] Use .keep files in fast-import during processing. Because fast-import automatically updates all references (heads and tags) at the end of its run the repository is corrupt unless the objects are available in the .git/objects/pack directory prior to the refs being modified. The easiest way to ensure that is true is to move the packfile and its associated index directly into the .git/objects/pack directory as soon as we have finished output to it. But the only safe way to do this is to create the a temporary .keep file for that pack, so we use the same tricks that index-pack uses when its being invoked by receive-pack. Signed-off-by: Shawn O. Pearce --- fast-import.c | 91 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 20 deletions(-) diff --git a/fast-import.c b/fast-import.c index 5767e808c6..393020504a 100644 --- a/fast-import.c +++ b/fast-import.c @@ -241,9 +241,7 @@ static unsigned int atom_cnt; static struct atom_str **atom_table; /* The .pack file being generated */ -static const char *base_name; static unsigned int pack_id; -static char *idx_name; static struct packed_git *pack_data; static struct packed_git **all_packs; static int pack_fd; @@ -591,17 +589,17 @@ static void release_tree_entry(struct tree_entry *e) static void start_packfile() { + static char tmpfile[PATH_MAX]; struct packed_git *p; struct pack_header hdr; - idx_name = xmalloc(strlen(base_name) + 11); - p = xcalloc(1, sizeof(*p) + strlen(base_name) + 13); - sprintf(p->pack_name, "%s%5.5i.pack", base_name, pack_id + 1); - sprintf(idx_name, "%s%5.5i.idx", base_name, pack_id + 1); - - pack_fd = open(p->pack_name, O_RDWR|O_CREAT|O_EXCL, 0666); + snprintf(tmpfile, sizeof(tmpfile), + "%s/pack_XXXXXX", get_object_directory()); + pack_fd = mkstemp(tmpfile); if (pack_fd < 0) - die("Can't create %s: %s", p->pack_name, strerror(errno)); + die("Can't create %s: %s", tmpfile, strerror(errno)); + p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2); + strcpy(p->pack_name, tmpfile); p->pack_fd = pack_fd; hdr.hdr_signature = htonl(PACK_SIGNATURE); @@ -656,13 +654,15 @@ static int oecmp (const void *a_, const void *b_) return hashcmp(a->sha1, b->sha1); } -static void write_index(const char *idx_name) +static char* create_index() { + static char tmpfile[PATH_MAX]; + SHA_CTX ctx; struct sha1file *f; struct object_entry **idx, **c, **last, *e; struct object_entry_pool *o; unsigned int array[256]; - int i; + int i, idx_fd; /* Build the sorted table of object IDs. */ idx = xmalloc(object_count * sizeof(struct object_entry*)); @@ -689,16 +689,68 @@ static void write_index(const char *idx_name) c = next; } - f = sha1create("%s", idx_name); + snprintf(tmpfile, sizeof(tmpfile), + "%s/index_XXXXXX", get_object_directory()); + idx_fd = mkstemp(tmpfile); + if (idx_fd < 0) + die("Can't create %s: %s", tmpfile, strerror(errno)); + f = sha1fd(idx_fd, tmpfile); sha1write(f, array, 256 * sizeof(int)); + SHA1_Init(&ctx); for (c = idx; c != last; c++) { unsigned int offset = htonl((*c)->offset); sha1write(f, &offset, 4); sha1write(f, (*c)->sha1, sizeof((*c)->sha1)); + SHA1_Update(&ctx, (*c)->sha1, 20); } sha1write(f, pack_data->sha1, sizeof(pack_data->sha1)); - sha1close(f, pack_data->sha1, 1); + sha1close(f, NULL, 1); free(idx); + SHA1_Final(pack_data->sha1, &ctx); + return tmpfile; +} + +static char* keep_pack(char *curr_index_name) +{ + static char name[PATH_MAX]; + static char *keep_msg = "fast-import"; + int keep_fd; + + chmod(pack_data->pack_name, 0444); + chmod(curr_index_name, 0444); + + snprintf(name, sizeof(name), "%s/pack/pack-%s.keep", + get_object_directory(), sha1_to_hex(pack_data->sha1)); + keep_fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600); + if (keep_fd < 0) + die("cannot create keep file"); + write(keep_fd, keep_msg, strlen(keep_msg)); + close(keep_fd); + + snprintf(name, sizeof(name), "%s/pack/pack-%s.pack", + get_object_directory(), sha1_to_hex(pack_data->sha1)); + if (move_temp_to_file(pack_data->pack_name, name)) + die("cannot store pack file"); + printf("%s\n", name); + + snprintf(name, sizeof(name), "%s/pack/pack-%s.idx", + get_object_directory(), sha1_to_hex(pack_data->sha1)); + if (move_temp_to_file(curr_index_name, name)) + die("cannot store index file"); + return name; +} + +static void unkeep_all_packs() +{ + static char name[PATH_MAX]; + int k; + + for (k = 0; k < pack_id; k++) { + struct packed_git *p = all_packs[k]; + snprintf(name, sizeof(name), "%s/pack/pack-%s.keep", + get_object_directory(), sha1_to_hex(p->sha1)); + unlink(name); + } } static void end_packfile() @@ -706,10 +758,10 @@ static void end_packfile() struct packed_git *old_p = pack_data, *new_p; if (object_count) { + char *idx_name; + fixup_header_footer(); - write_index(idx_name); - fprintf(stdout, "%s\n", old_p->pack_name); - fflush(stdout); + idx_name = keep_pack(create_index()); /* Register the packfile with core git's machinary. */ new_p = add_packed_git(idx_name, strlen(idx_name), 1); @@ -725,7 +777,6 @@ static void end_packfile() unlink(old_p->pack_name); } free(old_p); - free(idx_name); /* We can't carry a delta across packfiles. */ free(last_blob.data); @@ -1790,7 +1841,7 @@ static void cmd_checkpoint() } static const char fast_import_usage[] = -"git-fast-import [--objects=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file] [--branch-log=log] temp.pack"; +"git-fast-import [--objects=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file] [--branch-log=log]"; int main(int argc, const char **argv) { @@ -1826,9 +1877,8 @@ int main(int argc, const char **argv) else die("unknown option %s", a); } - if ((i+1) != argc) + if (i != argc) usage(fast_import_usage); - base_name = argv[i]; alloc_objects(est_obj_cnt); strbuf_init(&command_buf); @@ -1860,6 +1910,7 @@ int main(int argc, const char **argv) dump_branches(); dump_tags(); + unkeep_all_packs(); dump_marks(); if (branch_log) fclose(branch_log); From 1280158738333109cf0ada2fb378db2cdf7296ad Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 16 Jan 2007 01:17:47 -0500 Subject: [PATCH 211/548] Ensure we close the packfile after creating it in fast-import. Because we are renaming the packfile into its file destination we need to be sure its not open when the rename is called, otherwise some operating systems (e.g. Windows) may prevent the rename from occurring. Signed-off-by: Shawn O. Pearce --- fast-import.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fast-import.c b/fast-import.c index 393020504a..a9cf22dfe2 100644 --- a/fast-import.c +++ b/fast-import.c @@ -645,6 +645,7 @@ static void fixup_header_footer() SHA1_Final(pack_data->sha1, &c); write_or_die(pack_fd, pack_data->sha1, sizeof(pack_data->sha1)); + close(pack_fd); } static int oecmp (const void *a_, const void *b_) @@ -768,14 +769,11 @@ static void end_packfile() if (!new_p) die("core git rejected index %s", idx_name); new_p->windows = old_p->windows; - new_p->pack_fd = old_p->pack_fd; all_packs[pack_id++] = new_p; install_packed_git(new_p); } - else { - close(pack_fd); + else unlink(old_p->pack_name); - } free(old_p); /* We can't carry a delta across packfiles. */ From 0fcbcae75372f96539ba0f9598112c417d81ab0d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 16 Jan 2007 01:20:57 -0500 Subject: [PATCH 212/548] Remove unnecessary pack_fd global in fast-import. Much like the pack_sha1 the pack_fd is an unnecessary global variable, we already have the fd stored in our struct packed_git *pack_data so that the core library functions in sha1_file.c are able to lookup and decompress object data that we have previously written. Keeping an extra copy of this value in our own variable is just a hold-over from earlier versions of fast-import and is now completely unnecessary. Signed-off-by: Shawn O. Pearce --- fast-import.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/fast-import.c b/fast-import.c index a9cf22dfe2..281b8f6a5e 100644 --- a/fast-import.c +++ b/fast-import.c @@ -244,7 +244,6 @@ static struct atom_str **atom_table; static unsigned int pack_id; static struct packed_git *pack_data; static struct packed_git **all_packs; -static int pack_fd; static unsigned long pack_size; /* Table of objects we've written. */ @@ -592,6 +591,7 @@ static void start_packfile() static char tmpfile[PATH_MAX]; struct packed_git *p; struct pack_header hdr; + int pack_fd; snprintf(tmpfile, sizeof(tmpfile), "%s/pack_XXXXXX", get_object_directory()); @@ -605,7 +605,7 @@ static void start_packfile() hdr.hdr_signature = htonl(PACK_SIGNATURE); hdr.hdr_version = htonl(2); hdr.hdr_entries = 0; - write_or_die(pack_fd, &hdr, sizeof(hdr)); + write_or_die(p->pack_fd, &hdr, sizeof(hdr)); pack_data = p; pack_size = sizeof(hdr); @@ -617,6 +617,7 @@ static void start_packfile() static void fixup_header_footer() { + int pack_fd = pack_data->pack_fd; SHA_CTX c; char hdr[8]; unsigned long cnt; @@ -912,23 +913,23 @@ static int store_object( last->depth++; hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr); - write_or_die(pack_fd, hdr, hdrlen); + write_or_die(pack_data->pack_fd, hdr, hdrlen); pack_size += hdrlen; hdr[pos] = ofs & 127; while (ofs >>= 7) hdr[--pos] = 128 | (--ofs & 127); - write_or_die(pack_fd, hdr + pos, sizeof(hdr) - pos); + write_or_die(pack_data->pack_fd, hdr + pos, sizeof(hdr) - pos); pack_size += sizeof(hdr) - pos; } else { if (last) last->depth = 0; hdrlen = encode_header(type, datlen, hdr); - write_or_die(pack_fd, hdr, hdrlen); + write_or_die(pack_data->pack_fd, hdr, hdrlen); pack_size += hdrlen; } - write_or_die(pack_fd, out, s.total_out); + write_or_die(pack_data->pack_fd, out, s.total_out); pack_size += s.total_out; free(out); From eec11c24840bfc5293a80fed3c3b1e5bc10ac453 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 16 Jan 2007 04:25:12 -0500 Subject: [PATCH 213/548] Correct max_packsize default in fast-import. Apparently amd64 has defined 'unsigned long' to be a 64 bit value, which means -1 was way over the 4 GiB packfile limit. Whoops. Signed-off-by: Shawn O. Pearce --- fast-import.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fast-import.c b/fast-import.c index 281b8f6a5e..8342314bb0 100644 --- a/fast-import.c +++ b/fast-import.c @@ -217,7 +217,7 @@ struct hash_list /* Configured limits on output */ static unsigned long max_depth = 10; -static unsigned long max_packsize = -1; +static unsigned long max_packsize = (1LL << 32) - 1; static uintmax_t max_objects = -1; /* Stats and misc. counters */ From a7ddc48765ff2e4f6601ea146cba4283a342e0b1 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 16 Jan 2007 04:55:41 -0500 Subject: [PATCH 214/548] Correct object_count type and stat output in fast-import. Since object_count is limited to 'unsigned long' (really an unsigned 32 bit integer value) by the pack file format we may as well use exactly that type here in fast-import for that counter. An earlier change by me incorrectly made it uintmax_t. But since object_count is a counter for the current packfile only, we don't want to output its value at the end. Instead we should sum up the individual type counters and report that total, as that will cover all of the packfiles. Signed-off-by: Shawn O. Pearce --- fast-import.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fast-import.c b/fast-import.c index 8342314bb0..3992af5f25 100644 --- a/fast-import.c +++ b/fast-import.c @@ -222,11 +222,11 @@ static uintmax_t max_objects = -1; /* Stats and misc. counters */ static uintmax_t alloc_count; -static uintmax_t object_count; static uintmax_t marks_set_count; static uintmax_t object_count_by_type[1 << TYPE_BITS]; static uintmax_t duplicate_count_by_type[1 << TYPE_BITS]; static uintmax_t delta_count_by_type[1 << TYPE_BITS]; +static unsigned long object_count; static unsigned long branch_count; static unsigned long branch_load_count; @@ -1846,7 +1846,7 @@ int main(int argc, const char **argv) { int i; uintmax_t est_obj_cnt = object_entry_alloc; - uintmax_t duplicate_count; + uintmax_t total_count, duplicate_count; setup_ident(); git_config(git_default_config); @@ -1914,6 +1914,9 @@ int main(int argc, const char **argv) if (branch_log) fclose(branch_log); + total_count = 0; + for (i = 0; i < ARRAY_SIZE(object_count_by_type); i++) + total_count += object_count_by_type[i]; duplicate_count = 0; for (i = 0; i < ARRAY_SIZE(duplicate_count_by_type); i++) duplicate_count += duplicate_count_by_type[i]; @@ -1921,7 +1924,7 @@ int main(int argc, const char **argv) fprintf(stderr, "%s statistics:\n", argv[0]); fprintf(stderr, "---------------------------------------------------------------------\n"); fprintf(stderr, "Alloc'd objects: %10ju (%10ju overflow )\n", alloc_count, alloc_count - est_obj_cnt); - fprintf(stderr, "Total objects: %10ju (%10ju duplicates )\n", object_count, duplicate_count); + fprintf(stderr, "Total objects: %10ju (%10ju duplicates )\n", total_count, duplicate_count); fprintf(stderr, " blobs : %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]); fprintf(stderr, " trees : %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]); fprintf(stderr, " commits: %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]); From 2369ed79071edf0f040eb2c280e1e2cf9a883bb9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 16 Jan 2007 16:18:44 -0500 Subject: [PATCH 215/548] Print out the edge commits for each packfile in fast-import. To help callers repack very large repositories into a series of packfiles fast-import now outputs the last commits/tags it wrote to a packfile when it prints out the packfile name. This information can be feed to pack-objects --revs to repack. For the first pack of an initial import this is pretty easy (just feed those SHA1s on stdin) but for subsequent packs you want to feed the subsequent pack's final SHA1s but also all prior pack's SHA1s prefixed with the negation operator. This way the prior pack's data does not get included into the subsequent pack. Signed-off-by: Shawn O. Pearce --- fast-import.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/fast-import.c b/fast-import.c index 3992af5f25..84dfde9d2f 100644 --- a/fast-import.c +++ b/fast-import.c @@ -193,6 +193,7 @@ struct branch const char *name; unsigned long last_commit; struct tree_entry branch_tree; + unsigned int pack_id; unsigned char sha1[20]; }; @@ -200,6 +201,7 @@ struct tag { struct tag *next_tag; const char *name; + unsigned int pack_id; unsigned char sha1[20]; }; @@ -733,7 +735,6 @@ static char* keep_pack(char *curr_index_name) get_object_directory(), sha1_to_hex(pack_data->sha1)); if (move_temp_to_file(pack_data->pack_name, name)) die("cannot store pack file"); - printf("%s\n", name); snprintf(name, sizeof(name), "%s/pack/pack-%s.idx", get_object_directory(), sha1_to_hex(pack_data->sha1)); @@ -761,6 +762,9 @@ static void end_packfile() if (object_count) { char *idx_name; + int i; + struct branch *b; + struct tag *t; fixup_header_footer(); idx_name = keep_pack(create_index()); @@ -770,8 +774,24 @@ static void end_packfile() if (!new_p) die("core git rejected index %s", idx_name); new_p->windows = old_p->windows; - all_packs[pack_id++] = new_p; + all_packs[pack_id] = new_p; install_packed_git(new_p); + + /* Print the boundary */ + fprintf(stdout, "%s:", new_p->pack_name); + for (i = 0; i < branch_table_sz; i++) { + for (b = branch_table[i]; b; b = b->table_next_branch) { + if (b->pack_id == pack_id) + fprintf(stdout, " %s", sha1_to_hex(b->sha1)); + } + } + for (t = first_tag; t; t = t->next_tag) { + if (t->pack_id == pack_id) + fprintf(stdout, " %s", sha1_to_hex(t->sha1)); + } + fputc('\n', stdout); + + pack_id++; } else unlink(old_p->pack_name); @@ -1679,6 +1699,7 @@ static void cmd_new_commit() new_data.buffer, sp - (char*)new_data.buffer, NULL, b->sha1, next_mark); b->last_commit = object_count_by_type[OBJ_COMMIT]; + b->pack_id = pack_id; if (branch_log) { int need_dq = quote_c_style(b->name, NULL, NULL, 0); @@ -1787,6 +1808,7 @@ static void cmd_new_tag() store_object(OBJ_TAG, new_data.buffer, sp - (char*)new_data.buffer, NULL, t->sha1, 0); + t->pack_id = pack_id; if (branch_log) { int need_dq = quote_c_style(t->name, NULL, NULL, 0); From 2104838bf9b97066f21e4c32efdfa424d41e6b98 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 17 Jan 2007 00:33:18 -0500 Subject: [PATCH 216/548] Corrected BNF input documentation for fast-import. Now that fast-import uses uintmax_t (the largest available unsigned integer type) for marks we don't want to say its an unsigned 32 bit integer in ASCII base 10 notation. It could be much larger, especially on 64 bit systems, and especially if a frontend uses a very large number of marks (1 per file revision on a very, very large import). Signed-off-by: Shawn O. Pearce --- fast-import.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fast-import.c b/fast-import.c index 84dfde9d2f..f1b26d103f 100644 --- a/fast-import.c +++ b/fast-import.c @@ -72,6 +72,7 @@ Format of STDIN stream: path_str ::= path | '"' quoted(path) '"' ; declen ::= # unsigned 32 bit value, ascii base10 notation; + bigint ::= # unsigned integer value, ascii base10 notation; binary_data ::= # file content, not interpreted; sp ::= # ASCII space character; @@ -81,7 +82,7 @@ Format of STDIN stream: # an idnum. This is to distinguish it from a ref or tag name as # GIT does not permit ':' in ref or tag strings. # - idnum ::= ':' declen; + idnum ::= ':' bigint; path ::= # GIT style file path, e.g. "a/b/c"; ref ::= # GIT ref name, e.g. "refs/heads/MOZ_GECKO_EXPERIMENT"; tag ::= # GIT tag name, e.g. "FIREFOX_1_5"; From 6f64f6d9d2b12cdae1648cbf536685c888f3b981 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 17 Jan 2007 00:57:23 -0500 Subject: [PATCH 217/548] Correct a few types to be unsigned in fast-import. The length of an atom string cannot be negative. So make it explicit and declare it as an unsigned value. The shift width in a mark table node also cannot be negative. I'm also moving it to after the pointer arrays to prevent any possible alignment problems on a 64 bit system. Signed-off-by: Shawn O. Pearce --- fast-import.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fast-import.c b/fast-import.c index f1b26d103f..2c500d6be3 100644 --- a/fast-import.c +++ b/fast-import.c @@ -130,11 +130,11 @@ struct object_entry_pool struct mark_set { - int shift; union { struct object_entry *marked[1024]; struct mark_set *sets[1024]; } data; + unsigned int shift; }; struct last_object @@ -157,7 +157,7 @@ struct mem_pool struct atom_str { struct atom_str *next_atom; - int str_len; + unsigned int str_len; char str_dat[FLEX_ARRAY]; /* more */ }; @@ -192,8 +192,8 @@ struct branch struct branch *table_next_branch; struct branch *active_next_branch; const char *name; - unsigned long last_commit; struct tree_entry branch_tree; + unsigned long last_commit; unsigned int pack_id; unsigned char sha1[20]; }; From fd99224eec67d89f970b207e7db031b7c58e812e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 17 Jan 2007 01:47:25 -0500 Subject: [PATCH 218/548] Declare no-arg functions as (void) in fast-import. Apparently the git convention is to declare any function which takes no arguments as taking void. I did not do this during the early fast-import development, but should have. Signed-off-by: Shawn O. Pearce --- fast-import.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/fast-import.c b/fast-import.c index 2c500d6be3..84f855fb8e 100644 --- a/fast-import.c +++ b/fast-import.c @@ -561,7 +561,7 @@ static struct tree_content* grow_tree_content( return r; } -static struct tree_entry* new_tree_entry() +static struct tree_entry* new_tree_entry(void) { struct tree_entry *e; @@ -589,7 +589,7 @@ static void release_tree_entry(struct tree_entry *e) avail_tree_entry = e; } -static void start_packfile() +static void start_packfile(void) { static char tmpfile[PATH_MAX]; struct packed_git *p; @@ -618,7 +618,7 @@ static void start_packfile() all_packs[pack_id] = p; } -static void fixup_header_footer() +static void fixup_header_footer(void) { int pack_fd = pack_data->pack_fd; SHA_CTX c; @@ -659,7 +659,7 @@ static int oecmp (const void *a_, const void *b_) return hashcmp(a->sha1, b->sha1); } -static char* create_index() +static char* create_index(void) { static char tmpfile[PATH_MAX]; SHA_CTX ctx; @@ -744,7 +744,7 @@ static char* keep_pack(char *curr_index_name) return name; } -static void unkeep_all_packs() +static void unkeep_all_packs(void) { static char name[PATH_MAX]; int k; @@ -757,7 +757,7 @@ static void unkeep_all_packs() } } -static void end_packfile() +static void end_packfile(void) { struct packed_git *old_p = pack_data, *new_p; @@ -806,7 +806,7 @@ static void end_packfile() last_blob.depth = 0; } -static void checkpoint() +static void checkpoint(void) { end_packfile(); start_packfile(); @@ -1253,7 +1253,7 @@ del_entry: return 1; } -static void dump_branches() +static void dump_branches(void) { static const char *msg = "fast-import"; unsigned int i; @@ -1269,7 +1269,7 @@ static void dump_branches() } } -static void dump_tags() +static void dump_tags(void) { static const char *msg = "fast-import"; struct tag *t; @@ -1304,7 +1304,7 @@ static void dump_marks_helper(FILE *f, } } -static void dump_marks() +static void dump_marks(void) { if (mark_file) { @@ -1314,12 +1314,12 @@ static void dump_marks() } } -static void read_next_command() +static void read_next_command(void) { read_line(&command_buf, stdin, '\n'); } -static void cmd_mark() +static void cmd_mark(void) { if (!strncmp("mark :", command_buf.buf, 6)) { next_mark = strtoumax(command_buf.buf + 6, NULL, 10); @@ -1355,7 +1355,7 @@ static void* cmd_data (size_t *size) return buffer; } -static void cmd_new_blob() +static void cmd_new_blob(void) { size_t l; void *d; @@ -1368,7 +1368,7 @@ static void cmd_new_blob() free(d); } -static void unload_one_branch() +static void unload_one_branch(void) { while (cur_active_branches && cur_active_branches >= max_active_branches) { @@ -1601,7 +1601,7 @@ static struct hash_list* cmd_merge(unsigned int *count) return list; } -static void cmd_new_commit() +static void cmd_new_commit(void) { struct branch *b; void *msg; @@ -1715,7 +1715,7 @@ static void cmd_new_commit() } } -static void cmd_new_tag() +static void cmd_new_tag(void) { char *str_uq; const char *endp; @@ -1824,7 +1824,7 @@ static void cmd_new_tag() } } -static void cmd_reset_branch() +static void cmd_reset_branch(void) { struct branch *b; char *str_uq; @@ -1855,7 +1855,7 @@ static void cmd_reset_branch() cmd_from(b); } -static void cmd_checkpoint() +static void cmd_checkpoint(void) { if (object_count) checkpoint(); From 69e74e7412603dd536695c3d6a397673e8ae2bd2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 17 Jan 2007 02:42:43 -0500 Subject: [PATCH 219/548] Correct packfile edge output in fast-import. Branches are only contained by a packfile if the branch actually had its most recent commit in that packfile. So new branches are set to MAX_PACK_ID to ensure they don't cause their commit to list as part of the first packfile when it closes out if the commit was actually in existance before fast-import started. Also corrected the type of last_commit to be umaxint_t to prevent overflow and wraparound on very large imports. Though that is highly unlikely to occur as we're talking 4 billion commits, which no real project has right now. Signed-off-by: Shawn O. Pearce --- fast-import.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/fast-import.c b/fast-import.c index 84f855fb8e..a3073c5f03 100644 --- a/fast-import.c +++ b/fast-import.c @@ -111,12 +111,15 @@ Format of STDIN stream: #include "strbuf.h" #include "quote.h" +#define PACK_ID_BITS 16 +#define MAX_PACK_ID ((1<table_next_branch = branch_table[hc]; b->branch_tree.versions[0].mode = S_IFDIR; b->branch_tree.versions[1].mode = S_IFDIR; + b->pack_id = MAX_PACK_ID; branch_table[hc] = b; branch_count++; return b; @@ -1696,11 +1700,11 @@ static void cmd_new_commit(void) free(committer); free(msg); - store_object(OBJ_COMMIT, + if (!store_object(OBJ_COMMIT, new_data.buffer, sp - (char*)new_data.buffer, - NULL, b->sha1, next_mark); + NULL, b->sha1, next_mark)) + b->pack_id = pack_id; b->last_commit = object_count_by_type[OBJ_COMMIT]; - b->pack_id = pack_id; if (branch_log) { int need_dq = quote_c_style(b->name, NULL, NULL, 0); @@ -1807,9 +1811,12 @@ static void cmd_new_tag(void) free(tagger); free(msg); - store_object(OBJ_TAG, new_data.buffer, sp - (char*)new_data.buffer, - NULL, t->sha1, 0); - t->pack_id = pack_id; + if (store_object(OBJ_TAG, new_data.buffer, + sp - (char*)new_data.buffer, + NULL, t->sha1, 0)) + t->pack_id = MAX_PACK_ID; + else + t->pack_id = pack_id; if (branch_log) { int need_dq = quote_c_style(t->name, NULL, NULL, 0); From 566f44252b00003d1f4e7baaaf709d74bf73770f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 18 Jan 2007 11:26:06 -0500 Subject: [PATCH 220/548] Always use struct pack_header for pack header in fast-import. Previously we were using 'unsigned int' to update the hdr_entries field of the pack header after the file had been completed and was being hashed. This may not be 32 bits on all platforms. Instead we want to always uint32_t. I'm actually cheating here by just using the pack_header like the rest of Git and letting the struct definition declare the correct type. Right now that field is still 'unsigned int' (wrong) but a pending change submitted by Simon 'corecode' Schubert changes it to uint32_t. After that change is merged in fast-import will do the right thing all of the time. Signed-off-by: Shawn O. Pearce --- fast-import.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/fast-import.c b/fast-import.c index a3073c5f03..fb7d912eff 100644 --- a/fast-import.c +++ b/fast-import.c @@ -624,29 +624,31 @@ static void start_packfile(void) static void fixup_header_footer(void) { + static const int buf_sz = 128 * 1024; int pack_fd = pack_data->pack_fd; SHA_CTX c; - char hdr[8]; - unsigned long cnt; + struct pack_header hdr; char *buf; if (lseek(pack_fd, 0, SEEK_SET) != 0) die("Failed seeking to start: %s", strerror(errno)); + if (read_in_full(pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr)) + die("Unable to reread header of %s", pack_data->pack_name); + if (lseek(pack_fd, 0, SEEK_SET) != 0) + die("Failed seeking to start: %s", strerror(errno)); + hdr.hdr_entries = htonl(object_count); + write_or_die(pack_fd, &hdr, sizeof(hdr)); SHA1_Init(&c); - if (read_in_full(pack_fd, hdr, 8) != 8) - die("Unable to reread header of %s", pack_data->pack_name); - SHA1_Update(&c, hdr, 8); + SHA1_Update(&c, &hdr, sizeof(hdr)); - cnt = htonl(object_count); - SHA1_Update(&c, &cnt, 4); - write_or_die(pack_fd, &cnt, 4); - - buf = xmalloc(128 * 1024); + buf = xmalloc(buf_sz); for (;;) { - size_t n = xread(pack_fd, buf, 128 * 1024); - if (n <= 0) + size_t n = xread(pack_fd, buf, buf_sz); + if (!n) break; + if (n < 0) + die("Failed to checksum %s", pack_data->pack_name); SHA1_Update(&c, buf, n); } free(buf); From ebea9dd4f1b62cb3c8302f10aaca3af0231e9818 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 18 Jan 2007 11:30:17 -0500 Subject: [PATCH 221/548] Use fixed-size integers when writing out the index in fast-import. Currently the pack .idx file format uses 32-bit unsigned integers for the fan-out table and the object offsets. We had previously defined these as 'unsigned int', but not every system will define that type to be a 32 bit value. To ensure maximum portability we should always use 'uint32_t'. Signed-off-by: Shawn O. Pearce --- fast-import.c | 4 ++-- git-compat-util.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fast-import.c b/fast-import.c index fb7d912eff..7f519b4de3 100644 --- a/fast-import.c +++ b/fast-import.c @@ -672,7 +672,7 @@ static char* create_index(void) struct sha1file *f; struct object_entry **idx, **c, **last, *e; struct object_entry_pool *o; - unsigned int array[256]; + uint32_t array[256]; int i, idx_fd; /* Build the sorted table of object IDs. */ @@ -709,7 +709,7 @@ static char* create_index(void) sha1write(f, array, 256 * sizeof(int)); SHA1_Init(&ctx); for (c = idx; c != last; c++) { - unsigned int offset = htonl((*c)->offset); + uint32_t offset = htonl((*c)->offset); sha1write(f, &offset, 4); sha1write(f, (*c)->sha1, sizeof((*c)->sha1)); SHA1_Update(&ctx, (*c)->sha1, 20); diff --git a/git-compat-util.h b/git-compat-util.h index 614583e56a..ac06963e8d 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -46,6 +46,7 @@ #include #include #include +#include #include #ifndef NO_ICONV From e5808826c4abe183b4db9bae8f13445624696f66 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 18 Jan 2007 12:00:49 -0500 Subject: [PATCH 222/548] Remove unnecessary options from fast-import. The --objects command line option is rather unnecessary. Internally we allocate objects in 5000 unit blocks, ensuring that any sort of malloc overhead is ammortized over the individual objects to almost nothing. Since most frontends don't know how many objects they will need for a given import run (and its hard for them to predict without just doing the run) we probably won't see anyone using --objects. Further since there's really no major benefit to using the option, most frontends won't even bother supplying it even if they could estimate the number of objects. So I'm removing it. The --max-objects-per-pack option was probably a mistake to even have added in the first place. The packfile format is limited to 4 GiB today; given that objects need at least 3 bytes of data (and probably need even more) there's no way we are going to exceed the limit of 1<<32-1 objects before we reach the file size limit. So I'm removing it (to slightly reduce the complexity of the code) before anyone gets any wise ideas and tries to use it. Signed-off-by: Shawn O. Pearce --- fast-import.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/fast-import.c b/fast-import.c index 7f519b4de3..9a642f2e02 100644 --- a/fast-import.c +++ b/fast-import.c @@ -224,7 +224,6 @@ struct hash_list /* Configured limits on output */ static unsigned long max_depth = 10; static unsigned long max_packsize = (1LL << 32) - 1; -static uintmax_t max_objects = -1; /* Stats and misc. counters */ static uintmax_t alloc_count; @@ -900,9 +899,7 @@ static int store_object( deflateEnd(&s); /* Determine if we should auto-checkpoint. */ - if ((object_count + 1) > max_objects - || (object_count + 1) < object_count - || (pack_size + 60 + s.total_out) > max_packsize + if ((pack_size + 60 + s.total_out) > max_packsize || (pack_size + 60 + s.total_out) < pack_size) { /* This new object needs to *not* have the current pack_id. */ @@ -1872,12 +1869,11 @@ static void cmd_checkpoint(void) } static const char fast_import_usage[] = -"git-fast-import [--objects=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file] [--branch-log=log]"; +"git-fast-import [--depth=n] [--active-branches=n] [--export-marks=marks.file] [--branch-log=log]"; int main(int argc, const char **argv) { int i; - uintmax_t est_obj_cnt = object_entry_alloc; uintmax_t total_count, duplicate_count; setup_ident(); @@ -1888,10 +1884,6 @@ int main(int argc, const char **argv) if (*a != '-' || !strcmp(a, "--")) break; - else if (!strncmp(a, "--objects=", 10)) - est_obj_cnt = strtoumax(a + 10, NULL, 0); - else if (!strncmp(a, "--max-objects-per-pack=", 23)) - max_objects = strtoumax(a + 23, NULL, 0); else if (!strncmp(a, "--max-pack-size=", 16)) max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024; else if (!strncmp(a, "--depth=", 8)) @@ -1911,7 +1903,7 @@ int main(int argc, const char **argv) if (i != argc) usage(fast_import_usage); - alloc_objects(est_obj_cnt); + alloc_objects(object_entry_alloc); strbuf_init(&command_buf); atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*)); @@ -1955,7 +1947,7 @@ int main(int argc, const char **argv) fprintf(stderr, "%s statistics:\n", argv[0]); fprintf(stderr, "---------------------------------------------------------------------\n"); - fprintf(stderr, "Alloc'd objects: %10ju (%10ju overflow )\n", alloc_count, alloc_count - est_obj_cnt); + fprintf(stderr, "Alloc'd objects: %10ju\n", alloc_count); fprintf(stderr, "Total objects: %10ju (%10ju duplicates )\n", total_count, duplicate_count); fprintf(stderr, " blobs : %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]); fprintf(stderr, " trees : %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]); From 3b4dce02752d37c3cef9308eefb01ed758efe323 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 18 Jan 2007 13:14:27 -0500 Subject: [PATCH 223/548] Support delimited data regions in fast-import. During testing its nice to not have to feed the length of a data chunk to the 'data' command of fast-import. Instead we would prefer to be able to establish a data chunk much like shell's << operator and use a line delimiter to denote the end of the input. So now if a data command is started as 'data < --- fast-import.c | 64 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/fast-import.c b/fast-import.c index 9a642f2e02..90adc68042 100644 --- a/fast-import.c +++ b/fast-import.c @@ -50,14 +50,21 @@ Format of STDIN stream: # a new mark directive with the old idnum. # mark ::= 'mark' sp idnum lf; + data ::= (delimited_data | exact_data) + lf; + + # note: delim may be any string but must not contain lf. + # data_line may contain any data but must not be exactly + # delim. + delimited_data ::= 'data' sp '<<' delim lf + (data_line lf)* + delim lf; # note: declen indicates the length of binary_data in bytes. - # declen does not include the lf preceeding or trailing the - # binary data. + # declen does not include the lf preceeding the binary data. # - data ::= 'data' sp declen lf - binary_data - lf; + exact_data ::= 'data' sp declen lf + binary_data; # note: quoted strings are C-style quoting supporting \c for # common escapes of 'c' (e..g \n, \t, \\, \") or \nnn where nnn @@ -1334,21 +1341,48 @@ static void cmd_mark(void) static void* cmd_data (size_t *size) { - size_t n = 0; - void *buffer; size_t length; + char *buffer; if (strncmp("data ", command_buf.buf, 5)) die("Expected 'data n' command, found: %s", command_buf.buf); - length = strtoul(command_buf.buf + 5, NULL, 10); - buffer = xmalloc(length); - - while (n < length) { - size_t s = fread((char*)buffer + n, 1, length - n, stdin); - if (!s && feof(stdin)) - die("EOF in data (%lu bytes remaining)", length - n); - n += s; + if (!strncmp("<<", command_buf.buf + 5, 2)) { + char *term = xstrdup(command_buf.buf + 5 + 2); + size_t sz = 8192, term_len = command_buf.len - 5 - 2; + length = 0; + buffer = xmalloc(sz); + for (;;) { + read_next_command(); + if (command_buf.eof) + die("EOF in data (terminator '%s' not found)", term); + if (term_len == command_buf.len + && !strcmp(term, command_buf.buf)) + break; + if (sz < (length + command_buf.len)) { + sz = sz * 3 / 2 + 16; + if (sz < (length + command_buf.len)) + sz = length + command_buf.len; + buffer = xrealloc(buffer, sz); + } + memcpy(buffer + length, + command_buf.buf, + command_buf.len - 1); + length += command_buf.len - 1; + buffer[length++] = '\n'; + } + free(term); + } + else { + size_t n = 0; + length = strtoul(command_buf.buf + 5, NULL, 10); + buffer = xmalloc(length); + while (n < length) { + size_t s = fread(buffer + n, 1, length - n, stdin); + if (!s && feof(stdin)) + die("EOF in data (%lu bytes remaining)", length - n); + n += s; + } } if (fgetc(stdin) != '\n') From 50aee995121a103fe2698574e7f1d56660a5b89b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 18 Jan 2007 13:26:24 -0500 Subject: [PATCH 224/548] Create test case for fast-import. Now that its easier to craft test cases (thanks to 'data <<') we should start to verify fast-import works as expected. Signed-off-by: Shawn O. Pearce --- t/t9300-fast-import.sh | 184 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100755 t/t9300-fast-import.sh diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh new file mode 100755 index 0000000000..1463476b21 --- /dev/null +++ b/t/t9300-fast-import.sh @@ -0,0 +1,184 @@ +#!/bin/sh +# +# Copyright (c) 2007 Shawn Pearce +# + +test_description='test git-fast-import utility' +. ./test-lib.sh +. ../diff-lib.sh ;# test-lib chdir's into trash + +### +### series A +### + +test_tick +cat >input < $GIT_COMMITTER_DATE +data <expect < $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +initial +EOF +test_expect_success \ + 'A: verify commit' \ + 'git-cat-file commit master | sed 1d >actual && + diff -u expect actual' + +cat >expect <actual && + diff -u expect actual' + +cat >expect <actual && diff -u expect actual' + +cat >expect <actual && diff -u expect actual' + +printf abcd >expect +test_expect_success \ + 'A: verify file4' \ + 'git-cat-file blob master:file4 >actual && diff -u expect actual' + +cat >expect <input < $GIT_COMMITTER_DATE +data <input < $GIT_COMMITTER_DATE +data <expect < $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +second +EOF +test_expect_success \ + 'C: verify commit' \ + 'git-cat-file commit branch | sed 1d >actual && + diff -u expect actual' + +cat >expect <actual +test_expect_success \ + 'C: validate rename result' \ + 'compare_diff_raw expect actual' + +test_done From 8232dc427fb4b92b38e74e9e93b52231a67e354f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 18 Jan 2007 14:49:05 -0500 Subject: [PATCH 225/548] Reduce value duplication in t9300-fast-import. It is error prone to list the value of each file twice, instead we should list the value only once early in the script and reuse the shell variable when we need to access it. Signed-off-by: Shawn O. Pearce --- t/t9300-fast-import.sh | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 1463476b21..40b8c073bd 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -7,6 +7,16 @@ test_description='test git-fast-import utility' . ./test-lib.sh . ../diff-lib.sh ;# test-lib chdir's into trash +file2_data='file2 +second line of EOF' + +file3_data='EOF +in 3rd file + END' + +file4_data=abcd +file4_len=4 + ### ### series A ### @@ -16,22 +26,19 @@ cat >input < $GIT_COMMITTER_DATE @@ -73,24 +80,17 @@ test_expect_success \ 'git-cat-file -p master^{tree} | sed "s/ [0-9a-f]* / /" >actual && diff -u expect actual' -cat >expect <expect test_expect_success \ 'A: verify file2' \ 'git-cat-file blob master:file2 >actual && diff -u expect actual' -cat >expect <expect test_expect_success \ 'A: verify file3' \ 'git-cat-file blob master:file3 >actual && diff -u expect actual' -printf abcd >expect +printf "$file4_data" >expect test_expect_success \ 'A: verify file4' \ 'git-cat-file blob master:file4 >actual && diff -u expect actual' From b715cfbba4083d25ec0d0f94e440ad734607ddb0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 18 Jan 2007 15:17:58 -0500 Subject: [PATCH 226/548] Accept 'inline' file data in fast-import commit structure. Its very annoying to need to specify the file content ahead of a commit and use marks to connect the individual blobs to the commit's file modification entry, especially if the frontend can't/won't generate the blob SHA1s itself. Instead it would much easier to use if we can accept the blob data at the same time as we receive each file_change line. Now fast-import accepts 'inline' instead of a mark idnum or blob SHA1 within the 'M' type file_change command. If an inline is detected the very next line must be a 'data n' command, supplying the file data. Signed-off-by: Shawn O. Pearce --- fast-import.c | 29 ++++++++++++++++----- t/t9300-fast-import.sh | 59 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/fast-import.c b/fast-import.c index 90adc68042..487a91a4ee 100644 --- a/fast-import.c +++ b/fast-import.c @@ -25,10 +25,11 @@ Format of STDIN stream: lf; commit_msg ::= data; - file_change ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf - | 'D' sp path_str lf - ; - mode ::= '644' | '755'; + file_change ::= file_del | file_obm | file_inm; + file_del ::= 'D' sp path_str lf; + file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf; + file_inm ::= 'M' sp mode sp 'inline' sp path_str lf + data; new_tag ::= 'tag' sp tag_str lf 'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf @@ -77,6 +78,10 @@ Format of STDIN stream: sha1exp_str ::= sha1exp | '"' quoted(sha1exp) '"' ; tag_str ::= tag | '"' quoted(tag) '"' ; path_str ::= path | '"' quoted(path) '"' ; + mode ::= '100644' | '644' + | '100755' | '755' + | '140000' + ; declen ::= # unsigned 32 bit value, ascii base10 notation; bigint ::= # unsigned integer value, ascii base10 notation; @@ -1452,7 +1457,7 @@ static void file_change_m(struct branch *b) const char *endp; struct object_entry *oe; unsigned char sha1[20]; - unsigned int mode; + unsigned int mode, inline_data = 0; char type[20]; p = get_mode(p, &mode); @@ -1475,6 +1480,9 @@ static void file_change_m(struct branch *b) oe = find_mark(strtoumax(p + 1, &x, 10)); hashcpy(sha1, oe->sha1); p = x; + } else if (!strncmp("inline", p, 6)) { + inline_data = 1; + p += 6; } else { if (get_sha1_hex(p, sha1)) die("Invalid SHA1: %s", command_buf.buf); @@ -1491,7 +1499,16 @@ static void file_change_m(struct branch *b) p = p_uq; } - if (oe) { + if (inline_data) { + size_t l; + void *d; + if (!p_uq) + p = p_uq = xstrdup(p); + read_next_command(); + d = cmd_data(&l); + if (store_object(OBJ_BLOB, d, l, &last_blob, sha1, 0)) + free(d); + } else if (oe) { if (oe->type != OBJ_BLOB) die("Not a blob (actually a %s): %s", command_buf.buf, type_names[oe->type]); diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 40b8c073bd..a5cc846b34 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -17,6 +17,12 @@ in 3rd file file4_data=abcd file4_len=4 +file5_data='an inline file. + we should see it later.' + +file6_data='#!/bin/sh +echo "$@"' + ### ### series A ### @@ -181,4 +187,57 @@ test_expect_success \ 'C: validate rename result' \ 'compare_diff_raw expect actual' +### +### series D +### + +test_tick +cat >input < $GIT_COMMITTER_DATE +data <expect <actual +test_expect_success \ + 'D: validate new files added' \ + 'compare_diff_raw expect actual' + +echo "$file5_data" >expect +test_expect_success \ + 'D: verify file5' \ + 'git-cat-file blob branch:newdir/interesting >actual && + diff -u expect actual' + +echo "$file6_data" >expect +test_expect_success \ + 'D: verify file6' \ + 'git-cat-file blob branch:newdir/exec.sh >actual && + diff -u expect actual' + test_done From eae2ce619277903e73550663e6826f0299191bf3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 7 Dec 2006 19:59:46 -0500 Subject: [PATCH 227/548] git-gui: Reworded 'Include' to 'Add' to match core Git. Now that git-add is a first class citizen in core Git (Nico's 366bfcb6) users may start to expect the term 'add' to refer to the act of including a file's changes into a commit. So I'm replacing all uses of the term 'Include' in the UI with 'Add'. Signed-off-by: Shawn O. Pearce --- git-gui | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git-gui b/git-gui index 899fa35a60..1891215a64 100755 --- a/git-gui +++ b/git-gui @@ -3143,13 +3143,13 @@ lappend disable_on_lock \ lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] -.mbar.commit add command -label {Include In Commit} \ +.mbar.commit add command -label {Add To Commit} \ -command do_include_selection \ -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] -.mbar.commit add command -label {Include All In Commit} \ +.mbar.commit add command -label {Add All To Commit} \ -command do_include_all \ -accelerator $M1T-I \ -font font_ui @@ -3317,7 +3317,7 @@ 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 {Include All} \ +button .vpane.lower.commarea.buttons.incall -text {Add All} \ -command do_include_all \ -font font_ui pack .vpane.lower.commarea.buttons.incall -side top -fill x From 557afe820baccb21206c974fbd4afa65bd7f1e03 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 7 Dec 2006 22:07:38 -0500 Subject: [PATCH 228/548] git-gui: Created very crude Tools menu, to support miga. In one particular case I have a tool called 'miga' which users may need to invoke on their repository. This is a homegrown tool which is not (and should be) part of git-gui, but I still want to be able to run it from within the gui. Right now I'm taking a shortcut and adding it to the Tools menu if we are not on Mac OS X and the support script used to launch the tool exists in the local filesystem. This is nothing but a complete and utter hack. Signed-off-by: Shawn O. Pearce --- git-gui | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/git-gui b/git-gui index 1891215a64..36979afd77 100755 --- a/git-gui +++ b/git-gui @@ -3210,6 +3210,35 @@ if {[is_MacOSX]} { -command do_options \ -font font_ui + # -- Tools Menu + # + if {[file exists /usr/local/miga/lib/gui-miga]} { + proc do_miga {} { + global gitdir ui_status_value + if {![lock_index update]} return + set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""] + set miga_fd [open "|$cmd" r] + fconfigure $miga_fd -blocking 0 + fileevent $miga_fd readable [list miga_done $miga_fd] + set ui_status_value {Running miga...} + } + proc miga_done {fd} { + read $fd 512 + if {[eof $fd]} { + close $fd + unlock_index + rescan [list set ui_status_value {Ready.}] + } + } + .mbar add cascade -label Tools -menu .mbar.tools + menu .mbar.tools + .mbar.tools add command -label "Migrate" \ + -command do_miga \ + -font font_ui + lappend disable_on_lock \ + [list .mbar.tools entryconf [.mbar.tools index last] -state] + } + # -- Help Menu # .mbar add cascade -label Help -menu .mbar.help From 51e7e568c0a7854b2f93b86d6085695ce80053cc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 12 Dec 2006 22:44:38 -0500 Subject: [PATCH 229/548] git-gui: Show all fetched branches for remote pulls. Loop through every remote..fetch entry and add it as a valid option in the Pull menu. This way users can pull any remote branch that they track, without needing to leave the gui. Its a rather crude work around for not having a full merge interface. Signed-off-by: Shawn O. Pearce --- git-gui | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/git-gui b/git-gui index 36979afd77..0c88e4c7c3 100755 --- a/git-gui +++ b/git-gui @@ -1819,28 +1819,29 @@ proc populate_pull_menu {m} { global gitdir repo_config all_remotes disable_on_lock foreach remote $all_remotes { - set rb {} + set rb_list [list] if {[array get repo_config remote.$remote.url] ne {}} { if {[array get repo_config remote.$remote.fetch] ne {}} { - regexp {^([^:]+):} \ - [lindex $repo_config(remote.$remote.fetch) 0] \ - line rb + foreach line $repo_config(remote.$remote.fetch) { + if {[regexp {^([^:]+):} $line line rb]} { + lappend rb_list $rb + } + } } } else { catch { set fd [open [file join $gitdir remotes $remote] r] while {[gets $fd line] >= 0} { if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} { - break + lappend rb_list $rb } } close $fd } } - set rb_short $rb - regsub ^refs/heads/ $rb {} rb_short - if {$rb_short ne {}} { + foreach rb $rb_list { + regsub ^refs/heads/ $rb {} rb_short $m add command \ -label "Branch $rb_short from $remote..." \ -command [list pull_remote $remote $rb] \ From 81c0f29a5633f6a9ab01e0e9ded5e1c6d715b70b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 18:38:12 -0500 Subject: [PATCH 230/548] git-gui: Run git-gc rather than git-repack. Now that git 1.5.0-rc1 and later has a 'git gc' command which performs all important repository management activites (including reflog pruning, repacking local objects, unnecessary loose object pruning and rerere cache expiration) we should run 'gc' when the user wants us to cleanup their object database for them. I think the name 'gc' is horrible for a GUI application like git-gui, so I'm labeling the menu action 'Compress Database' instead. Hopefully this will provide some clue to the user about what the action does. Signed-off-by: Shawn O. Pearce --- git-gui | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/git-gui b/git-gui index 0c88e4c7c3..95c922b21e 100755 --- a/git-gui +++ b/git-gui @@ -2217,13 +2217,9 @@ proc do_gitk {revs} { } } -proc do_repack {} { - set w [new_console {repack} \ - {Repacking the object database}] - set cmd [list git repack] - lappend cmd -a - lappend cmd -d - console_exec $w $cmd +proc do_gc {} { + set w [new_console {gc} {Compressing the object database}] + console_exec $w {git gc} } proc do_fsck_objects {} { @@ -3033,8 +3029,8 @@ if {![is_MacOSX]} { .mbar.repository add separator if {!$single_commit} { - .mbar.repository add command -label {Repack Database} \ - -command do_repack \ + .mbar.repository add command -label {Compress Database} \ + -command do_gc \ -font font_ui .mbar.repository add command -label {Verify Database} \ From 6b0f3f46293e2f718054e9947e209c0344721a69 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 18:50:14 -0500 Subject: [PATCH 231/548] git-gui: Corrected behavior of deleted (but existing in HEAD) files. Apparently I did not account for the D_ file state. This can occur when a file has been marked for deletion by deleting it from the index, and the file also does not exist in the working directory. Typically this happens when the user deletes the file, hits Rescan, then includes the missing file in the commit, then hits Rescan again. We don't find the file in the working directory but its been removed in the index, so the state becomes D_. This state should be identical with DD. I'm not entirely sure why DD occurs sometimes and D_ others, it would seem like D_ is the state that should be happening instead of DD, leading me to believe there is a quirk in git-gui's state manipulation code. Signed-off-by: Shawn O. Pearce --- git-gui | 2 ++ 1 file changed, 2 insertions(+) diff --git a/git-gui b/git-gui index 95c922b21e..04fdb0c1e7 100755 --- a/git-gui +++ b/git-gui @@ -1429,6 +1429,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch msg after} { switch -glob -- [lindex $s 0] { A? {set new _O} M? {set new _M} + D_ {set new _D} D? {set new _?} ?? {continue} } @@ -1945,6 +1946,7 @@ foreach i { {_D i question "Missing"} {DD i removed "Removed by commit"} + {D_ i removed "Removed by commit"} {DO i removed "Removed (still exists)"} {DM i removed "Removed (but modified)"} From 68cbfb13919132cb2ddc591a765f4f20f9294657 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 18:54:56 -0500 Subject: [PATCH 232/548] git-gui: Correct wording of the revert confirmation dialog. We no longer describe updating the index as including changes, as we now use the add notation used by core Git's command line tools. So its confusing to be talking about unincluded changes within the revert dialog. Instead we should used language like 'unadded changes'. Signed-off-by: Shawn O. Pearce --- git-gui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-gui b/git-gui index 04fdb0c1e7..ade64dcd12 100755 --- a/git-gui +++ b/git-gui @@ -2429,9 +2429,9 @@ proc revert_helper {txt paths} { set reply [tk_dialog \ .confirm_revert \ "$appname ($reponame)" \ - "Revert unincluded changes in $s? + "Revert changes in $s? -Any unincluded changes will be permanently lost by the revert." \ +Any unadded changes will be permanently lost by the revert." \ question \ 1 \ {Do Nothing} \ From bdadecbae5b9f7317994bf2f521bb15068823a1d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 19:03:26 -0500 Subject: [PATCH 233/548] git-gui: Work around odd cygpath bug on Windows. There appears to be a bug on one of my test systems where cygpath with the --long-name option is generating a corrupt string that does not actually refer to sh.exe. This breaks any desktop icon created by git-gui as the executable we are trying to invoke does not exist. Since Cygwin is typically installed as C:\cygwin long path names is probably not actually necessary to link to the shell. I also added a small echo to the start of the icon script, as it can take one of my test systems several seconds to startup git-gui. This way the user knows we're starting git-gui, and was politely asked to wait for the action to complete. Signed-off-by: Shawn O. Pearce --- git-gui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-gui b/git-gui index ade64dcd12..c5120cc1ac 100755 --- a/git-gui +++ b/git-gui @@ -2742,7 +2742,6 @@ proc do_windows_shortcut {} { set sh [exec cygpath \ --windows \ --absolute \ - --long-name \ /bin/sh] set me [exec cygpath \ --unix \ @@ -2754,6 +2753,7 @@ proc do_windows_shortcut {} { $gitdir] regsub -all ' $me "'\\''" me regsub -all ' $gd "'\\''" gd + puts $fd "@ECHO Starting git-gui... Please wait..." puts -nonewline $fd "\"$sh\" --login -c \"" puts -nonewline $fd "GIT_DIR='$gd'" puts -nonewline $fd " '$me'" From 4d583c86ec52f8b2937a0b9dc02667b54c4a28a2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 19:07:46 -0500 Subject: [PATCH 234/548] git-gui: Change more 'include' language to 'add'. I just found a whole slew of places where we still were using the term 'include' rather than 'add' to refer to the act of updating the index with modifications from the working directory. To be consistent with all Git documentation and command line tools, these should be 'add'. Signed-off-by: Shawn O. Pearce --- git-gui | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/git-gui b/git-gui index c5120cc1ac..11ba41f74b 100755 --- a/git-gui +++ b/git-gui @@ -1935,9 +1935,9 @@ set max_status_desc 0 foreach i { {__ i plain "Unmodified"} {_M i mod "Modified"} - {M_ i fulltick "Included in commit"} + {M_ i fulltick "Added to commit"} {MM i parttick "Partially included"} - {MD i question "Included (but gone)"} + {MD i question "Added (but gone)"} {_O o plain "Untracked"} {A_ o fulltick "Added by commit"} @@ -2360,11 +2360,11 @@ proc do_include_selection {} { if {[array size selected_paths] > 0} { include_helper \ - {Including selected files} \ + {Adding selected files} \ [array names selected_paths] } elseif {$current_diff ne {}} { include_helper \ - "Including [short_path $current_diff]" \ + "Adding [short_path $current_diff]" \ [list $current_diff] } } @@ -2384,7 +2384,7 @@ proc do_include_all {} { } } include_helper \ - {Including all modified files} \ + {Adding all modified files} \ $paths } @@ -2615,7 +2615,7 @@ proc do_options {} { pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 foreach option { - {b partialinclude {Allow Partially Included Files}} + {b partialinclude {Allow Partially Added Files}} {b pullsummary {Show Pull Summary}} {b trustmtime {Trust File Modification Timestamps}} {i diffcontext {Number of Diff Context Lines}} @@ -2871,7 +2871,7 @@ proc toggle_or_diff {w x y} { } ?? { update_index \ - "Including [short_path $path]" \ + "Adding [short_path $path]" \ [list $path] \ [concat $after {set ui_status_value {Ready.}}] } From c25623321d52642fe8fb80c64904a53363f91b12 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 19:45:26 -0500 Subject: [PATCH 235/548] git-gui: Hide the ugly bash command line from the windows desktop icon. The user really doesn't need to see the technical details of how we launch git-gui from within their "desktop icon". Instead we should hide the command line from being displayed when the icon launches by putting @ at the start of the line. If they really need to see the command we are running they can edit the batch file. Signed-off-by: Shawn O. Pearce --- git-gui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-gui b/git-gui index 11ba41f74b..b79eb451d2 100755 --- a/git-gui +++ b/git-gui @@ -2754,7 +2754,7 @@ proc do_windows_shortcut {} { regsub -all ' $me "'\\''" me regsub -all ' $gd "'\\''" gd puts $fd "@ECHO Starting git-gui... Please wait..." - puts -nonewline $fd "\"$sh\" --login -c \"" + puts -nonewline $fd "@\"$sh\" --login -c \"" puts -nonewline $fd "GIT_DIR='$gd'" puts -nonewline $fd " '$me'" puts $fd "&\"" From 41bdcda37376a5faa63028f01260890723c3fcfa Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 20:00:07 -0500 Subject: [PATCH 236/548] git-gui: Modified makefile to embed version into git-gui script. We want to embed the version of git-gui directly into the script file, so that we can display it properly in the about dialog. Consequently I've refactored the Makefile process to act like the one in core git.git with regards to shell scripts, allowing git-gui to be constructed by a sed replacement performed on git-gui.sh. Signed-off-by: Shawn O. Pearce --- .gitignore | 2 ++ Makefile | 31 ++++++++++++++++++++++++++++--- git-citool | 1 - git-gui => git-gui.sh | 3 ++- 4 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 .gitignore delete mode 120000 git-citool rename git-gui => git-gui.sh (99%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..5bda901aeb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +git-citool +git-gui diff --git a/Makefile b/Makefile index e3e871f7c4..606bec640e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,13 @@ -all: git-gui +all:: + +SCRIPT_SH = git-gui.sh +GITGUI_BUILT_INS = git-citool +ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH)) +GITGUI_VERSION := $(shell git describe) + +ifndef SHELL_PATH + SHELL_PATH = /bin/sh +endif gitexecdir := $(shell git --exec-path) INSTALL = install @@ -6,9 +15,25 @@ INSTALL = install DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) -GITGUI_BUILTIN = git-citool +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) + +$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh + rm -f $@ $@+ + sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \ + $@.sh >$@+ + chmod +x $@+ + mv $@+ $@ + +$(GITGUI_BUILT_INS): git-gui + rm -f $@ && ln git-gui $@ + +all:: $(ALL_PROGRAMS) install: all $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)' - $(foreach p,$(GITGUI_BUILTIN), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;) + $(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;) + +clean:: + rm -f $(ALL_PROGRAMS) diff --git a/git-citool b/git-citool deleted file mode 120000 index b5f620fd09..0000000000 --- a/git-citool +++ /dev/null @@ -1 +0,0 @@ -git-gui \ No newline at end of file diff --git a/git-gui b/git-gui.sh similarity index 99% rename from git-gui rename to git-gui.sh index b79eb451d2..0770ad03f9 100755 --- a/git-gui +++ b/git-gui.sh @@ -3,7 +3,7 @@ exec wish "$0" -- "$@" set copyright { -Copyright © 2006 Shawn Pearce, Paul Mackerras. +Copyright © 2006, 2007 Shawn Pearce, Paul Mackerras. All rights reserved. @@ -11,6 +11,7 @@ This program is free software; it may be used, copied, modified and distributed under the terms of the GNU General Public Licence, either version 2, or (at your option) any later version.} +set appvers {@@GITGUI_VERSION@@} set appname [lindex [file split $argv0] end] set gitdir {} From 2f4479fb177fbbeab7c7311f3b2f1f31c6de428a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 20:04:02 -0500 Subject: [PATCH 237/548] git-gui: Display the git-gui version in the Help->About dialog. Now that we know what version git-gui is, the about dialog should display it to the end-user. This way users can find out what version they have before they report a problem or request a feature. 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 0770ad03f9..212a093118 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2505,7 +2505,7 @@ proc do_commit {} { } proc do_about {} { - global appname copyright + global appname appvers copyright global tcl_patchLevel tk_patchLevel set w .about_dialog @@ -2534,8 +2534,9 @@ $copyright" \ -font font_ui pack $w.desc -side top -fill x -padx 5 -pady 5 - set v [exec git --version] - append v "\n\n" + set v {} + append v "$appname version $appvers\n\n" + append v "[exec git --version]\n\n" if {$tcl_patchLevel eq $tk_patchLevel} { append v "Tcl/Tk version $tcl_patchLevel" } else { From 0499b24ad664d7f6a33d4cfe4a11912ae455b039 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 20:08:20 -0500 Subject: [PATCH 238/548] git-gui: Display the full GPL copyright notice in about dialog. We're a true GPL program, and we're interactive. We should show the entire GPL notice and disclaimer of warranty in our about dialog upon request by the user, as well as include it in the header of our source. Perhaps overkill, but is recommended by our license. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 212a093118..cb2b459ffd 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -5,11 +5,19 @@ exec wish "$0" -- "$@" set copyright { Copyright © 2006, 2007 Shawn Pearce, Paul Mackerras. -All rights reserved. +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. -This program is free software; it may be used, copied, modified -and distributed under the terms of the GNU General Public Licence, -either version 2, or (at your option) any later version.} +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +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} set appvers {@@GITGUI_VERSION@@} set appname [lindex [file split $argv0] end] From f1cee4e6d19919f6e333f9e671c720003a9a7cec Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 20:31:09 -0500 Subject: [PATCH 239/548] git-gui: Ensure version number is always current. I'm stealing the exact logic used by core Git within its own Makefile to setup the version number within scripts and executables. This way we can be sure that the version number is always updated after a commit, and that the version number also reflects when it is coming from a dirty working directory (and is thus pretty worthless). I've cleaned up some of the version display code in the about dialog too. There were simply too many blank lines in the bottom section where we showed the version data. Signed-off-by: Shawn O. Pearce --- .gitignore | 1 + GIT-VERSION-GEN | 46 ++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 15 ++++++++++++--- git-gui.sh | 7 ++++--- 4 files changed, 63 insertions(+), 6 deletions(-) create mode 100755 GIT-VERSION-GEN diff --git a/.gitignore b/.gitignore index 5bda901aeb..c714d382e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +GIT-VERSION-FILE git-citool git-gui diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN new file mode 100755 index 0000000000..79f1c527ff --- /dev/null +++ b/GIT-VERSION-GEN @@ -0,0 +1,46 @@ +#!/bin/sh + +GVF=GIT-VERSION-FILE +DEF_VER=v0.5.GIT + +LF=' +' + +# First try git-describe, then see if there is a version file +# (included in release tarballs), then default +if VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && + case "$VN" in + *$LF*) (exit 1) ;; + v[0-9]*) : happy ;; + esac +then + VN=$(echo "$VN" | sed -e 's/-/./g'); +elif test -f version +then + VN=$(cat version) || VN="$DEF_VER" +else + VN="$DEF_VER" +fi + +VN=$(expr "$VN" : v*'\(.*\)') + +dirty=$(sh -c 'git diff-index --name-only HEAD' 2>/dev/null) || dirty= +case "$dirty" in +'') + ;; +*) + VN="$VN-dirty" ;; +esac + +if test -r $GVF +then + VC=$(sed -e 's/^GIT_VERSION = //' <$GVF) +else + VC=unset +fi +test "$VN" = "$VC" || { + echo >&2 "GIT_VERSION = $VN" + echo "GIT_VERSION = $VN" >$GVF +} + + diff --git a/Makefile b/Makefile index 606bec640e..8fade69127 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,12 @@ all:: +GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE + @$(SHELL_PATH) ./GIT-VERSION-GEN +-include GIT-VERSION-FILE + SCRIPT_SH = git-gui.sh GITGUI_BUILT_INS = git-citool ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH)) -GITGUI_VERSION := $(shell git describe) ifndef SHELL_PATH SHELL_PATH = /bin/sh @@ -20,7 +23,7 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh rm -f $@ $@+ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ - -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \ + -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ $@.sh >$@+ chmod +x $@+ mv $@+ $@ @@ -28,6 +31,9 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(GITGUI_BUILT_INS): git-gui rm -f $@ && ln git-gui $@ +# These can record GIT_VERSION +$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE + all:: $(ALL_PROGRAMS) install: all @@ -36,4 +42,7 @@ install: all $(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;) clean:: - rm -f $(ALL_PROGRAMS) + rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE + +.PHONY: all install clean +.PHONY: .FORCE-GIT-VERSION-FILE diff --git a/git-gui.sh b/git-gui.sh index cb2b459ffd..e136e329c3 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -19,7 +19,7 @@ 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} -set appvers {@@GITGUI_VERSION@@} +set appvers {@@GIT_VERSION@@} set appname [lindex [file split $argv0] end] set gitdir {} @@ -2543,8 +2543,9 @@ $copyright" \ pack $w.desc -side top -fill x -padx 5 -pady 5 set v {} - append v "$appname version $appvers\n\n" - append v "[exec git --version]\n\n" + append v "$appname version $appvers\n" + append v "[exec git version]\n" + append v "\n" if {$tcl_patchLevel eq $tk_patchLevel} { append v "Tcl/Tk version $tcl_patchLevel" } else { From 98063115920338a1a6472a22c6792b13e37012b5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 20:47:31 -0500 Subject: [PATCH 240/548] git-gui: Allow the user to copy the version data to the clipboard. If a user wants to report an issue they will likely want to include the version number with their issue report. This may be difficult to enter if the version number includes an abbreviated commit SHA1 on the end of it. So we now give the user a context menu option on the version box which allows them to copy all of the relevant version data to the clipboard, ready for pasting into a report. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index e136e329c3..cfec89b45d 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2563,8 +2563,18 @@ $copyright" \ -font font_ui 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\] + " + bind $w "grab $w; focus $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 } From f7b9f6e4406458d522f9b376efdfecadf6b4ab35 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 20:53:37 -0500 Subject: [PATCH 241/548] git-gui: Don't offer my miga hack if its configuration file isn't present. I really hate that I have this specialized hack within git-gui, but its here. The hack shouldn't be offered unless miga's required .pvcsrc file is in the top level of the repository's working directory. If this file is missing miga will fail to startup properly, and the user cannot wouldn't be able to use it within this directory. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index cfec89b45d..e2dc931e48 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3232,7 +3232,8 @@ if {[is_MacOSX]} { # -- Tools Menu # - if {[file exists /usr/local/miga/lib/gui-miga]} { + if {[file exists /usr/local/miga/lib/gui-miga] + && [file exists .pvcsrc]} { proc do_miga {} { global gitdir ui_status_value if {![lock_index update]} return From 8ff487c737fe7a611da03c20f08888fb7f3a3550 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 21:23:21 -0500 Subject: [PATCH 242/548] git-gui: Suggest when running 'git gc' may be worthwhile. Users often forget to repack their object database, then start to complain about how slow it is to perform common operations after they have collected thousands of loose objects in their objects directory. A simple repack usually restores performance. During startup git-gui now asks git-count-objects how many loose objects exist, and if this number exceeds a hardcoded threshold we suggest that the user compress the database (aka run 'git gc') at this time. I've hardcoded this to 2000 objects on non-Windows systems as there the filesystems tend to handle the ~8 objects per directory just fine. On Windows NTFS and FAT are just so slow that we really start to lag when more than 200 loose objects exist, so the hardcoded threshold is much lower there. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index e2dc931e48..fb2d92d17c 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -195,6 +195,25 @@ proc info_popup {msg} { -message $msg } +proc ask_popup {msg} { + global gitdir appname + + set title $appname + if {$gitdir ne {}} { + append title { (} + append title [lindex \ + [file split [file normalize [file dirname $gitdir]]] \ + end] + append title {)} + } + return [tk_messageBox \ + -parent . \ + -icon question \ + -type yesno \ + -title $title \ + -message $msg] +} + ###################################################################### ## ## repository setup @@ -3790,5 +3809,26 @@ if {!$single_commit} { populate_push_menu .mbar.push } +# -- Only suggest a gc run if we are going to stay running. +# +if {!$single_commit} { + set object_limit 2000 + if {[is_Windows]} {set object_limit 200} + regexp {^([0-9]+) objects,} [exec git count-objects] _junk objects_current + if {$objects_current >= $object_limit} { + if {[ask_popup \ + "This repository currently has $objects_current loose objects. + +To maintain optimal performance it is strongly +recommended that you compress the database +when more than $object_limit loose objects exist. + +Compress the database now?"] eq yes} { + do_gc + } + } + unset object_limit _junk objects_current +} + lock_index begin-read after 1 do_rescan From 16d18b853b91e66219a26130c0485914c2969389 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 21:36:21 -0500 Subject: [PATCH 243/548] git-gui: Refactor reponame computation. We use reponame in a number of locations, and every time its always the same value. Instead of computing this multiple times with code that was copied and pasted around we can compute it once immediately after the global gitdir has been computed and set. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index fb2d92d17c..54cc5faad2 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -239,6 +239,9 @@ if {[catch {cd [file dirname $gitdir]} err]} { error_popup "No working directory [file dirname $gitdir]:\n\n$err" exit 1 } +set reponame [lindex [file split \ + [file normalize [file dirname $gitdir]]] \ + end] set single_commit 0 if {$appname eq {git-citool}} { @@ -2417,7 +2420,7 @@ proc do_include_all {} { } proc revert_helper {txt paths} { - global gitdir appname + global gitdir appname reponame global file_states current_diff if {![lock_index begin-update]} return @@ -2450,10 +2453,6 @@ proc revert_helper {txt paths} { set s "these $n files" } - set reponame [lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end] - set reply [tk_dialog \ .confirm_revert \ "$appname ($reponame)" \ @@ -2599,7 +2598,7 @@ $copyright" \ } proc do_options {} { - global appname gitdir font_descs + global appname gitdir reponame font_descs global repo_config global_config global repo_config_new global_config_new @@ -2618,9 +2617,6 @@ proc do_options {} { foreach name [array names global_config] { set global_config_new($name) $global_config($name) } - set reponame [lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end] set w .options_editor toplevel $w @@ -2756,11 +2752,7 @@ proc do_save_config {w} { } proc do_windows_shortcut {} { - global gitdir appname argv0 - - set reponame [lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end] + global gitdir appname reponame argv0 if {[catch { set desktop [exec cygpath \ @@ -2806,11 +2798,7 @@ proc do_windows_shortcut {} { } proc do_macosx_app {} { - global gitdir appname argv0 env - - set reponame [lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end] + global gitdir appname reponame argv0 env set fn [tk_getSaveFile \ -parent . \ From c950c66ed91ba8e66dd26c3b19167e6eecb47fe9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 21:48:56 -0500 Subject: [PATCH 244/548] git-gui: Cleanup usage of gitdir global variable. The gitdir global variable is essentially read-only, and is used rather frequently. So are appname and reponame. Needing to constantly declare 'global appname' just so we can access the value as $appname is downright annoying and redundant. So instead I'm declaring these as procedures and changing all uses to invoke the procedure rather than access the global directly. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 207 +++++++++++++++++++++++++---------------------------- 1 file changed, 97 insertions(+), 110 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 54cc5faad2..b937cf2163 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2,6 +2,7 @@ # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" +set appvers {@@GIT_VERSION@@} set copyright { Copyright © 2006, 2007 Shawn Pearce, Paul Mackerras. @@ -19,9 +20,28 @@ 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} -set appvers {@@GIT_VERSION@@} -set appname [lindex [file split $argv0] end] -set gitdir {} +###################################################################### +## +## read only globals + +set _appname [lindex [file split $argv0] end] +set _gitdir {} +set _reponame {} + +proc appname {} { + global _appname + return $_appname +} + +proc gitdir {} { + global _gitdir + return $_gitdir +} + +proc reponame {} { + global _reponame + return $_reponame +} ###################################################################### ## @@ -133,15 +153,9 @@ proc save_config {} { } proc error_popup {msg} { - global gitdir appname - - set title $appname - if {$gitdir ne {}} { - append title { (} - append title [lindex \ - [file split [file normalize [file dirname $gitdir]]] \ - end] - append title {)} + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" } set cmd [list tk_messageBox \ -icon error \ @@ -155,15 +169,9 @@ proc error_popup {msg} { } proc warn_popup {msg} { - global gitdir appname - - set title $appname - if {$gitdir ne {}} { - append title { (} - append title [lindex \ - [file split [file normalize [file dirname $gitdir]]] \ - end] - append title {)} + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" } set cmd [list tk_messageBox \ -icon warning \ @@ -177,15 +185,9 @@ proc warn_popup {msg} { } proc info_popup {msg} { - global gitdir appname - - set title $appname - if {$gitdir ne {}} { - append title { (} - append title [lindex \ - [file split [file normalize [file dirname $gitdir]]] \ - end] - append title {)} + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" } tk_messageBox \ -parent . \ @@ -196,15 +198,9 @@ proc info_popup {msg} { } proc ask_popup {msg} { - global gitdir appname - - set title $appname - if {$gitdir ne {}} { - append title { (} - append title [lindex \ - [file split [file normalize [file dirname $gitdir]]] \ - end] - append title {)} + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" } return [tk_messageBox \ -parent . \ @@ -218,33 +214,33 @@ proc ask_popup {msg} { ## ## repository setup -if { [catch {set gitdir $env(GIT_DIR)}] - && [catch {set gitdir [exec git rev-parse --git-dir]} err]} { +if { [catch {set _gitdir $env(GIT_DIR)}] + && [catch {set _gitdir [exec git rev-parse --git-dir]} err]} { catch {wm withdraw .} error_popup "Cannot find the git directory:\n\n$err" exit 1 } -if {![file isdirectory $gitdir]} { +if {![file isdirectory $_gitdir]} { catch {wm withdraw .} - error_popup "Git directory not found:\n\n$gitdir" + error_popup "Git directory not found:\n\n$_gitdir" exit 1 } -if {[lindex [file split $gitdir] end] ne {.git}} { +if {[lindex [file split $_gitdir] end] ne {.git}} { catch {wm withdraw .} error_popup "Cannot use funny .git directory:\n\n$gitdir" exit 1 } -if {[catch {cd [file dirname $gitdir]} err]} { +if {[catch {cd [file dirname $_gitdir]} err]} { catch {wm withdraw .} - error_popup "No working directory [file dirname $gitdir]:\n\n$err" + error_popup "No working directory [file dirname $_gitdir]:\n\n$err" exit 1 } -set reponame [lindex [file split \ - [file normalize [file dirname $gitdir]]] \ +set _reponame [lindex [file split \ + [file normalize [file dirname $_gitdir]]] \ end] set single_commit 0 -if {$appname eq {git-citool}} { +if {[appname] eq {git-citool}} { set single_commit 1 } @@ -289,7 +285,7 @@ proc unlock_index {} { ## status proc repository_state {ctvar hdvar mhvar} { - global gitdir current_branch + global current_branch upvar $ctvar ct $hdvar hd $mhvar mh set mh [list] @@ -309,7 +305,7 @@ proc repository_state {ctvar hdvar mhvar} { return } - set merge_head [file join $gitdir MERGE_HEAD] + set merge_head [file join [gitdir] MERGE_HEAD] if {[file exists $merge_head]} { set ct merge set fd_mh [open $merge_head r] @@ -385,7 +381,7 @@ proc rescan {after} { } proc rescan_stage2 {fd after} { - global gitdir ui_status_value + global ui_status_value global rescan_active buf_rdi buf_rdf buf_rlo if {$fd ne {}} { @@ -396,7 +392,7 @@ proc rescan_stage2 {fd after} { set ls_others [list | git ls-files --others -z \ --exclude-per-directory=.gitignore] - set info_exclude [file join $gitdir info exclude] + set info_exclude [file join [gitdir] info exclude] if {[file readable $info_exclude]} { lappend ls_others "--exclude-from=$info_exclude" } @@ -420,9 +416,9 @@ proc rescan_stage2 {fd after} { } proc load_message {file} { - global gitdir ui_comm + global ui_comm - set f [file join $gitdir $file] + set f [file join [gitdir] $file] if {[file isfile $f]} { if {[catch {set fd [open $f r]}]} { return 0 @@ -953,9 +949,9 @@ A good commit message has the following format: } proc commit_prehook {curHEAD msg} { - global gitdir ui_status_value pch_error + global ui_status_value pch_error - set pchook [file join $gitdir hooks pre-commit] + set pchook [file join [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. @@ -1010,7 +1006,7 @@ proc commit_writetree {curHEAD msg} { proc commit_committree {fd_wt curHEAD msg} { global HEAD PARENT MERGE_HEAD commit_type - global single_commit gitdir + global single_commit global ui_status_value ui_comm selected_commit_type global file_states selected_paths rescan_active @@ -1064,20 +1060,20 @@ proc commit_committree {fd_wt curHEAD msg} { # -- Cleanup after ourselves. # - catch {file delete [file join $gitdir MERGE_HEAD]} - catch {file delete [file join $gitdir MERGE_MSG]} - catch {file delete [file join $gitdir SQUASH_MSG]} - catch {file delete [file join $gitdir GITGUI_MSG]} + catch {file delete [file join [gitdir] MERGE_HEAD]} + catch {file delete [file join [gitdir] MERGE_MSG]} + catch {file delete [file join [gitdir] SQUASH_MSG]} + catch {file delete [file join [gitdir] GITGUI_MSG]} # -- Let rerere do its thing. # - if {[file isdirectory [file join $gitdir rr-cache]]} { + if {[file isdirectory [file join [gitdir] rr-cache]]} { catch {exec git rerere} } # -- Run the post-commit hook. # - set pchook [file join $gitdir hooks post-commit] + set pchook [file join [gitdir] hooks post-commit] if {[is_Windows] && [file isfile $pchook]} { set pchook [list sh -c [concat \ "if test -x \"$pchook\";" \ @@ -1736,13 +1732,13 @@ The rescan will be automatically started now. ## remote management proc load_all_remotes {} { - global gitdir repo_config + global repo_config global all_remotes tracking_branches set all_remotes [list] array unset tracking_branches - set rm_dir [file join $gitdir remotes] + set rm_dir [file join [gitdir] remotes] if {[file isdirectory $rm_dir]} { set all_remotes [glob \ -types f \ @@ -1786,7 +1782,7 @@ proc load_all_remotes {} { } proc populate_fetch_menu {m} { - global gitdir all_remotes repo_config + global all_remotes repo_config foreach r $all_remotes { set enable 0 @@ -1796,7 +1792,7 @@ proc populate_fetch_menu {m} { } } else { catch { - set fd [open [file join $gitdir remotes $r] r] + set fd [open [file join [gitdir] remotes $r] r] while {[gets $fd n] >= 0} { if {[regexp {^Pull:[ \t]*([^:]+):} $n]} { set enable 1 @@ -1817,7 +1813,7 @@ proc populate_fetch_menu {m} { } proc populate_push_menu {m} { - global gitdir all_remotes repo_config + global all_remotes repo_config foreach r $all_remotes { set enable 0 @@ -1827,7 +1823,7 @@ proc populate_push_menu {m} { } } else { catch { - set fd [open [file join $gitdir remotes $r] r] + set fd [open [file join [gitdir] remotes $r] r] while {[gets $fd n] >= 0} { if {[regexp {^Push:[ \t]*([^:]+):} $n]} { set enable 1 @@ -1848,7 +1844,7 @@ proc populate_push_menu {m} { } proc populate_pull_menu {m} { - global gitdir repo_config all_remotes disable_on_lock + global repo_config all_remotes disable_on_lock foreach remote $all_remotes { set rb_list [list] @@ -1862,7 +1858,7 @@ proc populate_pull_menu {m} { } } else { catch { - set fd [open [file join $gitdir remotes $remote] r] + set fd [open [file join [gitdir] remotes $remote] r] while {[gets $fd line] >= 0} { if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} { lappend rb_list $rb @@ -2033,8 +2029,6 @@ proc incr_font_size {font {amt 1}} { } proc hook_failed_popup {hook msg} { - global gitdir appname - set w .hookfail toplevel $w @@ -2072,9 +2066,7 @@ proc hook_failed_popup {hook msg} { bind $w "grab $w; focus $w" bind $w "destroy $w" - wm title $w "$appname ([lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end]): error" + wm title $w "[appname] ([reponame]): error" tkwait window $w } @@ -2088,8 +2080,7 @@ proc new_console {short_title long_title} { } proc console_init {w} { - global console_cr console_data - global gitdir appname M1B + global console_cr console_data M1B set console_cr($w) 1.0 toplevel $w @@ -2141,9 +2132,7 @@ proc console_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 ([lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end]): [lindex $console_data($w) 0]" + wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]" return $w } @@ -2268,14 +2257,14 @@ proc do_fsck_objects {} { set is_quitting 0 proc do_quit {} { - global gitdir ui_comm is_quitting repo_config commit_type + global ui_comm is_quitting repo_config commit_type if {$is_quitting} return set is_quitting 1 # -- Stash our current commit buffer. # - set save [file join $gitdir GITGUI_MSG] + set save [file join [gitdir] GITGUI_MSG] set msg [string trim [$ui_comm get 0.0 end]] if {![string match amend* $commit_type] && [$ui_comm edit modified] @@ -2420,7 +2409,6 @@ proc do_include_all {} { } proc revert_helper {txt paths} { - global gitdir appname reponame global file_states current_diff if {![lock_index begin-update]} return @@ -2455,7 +2443,7 @@ proc revert_helper {txt paths} { set reply [tk_dialog \ .confirm_revert \ - "$appname ($reponame)" \ + "[appname] ([reponame])" \ "Revert changes in $s? Any unadded changes will be permanently lost by the revert." \ @@ -2531,14 +2519,14 @@ proc do_commit {} { } proc do_about {} { - global appname appvers copyright + 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" \ + label $w.header -text "About [appname]" \ -font font_uibold pack $w.header -side top -fill x @@ -2550,7 +2538,7 @@ proc do_about {} { pack $w.buttons -side bottom -fill x -pady 10 -padx 10 label $w.desc \ - -text "$appname - a commit creation tool for Git. + -text "[appname] - a commit creation tool for Git. $copyright" \ -padx 5 -pady 5 \ -justify left \ @@ -2561,7 +2549,7 @@ $copyright" \ pack $w.desc -side top -fill x -padx 5 -pady 5 set v {} - append v "$appname version $appvers\n" + append v "[appname] version $appvers\n" append v "[exec git version]\n" append v "\n" if {$tcl_patchLevel eq $tk_patchLevel} { @@ -2593,13 +2581,12 @@ $copyright" \ bind $w "grab $w; focus $w" bind $w "destroy $w" bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w" - wm title $w "About $appname" + wm title $w "About [appname]" tkwait window $w } proc do_options {} { - global appname gitdir reponame font_descs - global repo_config global_config + global repo_config global_config font_descs global repo_config_new global_config_new array unset repo_config_new @@ -2622,7 +2609,7 @@ proc do_options {} { toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - label $w.header -text "$appname Options" \ + label $w.header -text "[appname] Options" \ -font font_uibold pack $w.header -side top -fill x @@ -2641,7 +2628,7 @@ proc do_options {} { pack $w.buttons.cancel -side right pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.repo -text "$reponame Repository" \ + labelframe $w.repo -text "[reponame] Repository" \ -font font_ui \ -relief raised -borderwidth 2 labelframe $w.global -text {Global (All Repositories)} \ @@ -2714,7 +2701,7 @@ proc do_options {} { bind $w "grab $w; focus $w" bind $w "destroy $w" - wm title $w "$appname ($reponame): Options" + wm title $w "[appname] ([reponame]): Options" tkwait window $w } @@ -2752,7 +2739,7 @@ proc do_save_config {w} { } proc do_windows_shortcut {} { - global gitdir appname reponame argv0 + global argv0 if {[catch { set desktop [exec cygpath \ @@ -2765,9 +2752,9 @@ proc do_windows_shortcut {} { } set fn [tk_getSaveFile \ -parent . \ - -title "$appname ($reponame): Create Desktop Icon" \ + -title "[appname] ([reponame]): Create Desktop Icon" \ -initialdir $desktop \ - -initialfile "Git $reponame.bat"] + -initialfile "Git [reponame].bat"] if {$fn != {}} { if {[catch { set fd [open $fn w] @@ -2782,7 +2769,7 @@ proc do_windows_shortcut {} { set gd [exec cygpath \ --unix \ --absolute \ - $gitdir] + [gitdir]] regsub -all ' $me "'\\''" me regsub -all ' $gd "'\\''" gd puts $fd "@ECHO Starting git-gui... Please wait..." @@ -2798,13 +2785,13 @@ proc do_windows_shortcut {} { } proc do_macosx_app {} { - global gitdir appname reponame argv0 env + global argv0 env set fn [tk_getSaveFile \ -parent . \ - -title "$appname ($reponame): Create Desktop Icon" \ + -title "[appname] ([reponame]): Create Desktop Icon" \ -initialdir [file join $env(HOME) Desktop] \ - -initialfile "Git $reponame.app"] + -initialfile "Git [reponame].app"] if {$fn != {}} { if {[catch { set Contents [file join $fn Contents] @@ -2839,7 +2826,7 @@ proc do_macosx_app {} { close $fd set fd [open $exe w] - set gd [file normalize $gitdir] + set gd [file normalize [gitdir]] set ep [file normalize [exec git --exec-path]] regsub -all ' $gd "'\\''" gd regsub -all ' $ep "'\\''" ep @@ -3223,10 +3210,10 @@ if {[is_MacOSX]} { .mbar add cascade -label Apple -menu .mbar.apple menu .mbar.apple - .mbar.apple add command -label "About $appname" \ + .mbar.apple add command -label "About [appname]" \ -command do_about \ -font font_ui - .mbar.apple add command -label "$appname Options..." \ + .mbar.apple add command -label "[appname] Options..." \ -command do_options \ -font font_ui } else { @@ -3242,7 +3229,7 @@ if {[is_MacOSX]} { if {[file exists /usr/local/miga/lib/gui-miga] && [file exists .pvcsrc]} { proc do_miga {} { - global gitdir ui_status_value + global ui_status_value if {![lock_index update]} return set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""] set miga_fd [open "|$cmd" r] @@ -3272,7 +3259,7 @@ if {[is_MacOSX]} { .mbar add cascade -label Help -menu .mbar.help menu .mbar.help - .mbar.help add command -label "About $appname" \ + .mbar.help add command -label "About [appname]" \ -command do_about \ -font font_ui } @@ -3727,7 +3714,7 @@ set current_branch {} set current_diff {} set selected_commit_type new -wm title . "$appname ([file normalize [file dirname $gitdir]])" +wm title . "[appname] ([file normalize [file dirname [gitdir]]])" focus -force $ui_comm # -- Warn the user about environmental problems. Cygwin's Tcl @@ -3741,7 +3728,7 @@ if {[is_Windows]} { The following environment variables are probably going to be ignored by any Git subprocess run -by $appname: +by [appname]: " foreach name [array names env] { From c2758a17cb268fa2bb7ff46e27b571038a9eeb5d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 21:55:05 -0500 Subject: [PATCH 245/548] git-gui: Allow [gitdir ...] to act as [file join [gitdir] ...]. Because it is such a common idiom to use [gitdir] along with [file join] to locate the path of an item within the .git directory of the current repository we might as well allow gitdir to act as a wrapper for the file join operation. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index b937cf2163..84205b08bf 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -33,9 +33,12 @@ proc appname {} { return $_appname } -proc gitdir {} { +proc gitdir {args} { global _gitdir - return $_gitdir + if {$args eq {}} { + return $_gitdir + } + return [eval [concat [list file join $_gitdir] $args]] } proc reponame {} { @@ -305,7 +308,7 @@ proc repository_state {ctvar hdvar mhvar} { return } - set merge_head [file join [gitdir] MERGE_HEAD] + set merge_head [gitdir MERGE_HEAD] if {[file exists $merge_head]} { set ct merge set fd_mh [open $merge_head r] @@ -392,7 +395,7 @@ proc rescan_stage2 {fd after} { set ls_others [list | git ls-files --others -z \ --exclude-per-directory=.gitignore] - set info_exclude [file join [gitdir] info exclude] + set info_exclude [gitdir info exclude] if {[file readable $info_exclude]} { lappend ls_others "--exclude-from=$info_exclude" } @@ -418,7 +421,7 @@ proc rescan_stage2 {fd after} { proc load_message {file} { global ui_comm - set f [file join [gitdir] $file] + set f [gitdir $file] if {[file isfile $f]} { if {[catch {set fd [open $f r]}]} { return 0 @@ -951,7 +954,7 @@ A good commit message has the following format: proc commit_prehook {curHEAD msg} { global ui_status_value pch_error - set pchook [file join [gitdir] hooks pre-commit] + 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. @@ -1060,20 +1063,20 @@ proc commit_committree {fd_wt curHEAD msg} { # -- Cleanup after ourselves. # - catch {file delete [file join [gitdir] MERGE_HEAD]} - catch {file delete [file join [gitdir] MERGE_MSG]} - catch {file delete [file join [gitdir] SQUASH_MSG]} - catch {file delete [file join [gitdir] GITGUI_MSG]} + 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 [file join [gitdir] rr-cache]]} { + if {[file isdirectory [gitdir rr-cache]]} { catch {exec git rerere} } # -- Run the post-commit hook. # - set pchook [file join [gitdir] hooks post-commit] + set pchook [gitdir hooks post-commit] if {[is_Windows] && [file isfile $pchook]} { set pchook [list sh -c [concat \ "if test -x \"$pchook\";" \ @@ -1738,7 +1741,7 @@ proc load_all_remotes {} { set all_remotes [list] array unset tracking_branches - set rm_dir [file join [gitdir] remotes] + set rm_dir [gitdir remotes] if {[file isdirectory $rm_dir]} { set all_remotes [glob \ -types f \ @@ -1792,7 +1795,7 @@ proc populate_fetch_menu {m} { } } else { catch { - set fd [open [file join [gitdir] remotes $r] r] + set fd [open [gitdir remotes $r] r] while {[gets $fd n] >= 0} { if {[regexp {^Pull:[ \t]*([^:]+):} $n]} { set enable 1 @@ -1823,7 +1826,7 @@ proc populate_push_menu {m} { } } else { catch { - set fd [open [file join [gitdir] remotes $r] r] + set fd [open [gitdir remotes $r] r] while {[gets $fd n] >= 0} { if {[regexp {^Push:[ \t]*([^:]+):} $n]} { set enable 1 @@ -1858,7 +1861,7 @@ proc populate_pull_menu {m} { } } else { catch { - set fd [open [file join [gitdir] remotes $remote] r] + set fd [open [gitdir remotes $remote] r] while {[gets $fd line] >= 0} { if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} { lappend rb_list $rb @@ -2264,7 +2267,7 @@ proc do_quit {} { # -- Stash our current commit buffer. # - set save [file join [gitdir] GITGUI_MSG] + set save [gitdir GITGUI_MSG] set msg [string trim [$ui_comm get 0.0 end]] if {![string match amend* $commit_type] && [$ui_comm edit modified] From dccfa6727e78190002364981158cb46878fdcfe2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 21:56:25 -0500 Subject: [PATCH 246/548] git-gui: Make the gitk starting message match our usual format. Because we usually say "Operation... please wait..." we should do the same thing when starting gitk. 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 84205b08bf..0851eaeebc 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2215,7 +2215,7 @@ proc console_read {w fd after} { ## ## ui commands -set starting_gitk_msg {Please wait... Starting gitk...} +set starting_gitk_msg {Starting gitk... please wait...} proc do_gitk {revs} { global ui_status_value starting_gitk_msg From c2faa43677ec2471dadc3cc789cab27f6bc2abbb Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 22:00:28 -0500 Subject: [PATCH 247/548] git-gui: Display the directory we are entering during startup. If the user has many git-gui icons it may be confusing when they start one which git-gui is still coming up. So on the windows systems we now include an echo statement which displays the full pathname of the working directory we are trying to enter into. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index 0851eaeebc..e719314e7b 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2773,9 +2773,14 @@ proc do_windows_shortcut {} { --unix \ --absolute \ [gitdir]] + set gw [exec cygpath \ + --windows \ + --absolute \ + [file dirname [gitdir]]] regsub -all ' $me "'\\''" me regsub -all ' $gd "'\\''" gd - puts $fd "@ECHO Starting git-gui... Please wait..." + 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'" From 0812665e578cf8ac1fe9a69b5557e3781be2d8ff Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 22:06:51 -0500 Subject: [PATCH 248/548] git-gui: Start file status display refactoring. I'm going to refactor the way file status information gets displayed so it more closely aligns with the way 'git-runstatus' displays the differences between HEAD<->index and index<->working directory. To that end the other file list is going to be changed to be the working directory difference. So this change renames it. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index e719314e7b..df21638cfb 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -337,7 +337,7 @@ proc PARENT {} { proc rescan {after} { global HEAD PARENT MERGE_HEAD commit_type - global ui_index ui_other ui_status_value ui_comm + global ui_index ui_workdir ui_status_value ui_comm global rescan_active file_states global repo_config @@ -561,7 +561,7 @@ proc prune_selection {} { ## diff proc clear_diff {} { - global ui_diff current_diff ui_index ui_other + global ui_diff current_diff ui_index ui_workdir $ui_diff conf -state normal $ui_diff delete 0.0 end @@ -570,7 +570,7 @@ proc clear_diff {} { set current_diff {} $ui_index tag remove in_diff 0.0 end - $ui_other tag remove in_diff 0.0 end + $ui_workdir tag remove in_diff 0.0 end } proc reshow_diff {} { @@ -1231,11 +1231,11 @@ proc push_to {remote} { ## ui helpers proc mapcol {state path} { - global all_cols ui_other + global all_cols ui_workdir if {[catch {set r $all_cols($state)}]} { puts "error: no column for state={$state} $path" - return $ui_other + return $ui_workdir } return $r } @@ -1368,19 +1368,19 @@ proc display_file {path state} { } proc display_all_files {} { - global ui_index ui_other + global ui_index ui_workdir global file_states file_lists global last_clicked selected_paths $ui_index conf -state normal - $ui_other conf -state normal + $ui_workdir conf -state normal $ui_index delete 0.0 end - $ui_other delete 0.0 end + $ui_workdir delete 0.0 end set last_clicked {} set file_lists($ui_index) [list] - set file_lists($ui_other) [list] + set file_lists($ui_workdir) [list] foreach path [lsort [array names file_states]] { set s $file_states($path) @@ -1402,7 +1402,7 @@ proc display_all_files {} { } $ui_index conf -state disabled - $ui_other conf -state disabled + $ui_workdir conf -state disabled } proc update_indexinfo {msg pathList after} { @@ -1960,7 +1960,7 @@ static unsigned char file_merge_bits[] = { } -maskdata $filemask set ui_index .vpane.files.index.list -set ui_other .vpane.files.other.list +set ui_workdir .vpane.files.workdir.list set max_status_desc 0 foreach i { {__ i plain "Unmodified"} @@ -1990,7 +1990,7 @@ foreach i { if {[lindex $i 1] eq {i}} { set all_cols([lindex $i 0]) $ui_index } else { - set all_cols([lindex $i 0]) $ui_other + set all_cols([lindex $i 0]) $ui_workdir } set all_icons([lindex $i 0]) file_[lindex $i 2] set all_descs([lindex $i 0]) [lindex $i 3] @@ -2858,7 +2858,7 @@ proc do_macosx_app {} { } proc toggle_or_diff {w x y} { - global file_states file_lists current_diff ui_index ui_other + global file_states file_lists current_diff ui_index ui_workdir global last_clicked selected_paths set pos [split [$w index @$x,$y] .] @@ -2873,7 +2873,7 @@ proc toggle_or_diff {w x y} { set last_clicked [list $w $lno] array unset selected_paths $ui_index tag remove in_sel 0.0 end - $ui_other tag remove in_sel 0.0 end + $ui_workdir tag remove in_sel 0.0 end if {$col == 0} { if {$current_diff eq $path} { @@ -3317,25 +3317,25 @@ pack .vpane.files.index.sb -side right -fill y pack $ui_index -side left -fill both -expand 1 .vpane.files add .vpane.files.index -sticky nsew -# -- Other (Add) File List +# -- Working Directory File List # -frame .vpane.files.other -height 100 -width 100 -label .vpane.files.other.title -text {Untracked Files} \ +frame .vpane.files.workdir -height 100 -width 100 +label .vpane.files.workdir.title -text {Untracked Files} \ -background red \ -font font_ui -text $ui_other -background white -borderwidth 0 \ +text $ui_workdir -background white -borderwidth 0 \ -width 40 -height 10 \ -font font_ui \ -cursor $cursor_ptr \ - -yscrollcommand {.vpane.files.other.sb set} \ + -yscrollcommand {.vpane.files.workdir.sb set} \ -state disabled -scrollbar .vpane.files.other.sb -command [list $ui_other yview] -pack .vpane.files.other.title -side top -fill x -pack .vpane.files.other.sb -side right -fill y -pack $ui_other -side left -fill both -expand 1 -.vpane.files add .vpane.files.other -sticky nsew +scrollbar .vpane.files.workdir.sb -command [list $ui_workdir yview] +pack .vpane.files.workdir.title -side top -fill x +pack .vpane.files.workdir.sb -side right -fill y +pack $ui_workdir -side left -fill both -expand 1 +.vpane.files add .vpane.files.workdir -sticky nsew -foreach i [list $ui_index $ui_other] { +foreach i [list $ui_index $ui_workdir] { $i tag conf in_diff -font font_uibold $i tag conf in_sel \ -background [$i cget -foreground] \ @@ -3703,7 +3703,7 @@ bind all <$M1B-Key-q> do_quit 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]} -foreach i [list $ui_index $ui_other] { +foreach i [list $ui_index $ui_workdir] { bind $i "toggle_or_diff $i %x %y; break" bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break" bind $i "add_range_to_selection $i %x %y; break" @@ -3711,7 +3711,7 @@ foreach i [list $ui_index $ui_other] { unset i set file_lists($ui_index) [list] -set file_lists($ui_other) [list] +set file_lists($ui_workdir) [list] set HEAD {} set PARENT {} From 21e409ad7f85b0557b0baa6d32f9b2064c7aa633 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 22:45:19 -0500 Subject: [PATCH 249/548] git-gui: Convert UI to use 'staged for commit' interface. This is a rather drastic change to the git-gui user interface, but it doesn't really look any different yet. I've taken the two lists and converted them to being "changes to be committed" and "changed but not updated". These lists correspond to the same lists output by git-runstatus based on how files differ in the HEAD<->index and the index<->working directory comparsions it performs. This change is meant to correlate with the change in Git 1.5.0 where we have brought the index more into the foreground and are trying to teach users to make use of it as part of their daily operations. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 208 +++++++++++++++++++++++++---------------------------- 1 file changed, 98 insertions(+), 110 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index df21638cfb..9fa467ab9f 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -610,16 +610,7 @@ files list, to prevent possible confusion. } clear_diff - set old_w [mapcol [lindex $file_states($path) 0] $path] - set lno [lsearch -sorted $file_lists($old_w) $path] - if {$lno >= 0} { - set file_lists($old_w) \ - [lreplace $file_lists($old_w) $lno $lno] - incr lno - $old_w conf -state normal - $old_w delete $lno.0 [expr {$lno + 1}].0 - $old_w conf -state disabled - } + display_file $path __ } proc show_diff {path {w {}} {lno {}}} { @@ -1230,21 +1221,11 @@ proc push_to {remote} { ## ## ui helpers -proc mapcol {state path} { - global all_cols ui_workdir - - if {[catch {set r $all_cols($state)}]} { - puts "error: no column for state={$state} $path" - return $ui_workdir - } - return $r -} - -proc mapicon {state path} { +proc mapicon {w state path} { global all_icons - if {[catch {set r $all_icons($state)}]} { - puts "error: no icon for state={$state} $path" + if {[catch {set r $all_icons($state$w)}]} { + puts "error: no icon for $w state={$state} $path" return file_plain } return $r @@ -1307,64 +1288,69 @@ proc merge_state {path new_state {head_info {}} {index_info {}}} { return $state } +proc display_file_helper {w path icon_name old_m new_m} { + global file_lists + + if {$new_m eq {_}} { + set lno [lsearch -sorted $file_lists($w) $path] + if {$lno >= 0} { + set file_lists($w) [lreplace $file_lists($w) $lno $lno] + incr lno + $w conf -state normal + $w delete $lno.0 [expr {$lno + 1}].0 + $w conf -state disabled + } + } elseif {$old_m eq {_} && $new_m ne {_}} { + lappend file_lists($w) $path + set file_lists($w) [lsort -unique $file_lists($w)] + set lno [lsearch -sorted $file_lists($w) $path] + incr lno + $w conf -state normal + $w image create $lno.0 \ + -align center -padx 5 -pady 1 \ + -name $icon_name \ + -image [mapicon $w $new_m $path] + $w insert $lno.1 "[escape_path $path]\n" + $w conf -state disabled + } elseif {$old_m ne $new_m} { + $w conf -state normal + $w image conf $icon_name -image [mapicon $w $new_m $path] + $w conf -state disabled + } +} + proc display_file {path state} { - global file_states file_lists selected_paths + global file_states selected_paths + global ui_index ui_workdir set old_m [merge_state $path $state] set s $file_states($path) set new_m [lindex $s 0] - set new_w [mapcol $new_m $path] - set old_w [mapcol $old_m $path] - set new_icon [mapicon $new_m $path] + set icon_name [lindex $s 1] + + display_file_helper $ui_index $path $icon_name \ + [string index $old_m 0] \ + [string index $new_m 0] + display_file_helper $ui_workdir $path $icon_name \ + [string index $old_m 1] \ + [string index $new_m 1] if {$new_m eq {__}} { - set lno [lsearch -sorted $file_lists($old_w) $path] - if {$lno >= 0} { - set file_lists($old_w) \ - [lreplace $file_lists($old_w) $lno $lno] - incr lno - $old_w conf -state normal - $old_w delete $lno.0 [expr {$lno + 1}].0 - $old_w conf -state disabled - } unset file_states($path) catch {unset selected_paths($path)} - return } +} - if {$new_w ne $old_w} { - set lno [lsearch -sorted $file_lists($old_w) $path] - if {$lno >= 0} { - set file_lists($old_w) \ - [lreplace $file_lists($old_w) $lno $lno] - incr lno - $old_w conf -state normal - $old_w delete $lno.0 [expr {$lno + 1}].0 - $old_w conf -state disabled - } +proc display_all_files_helper {w path icon_name m} { + global file_lists - lappend file_lists($new_w) $path - set file_lists($new_w) [lsort $file_lists($new_w)] - set lno [lsearch -sorted $file_lists($new_w) $path] - incr lno - $new_w conf -state normal - $new_w image create $lno.0 \ - -align center -padx 5 -pady 1 \ - -name [lindex $s 1] \ - -image $new_icon - $new_w insert $lno.1 "[escape_path $path]\n" - if {[catch {set in_sel $selected_paths($path)}]} { - set in_sel 0 - } - if {$in_sel} { - $new_w tag add in_sel $lno.0 [expr {$lno + 1}].0 - } - $new_w conf -state disabled - } elseif {$new_icon ne [mapicon $old_m $path]} { - $new_w conf -state normal - $new_w image conf [lindex $s 1] -image $new_icon - $new_w conf -state disabled - } + lappend file_lists($w) $path + set lno [expr {[lindex [split [$w index end] .] 0] - 1}] + $w image create end \ + -align center -padx 5 -pady 1 \ + -name $icon_name \ + -image [mapicon $w $m $path] + $w insert end "[escape_path $path]\n" } proc display_all_files {} { @@ -1385,19 +1371,15 @@ proc display_all_files {} { foreach path [lsort [array names file_states]] { set s $file_states($path) set m [lindex $s 0] - set w [mapcol $m $path] - lappend file_lists($w) $path - set lno [expr {[lindex [split [$w index end] .] 0] - 1}] - $w image create end \ - -align center -padx 5 -pady 1 \ - -name [lindex $s 1] \ - -image [mapicon $m $path] - $w insert end "[escape_path $path]\n" - if {[catch {set in_sel $selected_paths($path)}]} { - set in_sel 0 + set icon_name [lindex $s 1] + + if {[string index $m 0] ne {_}} { + display_all_files_helper $ui_index $path \ + $icon_name [string index $m 0] } - if {$in_sel} { - $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + if {[string index $m 1] ne {_}} { + display_all_files_helper $ui_workdir $path \ + $icon_name [string index $m 1] } } @@ -1961,41 +1943,47 @@ static unsigned char file_merge_bits[] = { set ui_index .vpane.files.index.list set ui_workdir .vpane.files.workdir.list + +set all_icons(_$ui_index) file_plain +set all_icons(A$ui_index) file_fulltick +set all_icons(M$ui_index) file_fulltick +set all_icons(D$ui_index) file_removed +set all_icons(U$ui_index) file_merge + +set all_icons(_$ui_workdir) file_plain +set all_icons(M$ui_workdir) file_mod +set all_icons(D$ui_workdir) file_question +set all_icons(O$ui_workdir) file_plain + set max_status_desc 0 foreach i { - {__ i plain "Unmodified"} - {_M i mod "Modified"} - {M_ i fulltick "Added to commit"} - {MM i parttick "Partially included"} - {MD i question "Added (but gone)"} + {__ "Unmodified"} + {_M "Modified"} + {M_ "Added to commit"} + {MM "Partially added"} + {MD "Added (but gone)"} - {_O o plain "Untracked"} - {A_ o fulltick "Added by commit"} - {AM o parttick "Partially added"} - {AD o question "Added (but gone)"} + {_O "Untracked"} + {A_ "Added by commit"} + {AM "Partially added"} + {AD "Added (but gone)"} - {_D i question "Missing"} - {DD i removed "Removed by commit"} - {D_ i removed "Removed by commit"} - {DO i removed "Removed (still exists)"} - {DM i removed "Removed (but modified)"} + {_D "Missing"} + {DD "Removed by commit"} + {D_ "Removed by commit"} + {DO "Removed (still exists)"} + {DM "Removed (but modified)"} - {UD i merge "Merge conflicts"} - {UM i merge "Merge conflicts"} - {U_ i merge "Merge conflicts"} + {UD "Merge conflicts"} + {UM "Merge conflicts"} + {U_ "Merge conflicts"} } { - if {$max_status_desc < [string length [lindex $i 3]]} { - set max_status_desc [string length [lindex $i 3]] + if {$max_status_desc < [string length [lindex $i 1]]} { + set max_status_desc [string length [lindex $i 1]] } - if {[lindex $i 1] eq {i}} { - set all_cols([lindex $i 0]) $ui_index - } else { - set all_cols([lindex $i 0]) $ui_workdir - } - set all_icons([lindex $i 0]) file_[lindex $i 2] - set all_descs([lindex $i 0]) [lindex $i 3] + set all_descs([lindex $i 0]) [lindex $i 1] } -unset filemask i +unset i ###################################################################### ## @@ -3302,7 +3290,7 @@ pack .vpane -anchor n -side top -fill both -expand 1 # -- Index File List # frame .vpane.files.index -height 100 -width 400 -label .vpane.files.index.title -text {Modified Files} \ +label .vpane.files.index.title -text {Changes To Be Committed} \ -background green \ -font font_ui text $ui_index -background white -borderwidth 0 \ @@ -3320,7 +3308,7 @@ pack $ui_index -side left -fill both -expand 1 # -- Working Directory File List # frame .vpane.files.workdir -height 100 -width 100 -label .vpane.files.workdir.title -text {Untracked Files} \ +label .vpane.files.workdir.title -text {Changed But Not Updated} \ -background red \ -font font_ui text $ui_workdir -background white -borderwidth 0 \ From b4b2b8454bca8f0636260358e3a63b9097351fc1 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 22:57:19 -0500 Subject: [PATCH 250/548] git-gui: Correct DD file state to be only D_. Apparently my earlier suspicion that the file state DD was a bug was correct. A file which has been deleted from the working directory and from the index will always get the state of D_ during a rescan. Thus the only valid state for this to have is D_. We should always use only D_ internally during our state changes. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 9fa467ab9f..9a2b70b475 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1104,7 +1104,7 @@ proc commit_committree {fd_wt curHEAD msg} { __ - A_ - M_ - - DD { + D_ { unset file_states($path) catch {unset selected_paths($path)} } @@ -1516,7 +1516,7 @@ proc write_update_index {fd pathList totalCnt batch msg after} { AD - MD - UD - - _D {set new DD} + _D {set new D_} _M - MM - @@ -1969,7 +1969,6 @@ foreach i { {AD "Added (but gone)"} {_D "Missing"} - {DD "Removed by commit"} {D_ "Removed by commit"} {DO "Removed (still exists)"} {DM "Removed (but modified)"} @@ -2872,7 +2871,7 @@ proc toggle_or_diff {w x y} { switch -glob -- [lindex $file_states($path) 0] { A_ - M_ - - DD - + D_ - DO - DM { update_indexinfo \ From 5989a57734dfd4d6cb7eb0045a008dfdc90c2fbb Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 22:58:52 -0500 Subject: [PATCH 251/548] git-gui: Remove invalid DM state. The DM state cannot really happen. Its implying that the file has been deleted in the index, but the file in the working directory has been modified relative to the file in the index. This is complete nonsense, the file doesn't exist in the index for it to be different against! Signed-off-by: Shawn O. Pearce --- git-gui.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 9a2b70b475..be286b0e78 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1114,8 +1114,7 @@ proc commit_committree {fd_wt curHEAD msg} { AM - AD - MM - - MD - - DM { + MD { set file_states($path) [list \ _[string index $m 1] \ [lindex $s 1] \ @@ -2872,8 +2871,7 @@ proc toggle_or_diff {w x y} { A_ - M_ - D_ - - DO - - DM { + DO { update_indexinfo \ "Removing [short_path $path] from commit" \ [list $path] \ From ac39160cd27cd4a57dbea80db6fbaec21ddb6ff4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 23:00:28 -0500 Subject: [PATCH 252/548] git-gui: Cleanup state descriptions. Updated the state descriptions for individual file states to try and make them more closely align with what git-runstatus might display. This way a user who is reading Git documentation will be less confused by our descriptions. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index be286b0e78..1d342392ab 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1957,24 +1957,24 @@ set all_icons(O$ui_workdir) file_plain set max_status_desc 0 foreach i { {__ "Unmodified"} - {_M "Modified"} - {M_ "Added to commit"} - {MM "Partially added"} - {MD "Added (but gone)"} - {_O "Untracked"} - {A_ "Added by commit"} - {AM "Partially added"} - {AD "Added (but gone)"} + {_M "Modified, not staged"} + {M_ "Staged for commit"} + {MM "Portions staged for commit"} + {MD "Staged for commit, missing"} + + {_O "Untracked, not staged"} + {A_ "Staged for commit"} + {AM "Portions staged for commit"} + {AD "Staged for commit, missing"} {_D "Missing"} - {D_ "Removed by commit"} - {DO "Removed (still exists)"} - {DM "Removed (but modified)"} + {D_ "Staged for removal"} + {DO "Staged for removal, still present"} - {UD "Merge conflicts"} - {UM "Merge conflicts"} - {U_ "Merge conflicts"} + {U_ "Requires merge resolution"} + {UM "Requires merge resolution"} + {UD "Requires merge resolution"} } { if {$max_status_desc < [string length [lindex $i 1]]} { set max_status_desc [string length [lindex $i 1]] From 93e912c5e62220bd3cc2d00bfe357ed8fd44d413 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 23:07:04 -0500 Subject: [PATCH 253/548] git-gui: Refactor add/remove proc names to align with reality. Now that core Git refers to resetting paths in the index as "unstaging" the paths we should do the same in git-gui, both internally in our code and also within the menu action name. The same follows for our staging logic, as core Git refers to this as 'add'. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 1d342392ab..84ec57366c 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2287,7 +2287,7 @@ proc do_rescan {} { rescan {set ui_status_value {Ready.}} } -proc remove_helper {txt paths} { +proc unstage_helper {txt paths} { global file_states current_diff if {![lock_index begin-update]} return @@ -2316,21 +2316,21 @@ proc remove_helper {txt paths} { } } -proc do_remove_selection {} { +proc do_unstage_selection {} { global current_diff selected_paths if {[array size selected_paths] > 0} { - remove_helper \ - {Removing selected files from commit} \ + unstage_helper \ + {Unstaging selected files from commit} \ [array names selected_paths] } elseif {$current_diff ne {}} { - remove_helper \ - "Removing [short_path $current_diff] from commit" \ + unstage_helper \ + "Unstaging [short_path $current_diff] from commit" \ [list $current_diff] } } -proc include_helper {txt paths} { +proc add_helper {txt paths} { global file_states current_diff if {![lock_index begin-update]} return @@ -2364,21 +2364,21 @@ proc include_helper {txt paths} { } } -proc do_include_selection {} { +proc do_add_selection {} { global current_diff selected_paths if {[array size selected_paths] > 0} { - include_helper \ + add_helper \ {Adding selected files} \ [array names selected_paths] } elseif {$current_diff ne {}} { - include_helper \ + add_helper \ "Adding [short_path $current_diff]" \ [list $current_diff] } } -proc do_include_all {} { +proc do_add_all {} { global file_states set paths [list] @@ -2392,7 +2392,7 @@ proc do_include_all {} { _D {lappend paths $path} } } - include_helper \ + add_helper \ {Adding all modified files} \ $paths } @@ -2873,7 +2873,7 @@ proc toggle_or_diff {w x y} { D_ - DO { update_indexinfo \ - "Removing [short_path $path] from commit" \ + "Unstaging [short_path $path] from commit" \ [list $path] \ [concat $after {set ui_status_value {Ready.}}] } @@ -3151,20 +3151,20 @@ lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Add To Commit} \ - -command do_include_selection \ + -command do_add_selection \ -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Add All To Commit} \ - -command do_include_all \ + -command do_add_all \ -accelerator $M1T-I \ -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] -.mbar.commit add command -label {Remove From Commit} \ - -command do_remove_selection \ +.mbar.commit add command -label {Unstage From Commit} \ + -command do_unstage_selection \ -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] @@ -3355,7 +3355,7 @@ lappend disable_on_lock \ {.vpane.lower.commarea.buttons.rescan conf -state} button .vpane.lower.commarea.buttons.incall -text {Add All} \ - -command do_include_all \ + -command do_add_all \ -font font_ui pack .vpane.lower.commarea.buttons.incall -side top -fill x lappend disable_on_lock \ @@ -3651,8 +3651,8 @@ unset gm # -- Key Bindings # bind $ui_comm <$M1B-Key-Return> {do_commit;break} -bind $ui_comm <$M1B-Key-i> {do_include_all;break} -bind $ui_comm <$M1B-Key-I> {do_include_all;break} +bind $ui_comm <$M1B-Key-i> {do_add_all;break} +bind $ui_comm <$M1B-Key-I> {do_add_all;break} bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break} bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break} bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break} @@ -3681,8 +3681,8 @@ bind all <$M1B-Key-r> do_rescan bind all <$M1B-Key-R> do_rescan bind . <$M1B-Key-s> do_signoff bind . <$M1B-Key-S> do_signoff -bind . <$M1B-Key-i> do_include_all -bind . <$M1B-Key-I> do_include_all +bind . <$M1B-Key-i> do_add_all +bind . <$M1B-Key-I> do_add_all bind . <$M1B-Key-Return> do_commit bind all <$M1B-Key-q> do_quit bind all <$M1B-Key-Q> do_quit From de5f6d5d178a1c8113aaca34c8f6b3842732a741 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 23:10:30 -0500 Subject: [PATCH 254/548] git-gui: Add or unstage based on the specific icon used. Rather than relying on the file state and just inverting it, we should look at which file icon the user clicked on. If they clicked on the one in the "Changes To Be Committed" list then they want to unstage the file. If they clicked on the icon in the "Changed But Not Updated" list then they want to add the file to the commit. This should be much more reliable about capturing the user's intent then looking at the file state. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 84ec57366c..c1ee48bf07 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2867,23 +2867,17 @@ proc toggle_or_diff {w x y} { } else { set after {} } - switch -glob -- [lindex $file_states($path) 0] { - A_ - - M_ - - D_ - - DO { + if {$w eq $ui_index} { update_indexinfo \ "Unstaging [short_path $path] from commit" \ [list $path] \ [concat $after {set ui_status_value {Ready.}}] - } - ?? { + } elseif {$w eq $ui_workdir} { update_index \ "Adding [short_path $path]" \ [list $path] \ [concat $after {set ui_status_value {Ready.}}] } - } } else { show_diff $path $w $lno } From 7d40edfa06adf7f31f787bb6379c1eb454242c19 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 23:20:17 -0500 Subject: [PATCH 255/548] git-gui: Refactor the revert (aka checkout-index) implementation. We can revert any file which has a valid stage 0 (is not unmerged) and which is has a working directory status of M or D. This vastly simplifies our pattern matching on file status when building up the list of files to perform a checkout-index against. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index c1ee48bf07..7bd2b87fe3 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1597,20 +1597,14 @@ proc write_checkout_index {fd pathList totalCnt batch msg after} { {incr i -1} { set path [lindex $pathList $update_index_cp] incr update_index_cp - switch -glob -- [lindex $file_states($path) 0] { - AM - - AD {set new A_} - MM - - MD {set new M_} - _M - - _D {set new __} - ?? {continue} + U? {continue} + ?M - + ?D { + puts -nonewline $fd "$path\0" + display_file $path ?_ + } } - - puts -nonewline $fd $path - puts -nonewline $fd "\0" - display_file $path $new } set ui_status_value [format \ @@ -2406,12 +2400,9 @@ proc revert_helper {txt paths} { set after {} foreach path $paths { switch -glob -- [lindex $file_states($path) 0] { - AM - - AD - - MM - - MD - - _M - - _D { + U? {continue} + ?M - + ?D { lappend pathList $path if {$path eq $current_diff} { set after {reshow_diff;} From b4b491e388627b75ed3aee5dc60e4199eae7b362 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 23:33:34 -0500 Subject: [PATCH 256/548] git-gui: Refactor the add to commit state filters. The list of states which are valid for update-index were a little too verbose and fed a few too many cases to the program. We can do better with less lines of code by using more pattern matching, and since we already were globbing here there's little change in runtime cost. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 50 +++++++++++++++----------------------------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 7bd2b87fe3..954b1983e8 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1512,26 +1512,15 @@ proc write_update_index {fd pathList totalCnt batch msg after} { incr update_index_cp switch -glob -- [lindex $file_states($path) 0] { - AD - - MD - - UD - - _D {set new D_} - - _M - - MM - - UM - - U_ - - M_ {set new M_} - + AD {set new __} + ?D {set new D_} _O - - AM - - A_ {set new A_} - + AM {set new A_} + U_ - + ?M {set new M_} ?? {continue} } - - puts -nonewline $fd $path - puts -nonewline $fd "\0" + puts -nonewline $fd "$path\0" display_file $path $new } @@ -2333,14 +2322,10 @@ proc add_helper {txt paths} { set after {} foreach path $paths { switch -glob -- [lindex $file_states($path) 0] { - AM - - AD - - MM - - MD - - U? - - _M - - _D - - _O { + _O - + ?M - + ?D - + U? { lappend pathList $path if {$path eq $current_diff} { set after {reshow_diff;} @@ -2377,18 +2362,13 @@ proc do_add_all {} { set paths [list] foreach path [array names file_states] { - switch -- [lindex $file_states($path) 0] { - AM - - AD - - MM - - MD - - _M - - _D {lappend paths $path} + switch -glob -- [lindex $file_states($path) 0] { + U? {continue} + ?M - + ?D {lappend paths $path} } } - add_helper \ - {Adding all modified files} \ - $paths + add_helper {Adding all changed files} $paths } proc revert_helper {txt paths} { From 31a8d1968ed12c0b98c1c34d789a66f54ecbbc13 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 23:35:39 -0500 Subject: [PATCH 257/548] git-gui: Simplify printing of index info to update-index. During unstaging we can simplify the way we perform the output by combining our four puts into a single call. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 954b1983e8..984535687c 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1447,10 +1447,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch msg after} { set info [lindex $s 2] if {$info eq {}} continue - puts -nonewline $fd $info - puts -nonewline $fd "\t" - puts -nonewline $fd $path - puts -nonewline $fd "\0" + puts -nonewline $fd "$info\t$path\0" display_file $path $new } From 833eda736ad1ec99c30b2c4d7a565ccc406c97a0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 23:46:53 -0500 Subject: [PATCH 258/548] git-gui: Only permit selection in one list at a time. Now that our lists represent more defined states it no longer makes any sense to permit a user to make selections from both lists at once, as the each available operation acts only on files whose status corresponds to only one of the lists. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 984535687c..a1002ecab8 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1355,7 +1355,7 @@ proc display_all_files_helper {w path icon_name m} { proc display_all_files {} { global ui_index ui_workdir global file_states file_lists - global last_clicked selected_paths + global last_clicked $ui_index conf -state normal $ui_workdir conf -state normal @@ -2852,18 +2852,21 @@ proc toggle_or_diff {w x y} { } proc add_one_to_selection {w x y} { - global file_lists - global last_clicked selected_paths + global file_lists last_clicked selected_paths - set pos [split [$w index @$x,$y] .] - set lno [lindex $pos 0] - set col [lindex $pos 1] + set lno [lindex [split [$w index @$x,$y] .] 0] set path [lindex $file_lists($w) [expr {$lno - 1}]] if {$path eq {}} { set last_clicked {} return } + if {$last_clicked ne {} + && [lindex $last_clicked 0] ne $w} { + array unset selected_paths + [lindex $last_clicked 0] tag remove in_sel 0.0 end + } + set last_clicked [list $w $lno] if {[catch {set in_sel $selected_paths($path)}]} { set in_sel 0 @@ -2878,16 +2881,14 @@ proc add_one_to_selection {w x y} { } proc add_range_to_selection {w x y} { - global file_lists - global last_clicked selected_paths + global file_lists last_clicked selected_paths if {[lindex $last_clicked 0] ne $w} { toggle_or_diff $w $x $y return } - set pos [split [$w index @$x,$y] .] - set lno [lindex $pos 0] + set lno [lindex [split [$w index @$x,$y] .] 0] set lc [lindex $last_clicked 1] if {$lc < $lno} { set begin $lc From ab26abd483d2e63f782a4335331ba6b2cfeed1bc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 20 Jan 2007 23:52:19 -0500 Subject: [PATCH 259/548] git-gui: Pad the cancel/save buttons in the options window. It looks horrible to have the cancel and save buttons wedged up against each other in our options dialog. Therefore toss a 5 pixel pad between them. 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 a1002ecab8..13cd1b9b4a 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2582,7 +2582,7 @@ proc do_options {} { button $w.buttons.cancel -text {Cancel} \ -font font_ui \ -command [list destroy $w] - pack $w.buttons.cancel -side right + 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" \ From c5ab47cbe4cf9f2f3068c9bf23a666404b615176 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 01:31:14 -0500 Subject: [PATCH 260/548] git-gui: Implemented create branch GUI. Users may now create new branches by activating the Branch->Create menu item. This opens a dialog which lets the user enter the new branch name and select the starting revision for the new branch. For the starting revision we allow the user to either select from a list of known heads (aka local branches) or to enter an arbitrary SHA1 expression. For either creation technique we run the starting revision through rev-parse to verify it is valid before trying to create the ref with update-ref. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 3 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 13cd1b9b4a..605fc17dc5 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1622,9 +1622,25 @@ proc load_all_heads {} { set all_heads [lsort $all_heads] } -proc populate_branch_menu {m} { +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 + } + } + $m add separator foreach b $all_heads { $m add radiobutton \ @@ -1638,8 +1654,169 @@ proc populate_branch_menu {m} { } } +proc do_create_branch_action {w} { + global all_heads null_sha1 + global create_branch_checkout create_branch_revtype create_branch_head + + set newbranch [string trim [$w.name.t get 0.0 end]] + if {![catch {exec 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.name.t + return + } + if {[catch {exec 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.name.t + return + } + + set rev {} + switch -- $create_branch_revtype { + head {set rev $create_branch_head} + expression {set rev [string trim [$w.from.exp.t get 0.0 end]]} + } + if {[catch {set cmt [exec git rev-parse --verify "${rev}^0"]}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $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]} { + 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 +} + proc do_create_branch {} { - error "NOT IMPLEMENTED" + global all_heads current_branch + global create_branch_checkout create_branch_revtype create_branch_head + + set create_branch_checkout true + set create_branch_revtype head + set create_branch_head $current_branch + + 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 \ + -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.name \ + -text {Branch Description} \ + -font font_ui + label $w.name.l -text {Name:} -font font_ui + text $w.name.t \ + -height 1 \ + -width 40 \ + -font font_ui + bind $w.name.t "focus $w.postActions.checkout;break" + bind $w.name.t "focus $w.from.exp.t;break" + bind $w.name.t "do_create_branch_action $w;break" + bind $w.name.t { + if {{%K} ne {BackSpace} + && {%K} ne {Tab} + && {%K} ne {Escape} + && {%K} ne {Return}} { + if {%k <= 32} break + if {[string first %A {~^:?*[}] >= 0} break + } + } + pack $w.name.l -side left -padx 5 + pack $w.name.t -side left -fill x -expand 1 + pack $w.name -anchor nw -fill x -pady 5 -padx 5 + + labelframe $w.from \ + -text {Starting Revision} \ + -font font_ui + frame $w.from.head + radiobutton $w.from.head.r \ + -text {Local Branch:} \ + -value head \ + -variable create_branch_revtype \ + -font font_ui + eval tk_optionMenu $w.from.head.m create_branch_head $all_heads + pack $w.from.head.r -side left + pack $w.from.head.m -side left + frame $w.from.exp + radiobutton $w.from.exp.r \ + -text {Revision Expression:} \ + -value expression \ + -variable create_branch_revtype \ + -font font_ui + text $w.from.exp.t \ + -height 1 \ + -width 50 \ + -font font_ui + bind $w.from.exp.t "focus $w.name.t;break" + bind $w.from.exp.t "focus $w.postActions.checkout;break" + bind $w.from.exp.t "do_create_branch_action $w;break" + pack $w.from.exp.r -side left + pack $w.from.exp.t -side left -fill x -expand 1 + pack $w.from.head -padx 5 -fill x -expand 1 + pack $w.from.exp -padx 5 -fill x -expand 1 + pack $w.from -anchor nw -fill x -pady 5 -padx 5 + + labelframe $w.postActions \ + -text {Post Creation Actions} \ + -font font_ui + checkbutton $w.postActions.checkout \ + -text {Checkout after creation} \ + -offvalue false \ + -onvalue true \ + -variable create_branch_checkout \ + -font font_ui + pack $w.postActions.checkout -anchor nw + pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 + + bind $w "grab $w; focus $w.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 {} { @@ -3734,7 +3911,7 @@ if {!$single_commit} { load_all_remotes load_all_heads - populate_branch_menu .mbar.branch + populate_branch_menu populate_fetch_menu .mbar.fetch populate_pull_menu .mbar.pull populate_push_menu .mbar.push From bd29ebc3927780c2bc6c91abb12054a283201c15 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 01:34:55 -0500 Subject: [PATCH 261/548] git-gui: Bind M1-N to create branch. Creating branches is a common enough activity within a Git project that we probably should give it a keyboard accelerator. N is not currently used and seems reasonable to stand for "New Branch". To bad our menu calls it create. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index 605fc17dc5..db6d014e66 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3248,6 +3248,7 @@ if {!$single_commit} { .mbar.branch add command -label {Create...} \ -command do_create_branch \ + -accelerator $M1T-N \ -font font_ui lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] @@ -3815,6 +3816,11 @@ 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} +if {!$single_commit} { + bind . <$M1B-Key-n> do_create_branch + bind . <$M1B-Key-N> do_create_branch +} + bind . do_quit bind all do_rescan bind all <$M1B-Key-r> do_rescan From 887412d4e84c8249987a686aded6a7eab4345cb8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 02:14:00 -0500 Subject: [PATCH 262/548] git-gui: Implemented local branch deletion. Users can now delete a local branch by selecting from a list of available branches. The list automatically does not include the current branch, as deleting the current branch could be quite dangerous and should not be supported. The user may also chose to have us verify the branches are fully merged into another branch before deleting them. By default we select the current branch, matching 'git branch -d' behavior, but the user could also select any other local branch. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 2 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index db6d014e66..48b11111b2 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1713,7 +1713,6 @@ proc do_create_branch_action {w} { lappend all_heads $newbranch set all_heads [lsort $all_heads] populate_branch_menu - destroy $w } @@ -1819,8 +1818,138 @@ proc do_create_branch {} { tkwait window $w } +proc do_delete_branch_action {w} { + global all_heads + global delete_branch_checkhead delete_branch_head + + set to_delete [list] + set msg {Are you sure you want to delete the following branches? + +} + foreach i [$w.list.l curselection] { + set b [$w.list.l get $i] + if {[catch {set o [exec git rev-parse --verify $b]}]} continue + if {$delete_branch_checkhead} { + if {[catch {set m [exec git merge-base $o $delete_branch_head]}]} continue + if {$o ne $m} continue + } + lappend to_delete [list $b $o] + append msg " - $b\n" + } + if {$to_delete eq {}} { + tk_messageBox \ + -icon info \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message {No branches are able to be deleted. + +This is likely because you did not select any branches, +or all selected branches are not completely merged. +} + return + } + append msg { +It can be difficult to recover deleted branches. + +Delete the above 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 {exec git update-ref -d "refs/heads/$b" $o} err]} { + append failed " - $b: $err\n" + } else { + set x [lsearch -sorted $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 {} { - error "NOT IMPLEMENTED" + global all_heads current_branch + global delete_branch_checkhead delete_branch_head + + set delete_branch_checkhead 1 + set delete_branch_head $current_branch + + 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 \ + -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 + listbox $w.list.l \ + -height 10 \ + -width 50 \ + -selectmode extended \ + -font font_ui + foreach h $all_heads { + if {$h ne $current_branch} { + $w.list.l insert end $h + } + } + pack $w.list.l -fill both -pady 5 -padx 5 + pack $w.list -fill both -pady 5 -padx 5 + + labelframe $w.validate \ + -text {Only Delete If} \ + -font font_ui + frame $w.validate.head + checkbutton $w.validate.head.r \ + -text {Already Merged Into Local Branch:} \ + -variable delete_branch_checkhead \ + -font font_ui + eval tk_optionMenu $w.validate.head.m delete_branch_head $all_heads + pack $w.validate.head.r -side left + pack $w.validate.head.m -side left + pack $w.validate.head -padx 5 -fill x -expand 1 + pack $w.validate -anchor nw -fill x -pady 5 -padx 5 + + bind $w "grab $w; focus $w" + bind $w "destroy $w" + wm title $w "[appname] ([reponame]): Delete Branch" + tkwait window $w } proc switch_branch {b} { From 0a25f93cdaee5ef4da67c4b1db2cb06bfc88e84a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 02:21:45 -0500 Subject: [PATCH 263/548] git-gui: Allow users to delete branches merged upstream. Most of the time when you are deleting branches you want to delete those which have been merged into your upstream source. Typically that means it has been merged into the tip commit of some tracking branch, and the current branch (or any other head) doesn't matter. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 48b11111b2..85be9833a0 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1891,7 +1891,7 @@ Delete the above branches?} } proc do_delete_branch {} { - global all_heads current_branch + global all_heads tracking_branches current_branch global delete_branch_checkhead delete_branch_head set delete_branch_checkhead 1 @@ -1932,15 +1932,23 @@ proc do_delete_branch {} { pack $w.list.l -fill both -pady 5 -padx 5 pack $w.list -fill both -pady 5 -padx 5 + set all_trackings [list] + foreach b [array names tracking_branches] { + regsub ^refs/(heads|remotes)/ $b {} b + lappend all_trackings $b + } + labelframe $w.validate \ -text {Only Delete If} \ -font font_ui frame $w.validate.head checkbutton $w.validate.head.r \ - -text {Already Merged Into Local Branch:} \ + -text {Already Merged Into:} \ -variable delete_branch_checkhead \ -font font_ui - eval tk_optionMenu $w.validate.head.m delete_branch_head $all_heads + eval tk_optionMenu $w.validate.head.m delete_branch_head \ + $all_heads \ + [lsort -unique $all_trackings] pack $w.validate.head.r -side left pack $w.validate.head.m -side left pack $w.validate.head -padx 5 -fill x -expand 1 From 859d8057bd0194bf21fac74d196cd9c07e2fec19 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 02:27:26 -0500 Subject: [PATCH 264/548] git-gui: Allow creating branches from tracking heads. Sometimes you want to create a branch from a remote tracking branch. Needing to enter it in the revision expression field is very annoying, so instead let the user select it from a list of known tracking branches. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 85be9833a0..c187e9bbc2 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1656,7 +1656,8 @@ proc populate_branch_menu {} { proc do_create_branch_action {w} { global all_heads null_sha1 - global create_branch_checkout create_branch_revtype create_branch_head + global create_branch_checkout create_branch_revtype + global create_branch_head create_branch_trackinghead set newbranch [string trim [$w.name.t get 0.0 end]] if {![catch {exec git show-ref --verify -- "refs/heads/$newbranch"}]} { @@ -1683,6 +1684,7 @@ proc do_create_branch_action {w} { set rev {} switch -- $create_branch_revtype { head {set rev $create_branch_head} + tracking {set rev $create_branch_trackinghead} expression {set rev [string trim [$w.from.exp.t get 0.0 end]]} } if {[catch {set cmt [exec git rev-parse --verify "${rev}^0"]}]} { @@ -1717,12 +1719,14 @@ proc do_create_branch_action {w} { } proc do_create_branch {} { - global all_heads current_branch - global create_branch_checkout create_branch_revtype create_branch_head + global all_heads current_branch tracking_branches + global create_branch_checkout create_branch_revtype + global create_branch_head create_branch_trackinghead set create_branch_checkout true set create_branch_revtype head set create_branch_head $current_branch + set create_branch_trackinghead {} set w .branch_editor toplevel $w @@ -1768,6 +1772,16 @@ proc do_create_branch {} { pack $w.name.t -side left -fill x -expand 1 pack $w.name -anchor nw -fill x -pady 5 -padx 5 + set all_trackings [list] + foreach b [array names tracking_branches] { + regsub ^refs/(heads|remotes)/ $b {} b + lappend all_trackings $b + } + set all_trackings [lsort -unique $all_trackings] + if {$all_trackings ne {}} { + set create_branch_trackinghead [lindex $all_trackings 0] + } + labelframe $w.from \ -text {Starting Revision} \ -font font_ui @@ -1780,6 +1794,17 @@ proc do_create_branch {} { eval tk_optionMenu $w.from.head.m create_branch_head $all_heads pack $w.from.head.r -side left pack $w.from.head.m -side left + frame $w.from.tracking + radiobutton $w.from.tracking.r \ + -text {Tracking Branch:} \ + -value tracking \ + -variable create_branch_revtype \ + -font font_ui + eval tk_optionMenu $w.from.tracking.m \ + create_branch_trackinghead \ + $all_trackings + pack $w.from.tracking.r -side left + pack $w.from.tracking.m -side left frame $w.from.exp radiobutton $w.from.exp.r \ -text {Revision Expression:} \ @@ -1796,6 +1821,7 @@ proc do_create_branch {} { pack $w.from.exp.r -side left pack $w.from.exp.t -side left -fill x -expand 1 pack $w.from.head -padx 5 -fill x -expand 1 + pack $w.from.tracking -padx 5 -fill x -expand 1 pack $w.from.exp -padx 5 -fill x -expand 1 pack $w.from -anchor nw -fill x -pady 5 -padx 5 From 62efea111fe6935c5916f3537fb135bdab324264 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 03:13:13 -0500 Subject: [PATCH 265/548] git-gui: Use borders on text fields in branch dialog. On Mac OS X wish does not draw borders around text fields, making the field look like its not even there until the user focuses into it. I don't know the Mac OS X UI standards very well, but that just seems wrong. Other applications (e.g. Terminal.app) show their input boxes with a sunken relief, so we should do the same. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index c187e9bbc2..26b1f346fc 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1753,6 +1753,8 @@ proc do_create_branch {} { -font font_ui label $w.name.l -text {Name:} -font font_ui text $w.name.t \ + -borderwidth 1 \ + -relief sunken \ -height 1 \ -width 40 \ -font font_ui @@ -1812,6 +1814,8 @@ proc do_create_branch {} { -variable create_branch_revtype \ -font font_ui text $w.from.exp.t \ + -borderwidth 1 \ + -relief sunken \ -height 1 \ -width 50 \ -font font_ui From 6f48f3a6885e9dcc7d05a3a392723084dbf960d2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 04:19:33 -0500 Subject: [PATCH 266/548] git-gui: Remove 'Allow Partially Added Files' option. Now that we take the approach of core Git where we allow the user to stage their changes directly into the index all of the time there is absolutely no reason to have the Allow Partially Added Files option, nor is there a reason or desire to default that option to false. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 26b1f346fc..e82eb6bbe5 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -524,24 +524,6 @@ proc rescan_done {fd buf after} { prune_selection unlock_index display_all_files - - if {$repo_config(gui.partialinclude) ne {true}} { - set pathList [list] - foreach path [array names file_states] { - switch -- [lindex $file_states($path) 0] { - A? - - M? {lappend pathList $path} - } - } - if {$pathList ne {}} { - update_index \ - "Updating included files" \ - $pathList \ - [concat {reshow_diff;} $after] - return - } - } - reshow_diff uplevel #0 $after } @@ -918,27 +900,6 @@ A good commit message has the following format: return } - # -- Update included files if partialincludes are off. - # - if {$repo_config(gui.partialinclude) ne {true}} { - set pathList [list] - foreach path [array names file_states] { - switch -glob -- [lindex $file_states($path) 0] { - A? - - M? {lappend pathList $path} - } - } - if {$pathList ne {}} { - unlock_index - update_index \ - "Updating included files" \ - $pathList \ - [concat {lock_index update;} \ - [list commit_prehook $curHEAD $msg]] - return - } - } - commit_prehook $curHEAD $msg } @@ -2939,7 +2900,6 @@ proc do_options {} { pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 foreach option { - {b partialinclude {Allow Partially Added Files}} {b pullsummary {Show Pull Summary}} {b trustmtime {Trust File Modification Timestamps}} {i diffcontext {Number of Diff Context Lines}} @@ -3299,7 +3259,6 @@ proc apply_config {} { set default_config(gui.trustmtime) false set default_config(gui.pullsummary) true -set default_config(gui.partialinclude) false set default_config(gui.diffcontext) 5 set default_config(gui.fontui) [font configure font_ui] set default_config(gui.fontdiff) [font configure font_diff] From 6858efbda324ab30f8857a06000933bcc55b11cc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 04:28:22 -0500 Subject: [PATCH 267/548] git-gui: Move commit_prehook into commit_tree. The only reason the commit_prehook logic was broken out into its own proc was so it could be invoked after the current set of files that were already added to the commit could be refreshed if 'Allow Partially Added Files' was set to false. Now that we no longer even offer that option to the user there is no reason to keep this code broken out into its own procedure. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index e82eb6bbe5..e4676bf795 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -825,6 +825,7 @@ proc committer_ident {} { proc commit_tree {} { global HEAD commit_type file_states ui_comm repo_config + global ui_status_value pch_error if {![lock_index update]} return if {[committer_ident] eq {}} return @@ -900,12 +901,8 @@ A good commit message has the following format: return } - commit_prehook $curHEAD $msg -} - -proc commit_prehook {curHEAD msg} { - global ui_status_value pch_error - + # -- Run the pre-commit hook. + # set pchook [gitdir hooks pre-commit] # On Cygwin [file executable] might lie so we need to ask From 4f9d8519fb3297063392d0268c0430fe5a6a02f1 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 04:51:45 -0500 Subject: [PATCH 268/548] git-gui: Improve the branch delete confirmation dialogs. If the user is deleting a branch which is fully merged into the selected test branch we should not confirm the delete with them, the fact that the branch is fully merged means we can recover the branch and no work will be lost. If a branch is not fully merged, we should warn the user about which branch(es) that is and continue deleting those which are fully merged. We should only delete a branch if the user disables the merge check, and in that case we should confirm with the user that a delete should occur as this may cause them to lose changes. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index e4676bf795..c9143973d7 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1811,43 +1811,43 @@ proc do_delete_branch_action {w} { global delete_branch_checkhead delete_branch_head set to_delete [list] - set msg {Are you sure you want to delete the following branches? - -} + set not_merged [list] foreach i [$w.list.l curselection] { set b [$w.list.l get $i] if {[catch {set o [exec git rev-parse --verify $b]}]} continue if {$delete_branch_checkhead} { if {[catch {set m [exec git merge-base $o $delete_branch_head]}]} continue - if {$o ne $m} continue + if {$o ne $m} { + lappend not_merged $b + continue + } } lappend to_delete [list $b $o] - append msg " - $b\n" } - if {$to_delete eq {}} { + if {$not_merged ne {}} { + set msg "The following branches are not completely merged into $delete_branch_head: + + - [join $not_merged "\n - "]" tk_messageBox \ -icon info \ -type ok \ -title [wm title $w] \ -parent $w \ - -message {No branches are able to be deleted. - -This is likely because you did not select any branches, -or all selected branches are not completely merged. -} - return + -message $msg } - append msg { -It can be difficult to recover deleted branches. + if {$to_delete eq {}} return + if {!$delete_branch_checkhead} { + set msg {Recovering deleted branches is difficult. -Delete the above branches?} - if {[tk_messageBox \ - -icon warning \ - -type yesno \ - -title [wm title $w] \ - -parent $w \ - -message $msg] ne yes} { - return +Delete the selected branches?} + if {[tk_messageBox \ + -icon warning \ + -type yesno \ + -title [wm title $w] \ + -parent $w \ + -message $msg] ne yes} { + return + } } set failed {} From 3dcdfdf015746c7ec10d915705c1a76cc9d9de31 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 04:54:01 -0500 Subject: [PATCH 269/548] git-gui: Don't delete the test target branch. Its possible for the user to select a branch for the merge test (while deleting branches) and also select that branch for deletion. Doing so would have bypassed our merge check for that branch, as a branch is always a strict subset of itself. So we will simply skip over a branch and not delete it if that is the branch which the user selected for the merge check. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/git-gui.sh b/git-gui.sh index c9143973d7..e79a0ae073 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1816,6 +1816,7 @@ proc do_delete_branch_action {w} { set b [$w.list.l get $i] if {[catch {set o [exec git rev-parse --verify $b]}]} continue if {$delete_branch_checkhead} { + if {$b eq $delete_branch_head} continue if {[catch {set m [exec git merge-base $o $delete_branch_head]}]} continue if {$o ne $m} { lappend not_merged $b From e21594a998d71eff39d80af878162abe94ef6e17 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 04:57:11 -0500 Subject: [PATCH 270/548] git-gui: Attempt to checkout the new branch after creation. If the user asked us to checkout the branch after creating it then we should try to do so. This may fail, especially right now since branch switching from within git-gui is not supported. 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 e79a0ae073..ae1f0248f5 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1674,6 +1674,9 @@ proc do_create_branch_action {w} { set all_heads [lsort $all_heads] populate_branch_menu destroy $w + if {$create_branch_checkout} { + switch_branch $newbranch + } } proc do_create_branch {} { @@ -1681,7 +1684,7 @@ proc do_create_branch {} { global create_branch_checkout create_branch_revtype global create_branch_head create_branch_trackinghead - set create_branch_checkout true + set create_branch_checkout 1 set create_branch_revtype head set create_branch_head $current_branch set create_branch_trackinghead {} @@ -1792,8 +1795,6 @@ proc do_create_branch {} { -font font_ui checkbutton $w.postActions.checkout \ -text {Checkout after creation} \ - -offvalue false \ - -onvalue true \ -variable create_branch_checkout \ -font font_ui pack $w.postActions.checkout -anchor nw From 20a53c029efe15cacc26c0e6a0c980ff4bda6635 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 11:37:58 -0500 Subject: [PATCH 271/548] git-gui: Refactor current_diff -> current_diff_path. We now need to keep track of which side the current diff is for, HEAD<->index or index<->working directory. Consequently we need an additional "current diff" variable to tell us which side the diff is for. Since this is really only necessary in reshow_diff I'm going to declare a new global, rather than try to shove both the path and the side into current_diff. To keep things clear later on, I'm renaming current_diff to current_diff_path. There is no functionality change in this commit. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 82 +++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index ae1f0248f5..0bfd56051d 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -543,33 +543,33 @@ proc prune_selection {} { ## diff proc clear_diff {} { - global ui_diff current_diff ui_index ui_workdir + global ui_diff current_diff_path ui_index ui_workdir $ui_diff conf -state normal $ui_diff delete 0.0 end $ui_diff conf -state disabled - set current_diff {} + set current_diff_path {} $ui_index tag remove in_diff 0.0 end $ui_workdir tag remove in_diff 0.0 end } proc reshow_diff {} { - global current_diff ui_status_value file_states + global current_diff_path ui_status_value file_states - if {$current_diff eq {} - || [catch {set s $file_states($current_diff)}]} { + if {$current_diff_path eq {} + || [catch {set s $file_states($current_diff_path)}]} { clear_diff } else { - show_diff $current_diff + show_diff $current_diff_path } } proc handle_empty_diff {} { - global current_diff file_states file_lists + global current_diff_path file_states file_lists - set path $current_diff + set path $current_diff_path set s $file_states($path) if {[lindex $s 0] ne {_M}} return @@ -598,7 +598,7 @@ files list, to prevent possible confusion. proc show_diff {path {w {}} {lno {}}} { global file_states file_lists global is_3way_diff diff_active repo_config - global ui_diff current_diff ui_status_value + global ui_diff current_diff_path ui_status_value if {$diff_active || ![lock_index read]} return @@ -620,7 +620,7 @@ proc show_diff {path {w {}} {lno {}}} { set m [lindex $s 0] set is_3way_diff 0 set diff_active 1 - set current_diff $path + set current_diff_path $path set ui_status_value "Loading diff of [escape_path $path]..." set cmd [list | git diff-index] @@ -1379,7 +1379,7 @@ proc update_indexinfo {msg pathList after} { proc write_update_indexinfo {fd pathList totalCnt batch msg after} { global update_index_cp ui_status_value - global file_states current_diff + global file_states current_diff_path if {$update_index_cp >= $totalCnt} { close $fd @@ -1451,7 +1451,7 @@ proc update_index {msg pathList after} { proc write_update_index {fd pathList totalCnt batch msg after} { global update_index_cp ui_status_value - global file_states current_diff + global file_states current_diff_path if {$update_index_cp >= $totalCnt} { close $fd @@ -1527,7 +1527,7 @@ proc checkout_index {msg pathList after} { proc write_checkout_index {fd pathList totalCnt batch msg after} { global update_index_cp ui_status_value - global file_states current_diff + global file_states current_diff_path if {$update_index_cp >= $totalCnt} { close $fd @@ -2572,7 +2572,7 @@ proc do_rescan {} { } proc unstage_helper {txt paths} { - global file_states current_diff + global file_states current_diff_path if {![lock_index begin-update]} return @@ -2584,7 +2584,7 @@ proc unstage_helper {txt paths} { M? - D? { lappend pathList $path - if {$path eq $current_diff} { + if {$path eq $current_diff_path} { set after {reshow_diff;} } } @@ -2601,21 +2601,21 @@ proc unstage_helper {txt paths} { } proc do_unstage_selection {} { - global current_diff selected_paths + 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 ne {}} { + } elseif {$current_diff_path ne {}} { unstage_helper \ - "Unstaging [short_path $current_diff] from commit" \ - [list $current_diff] + "Unstaging [short_path $current_diff_path] from commit" \ + [list $current_diff_path] } } proc add_helper {txt paths} { - global file_states current_diff + global file_states current_diff_path if {![lock_index begin-update]} return @@ -2628,7 +2628,7 @@ proc add_helper {txt paths} { ?D - U? { lappend pathList $path - if {$path eq $current_diff} { + if {$path eq $current_diff_path} { set after {reshow_diff;} } } @@ -2645,16 +2645,16 @@ proc add_helper {txt paths} { } proc do_add_selection {} { - global current_diff selected_paths + global current_diff_path selected_paths if {[array size selected_paths] > 0} { add_helper \ {Adding selected files} \ [array names selected_paths] - } elseif {$current_diff ne {}} { + } elseif {$current_diff_path ne {}} { add_helper \ - "Adding [short_path $current_diff]" \ - [list $current_diff] + "Adding [short_path $current_diff_path]" \ + [list $current_diff_path] } } @@ -2673,7 +2673,7 @@ proc do_add_all {} { } proc revert_helper {txt paths} { - global file_states current_diff + global file_states current_diff_path if {![lock_index begin-update]} return @@ -2685,7 +2685,7 @@ proc revert_helper {txt paths} { ?M - ?D { lappend pathList $path - if {$path eq $current_diff} { + if {$path eq $current_diff_path} { set after {reshow_diff;} } } @@ -2724,16 +2724,16 @@ Any unadded changes will be permanently lost by the revert." \ } proc do_revert_selection {} { - global current_diff selected_paths + global current_diff_path selected_paths if {[array size selected_paths] > 0} { revert_helper \ {Reverting selected files} \ [array names selected_paths] - } elseif {$current_diff ne {}} { + } elseif {$current_diff_path ne {}} { revert_helper \ - "Reverting [short_path $current_diff]" \ - [list $current_diff] + "Reverting [short_path $current_diff_path]" \ + [list $current_diff_path] } } @@ -3115,7 +3115,7 @@ proc do_macosx_app {} { } proc toggle_or_diff {w x y} { - global file_states file_lists current_diff ui_index ui_workdir + global file_states file_lists current_diff_path ui_index ui_workdir global last_clicked selected_paths set pos [split [$w index @$x,$y] .] @@ -3133,7 +3133,7 @@ proc toggle_or_diff {w x y} { $ui_workdir tag remove in_sel 0.0 end if {$col == 0} { - if {$current_diff eq $path} { + if {$current_diff_path eq $path} { set after {reshow_diff;} } else { set after {} @@ -3739,17 +3739,17 @@ bind_button3 $ui_comm "tk_popup $ctxm %X %Y" # -- Diff Header # -set current_diff {} +set current_diff_path {} set diff_actions [list] -proc trace_current_diff {varname args} { - global current_diff diff_actions file_states - if {$current_diff eq {}} { +proc trace_current_diff_path {varname args} { + global current_diff_path diff_actions file_states + if {$current_diff_path eq {}} { set s {} set f {} set p {} set o disabled } else { - set p $current_diff + set p $current_diff_path set s [mapdesc [lindex $file_states($p) 0] $p] set f {File:} set p [escape_path $p] @@ -3763,7 +3763,7 @@ proc trace_current_diff {varname args} { uplevel #0 $w $o } } -trace add variable current_diff write trace_current_diff +trace add variable current_diff_path write trace_current_diff_path frame .vpane.lower.diff.header -background orange label .vpane.lower.diff.header.status \ @@ -3795,7 +3795,7 @@ $ctxm add command \ clipboard append \ -format STRING \ -type STRING \ - -- $current_diff + -- $current_diff_path } lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y" @@ -3975,7 +3975,7 @@ set MERGE_HEAD [list] set commit_type {} set empty_tree {} set current_branch {} -set current_diff {} +set current_diff_path {} set selected_commit_type new wm title . "[appname] ([file normalize [file dirname [gitdir]]])" From 82cb8706bb9f4f43c72e0228ad33d91c29448f3f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 11:54:16 -0500 Subject: [PATCH 272/548] git-gui: Remove combined diff showing behavior. The combined diff format can be very confusing, especially to new users who may not even be familiar with a standard two way diff format. So for files which are already staged for commit and which are modifed in the working directory we should show two different diffs, depending on which side the user clicked on. If the user clicks on the "Changes To Be Committed" side then we should show them the PARENT<->index difference. This is the set of changes they will actually commit. If the user clicks on the "Changed But Not Updated" side we should show them the index<->working directory difference. This is the set of changes which will not be committed, as they have not been staged into the index. This is especially useful when merging, as the "Changed But Not Updated" files are the ones that need merge conflict resolution, and the diff here is the conflict hunks and/or any evil merge created by the user. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 0bfd56051d..64c2ae30e7 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -556,13 +556,14 @@ proc clear_diff {} { } proc reshow_diff {} { - global current_diff_path ui_status_value file_states + global ui_status_value file_states + global current_diff_path current_diff_side if {$current_diff_path eq {} || [catch {set s $file_states($current_diff_path)}]} { clear_diff } else { - show_diff $current_diff_path + show_diff $current_diff_path $current_diff_side } } @@ -595,10 +596,11 @@ files list, to prevent possible confusion. display_file $path __ } -proc show_diff {path {w {}} {lno {}}} { +proc show_diff {path w {lno {}}} { global file_states file_lists global is_3way_diff diff_active repo_config - global ui_diff current_diff_path ui_status_value + global ui_diff ui_status_value ui_index ui_workdir + global current_diff_path current_diff_side if {$diff_active || ![lock_index read]} return @@ -621,20 +623,12 @@ proc show_diff {path {w {}} {lno {}}} { set is_3way_diff 0 set diff_active 1 set current_diff_path $path + set current_diff_side $w set ui_status_value "Loading diff of [escape_path $path]..." - set cmd [list | git diff-index] - lappend cmd --no-color - if {$repo_config(gui.diffcontext) > 0} { - lappend cmd "-U$repo_config(gui.diffcontext)" - } - lappend cmd -p - - switch $m { - MM { - lappend cmd -c - } - _O { + # - Git won't give us the diff, there's nothing to compare to! + # + if {$m eq {_O}} { if {[catch { set fd [open $path r] set content [read $fd] @@ -654,9 +648,23 @@ proc show_diff {path {w {}} {lno {}}} { 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} { + lappend cmd diff-files } - lappend cmd [PARENT] + 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 From 3b4db3c1a3b9be66b46d8bd64560ee3f14f1084d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 12:30:51 -0500 Subject: [PATCH 273/548] git-gui: Improve the display of merge conflicts. If a file has a merge conflict we want it to show up in the 'Changed But Not Updated' file list rather than the 'Changes To Be Committed' file list. This way the user can mostly ignore the left side (the HEAD<->index comparsion) while resolving a merge and instead focus on the merge conflicts, which are just shown on the right hand side. This requires detecting the U state in the index side and drawing it as though it were _, then forcing the working directory side to have a U state. We have to delay this until presentation time as we don't want to change our internal state data to be different from what Git is telling us (I tried, the patch for that was ugly and didn't work). When showing a working directory diff and its a merge conflict we don't want to use diff-files as this would wind up showing any automatically merged hunks obtained from MERGE_HEAD in the diff. These are not usually very interesting as they were completed by the system. Instead we just want to see the conflicts. Fortunately the diff porcelain-ish frontend (aka 'git diff') detects the case of an unmerged file and generates a --cc diff against HEAD and MERGE_HEAD. So we now force any working directory diff with an index state of 'U' to go through that difference path. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 64c2ae30e7..2d130faba4 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -654,7 +654,11 @@ proc show_diff {path w {lno {}}} { lappend cmd diff-index lappend cmd --cached } elseif {$w eq $ui_workdir} { - lappend cmd diff-files + if {[string index $m 0] eq {U}} { + lappend cmd diff + } else { + lappend cmd diff-files + } } lappend cmd -p @@ -1293,12 +1297,20 @@ proc display_file {path state} { set new_m [lindex $s 0] set icon_name [lindex $s 1] + set s [string index $new_m 0] + if {$s eq {U}} { + set s _ + } display_file_helper $ui_index $path $icon_name \ - [string index $old_m 0] \ - [string index $new_m 0] + [string index $old_m 0] $s + + if {[string index $new_m 0] eq {U}} { + set s U + } else { + set s [string index $new_m 1] + } display_file_helper $ui_workdir $path $icon_name \ - [string index $old_m 1] \ - [string index $new_m 1] + [string index $old_m 1] $s if {$new_m eq {__}} { unset file_states($path) @@ -1338,13 +1350,20 @@ proc display_all_files {} { set m [lindex $s 0] set icon_name [lindex $s 1] - if {[string index $m 0] ne {_}} { + set s [string index $m 0] + if {$s ne {U} && $s ne {_}} { display_all_files_helper $ui_index $path \ - $icon_name [string index $m 0] + $icon_name $s } - if {[string index $m 1] ne {_}} { + + if {[string index $m 0] eq {U}} { + set s U + } else { + set s [string index $m 1] + } + if {$s ne {_}} { display_all_files_helper $ui_workdir $path \ - $icon_name [string index $m 1] + $icon_name $s } } @@ -1479,7 +1498,13 @@ proc write_update_index {fd pathList totalCnt batch msg after} { ?D {set new D_} _O - AM {set new A_} - U_ - + U? { + if {[file exists $path]} { + set new M_ + } else { + set new D_ + } + } ?M {set new M_} ?? {continue} } @@ -2244,6 +2269,7 @@ set all_icons(U$ui_index) file_merge set all_icons(_$ui_workdir) file_plain set all_icons(M$ui_workdir) file_mod set all_icons(D$ui_workdir) file_question +set all_icons(U$ui_workdir) file_merge set all_icons(O$ui_workdir) file_plain set max_status_desc 0 @@ -2265,6 +2291,7 @@ foreach i { {DO "Staged for removal, still present"} {U_ "Requires merge resolution"} + {UU "Requires merge resolution"} {UM "Requires merge resolution"} {UD "Requires merge resolution"} } { From fec4a78590229aab648e37195a071e3aae02bfe0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 13:12:02 -0500 Subject: [PATCH 274/548] git-gui: Improve diff --cc viewing for unmerged files. Now that we are using 'git diff' to display unmerged working directory files we are getting 'diff --cc' output rather than 'diff --combined' output. Further the markers in the first two columns actually make sense here, we shouldn't attempt to rewrite them to something else. I've added 'diff --cc *' to the skip list in our diff viewer, as that particular line is not very interesting to display. I've completely refactored how we perform detection of the state of a line during diff parsing; we now report an error message if we don't understand the particular state of any given line. This way we know if we aren't tagging something we maybe should have tagged in the UI. I've also added special display of the standard conflict hunk markers (<<<<<<<, =======, >>>>>>>). These are formatted without a patch op as the patch op is always '+' or '++' (meaning the line has been added relative to the committed state) and are displayed in orange bold text, sort of like the @@ or @@@ marker line is at the start of each hunk. In a 3 way merge diff hunks which came from our HEAD are shown with a azure2 background, and hunks which came from the incoming MERGE_HEAD are displayed with a 'light goldenrod yellow' background. This makes the two different hunks clearly visible within the file. Hunks which are ++ or -- (added or deleted relative to both parents) are shown without any background at all. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 88 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 24 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 2d130faba4..5463bb98ae 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -693,6 +693,7 @@ proc read_diff {fd} { # -- Cleanup uninteresting diff header lines. # if {[string match {diff --git *} $line]} continue + if {[string match {diff --cc *} $line]} continue if {[string match {diff --combined *} $line]} continue if {[string match {--- *} $line]} continue if {[string match {+++ *} $line]} continue @@ -704,27 +705,49 @@ proc read_diff {fd} { # if {[string match {@@@ *} $line]} {set is_3way_diff 1} - # -- Reformat a 3 way diff, 'cause its too weird. - # - if {$is_3way_diff} { + if {[string match {index *} $line]} { + set tags {} + } elseif {$is_3way_diff} { set op [string range $line 0 1] switch -- $op { + { } {set tags {}} {@@} {set tags d_@} - {++} {set tags d_+ ; set op { +}} - {--} {set tags d_- ; set op { -}} - { +} {set tags d_++; set op {++}} - { -} {set tags d_--; set op {--}} - {+ } {set tags d_-+; set op {-+}} - {- } {set tags d_+-; set op {+-}} - default {set tags {}} + { +} {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 {} + } } - set line [string replace $line 0 1 $op] } else { - switch -- [string index $line 0] { - @ {set tags d_@} - + {set tags d_+} - - {set tags d_-} - default {set tags {}} + 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 @@ -3856,16 +3879,33 @@ pack .vpane.lower.diff.header -side top -fill x pack .vpane.lower.diff.body -side bottom -fill both -expand 1 $ui_diff tag conf d_@ -font font_diffbold -$ui_diff tag conf d_+ -foreground blue -$ui_diff tag conf d_- -foreground red -$ui_diff tag conf d_++ -foreground {#00a000} -$ui_diff tag conf d_-- -foreground {#a000a0} -$ui_diff tag conf d_+- \ - -foreground red \ - -background {light goldenrod yellow} -$ui_diff tag conf d_-+ \ +$ui_diff tag conf d_+ -foreground blue +$ui_diff tag conf d_- -foreground red + +$ui_diff tag conf d_++ -foreground blue +$ui_diff tag conf d_-- -foreground red +$ui_diff tag conf d_+s \ -foreground blue \ -background azure2 +$ui_diff tag conf d_-s \ + -foreground red \ + -background azure2 +$ui_diff tag conf d_s+ \ + -foreground blue \ + -background {light goldenrod yellow} +$ui_diff tag conf d_s- \ + -foreground red \ + -background {light goldenrod yellow} + +$ui_diff tag conf d<<<<<<< \ + -foreground orange \ + -font font_diffbold +$ui_diff tag conf d======= \ + -foreground orange \ + -font font_diffbold +$ui_diff tag conf d>>>>>>> \ + -foreground orange \ + -font font_diffbold # -- Diff Body Context Menu # From 079d0d5057dd66916c8d23802d48b19235fedf09 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 13:18:11 -0500 Subject: [PATCH 275/548] git-gui: Fix bug in unmerged file display. We were not correctly setting the old state of an index display to _ if the index was previously unmerged. This caused us to try and update a U->M when resolving a merge conflict but we were unable to do so as the icon did not exist in the index viewer. Tk did not like being asked to modify an icon which was undefined. Now we always transform both the old and the new states for both sides (index and working directory) prior to updating the UI. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 5463bb98ae..28c71c0b2d 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1320,20 +1320,27 @@ proc display_file {path state} { set new_m [lindex $s 0] set icon_name [lindex $s 1] - set s [string index $new_m 0] - if {$s eq {U}} { - set s _ + set o [string index $old_m 0] + set n [string index $new_m 0] + if {$o eq {U}} { + set o _ } - display_file_helper $ui_index $path $icon_name \ - [string index $old_m 0] $s + if {$n eq {U}} { + set n _ + } + display_file_helper $ui_index $path $icon_name $o $n - if {[string index $new_m 0] eq {U}} { - set s U + if {[string index $old_m 0] eq {U}} { + set o U } else { - set s [string index $new_m 1] + set o [string index $old_m 0] } - display_file_helper $ui_workdir $path $icon_name \ - [string index $old_m 1] $s + if {[string index $new_m 0] eq {U}} { + set n U + } else { + set n [string index $new_m 1] + } + display_file_helper $ui_workdir $path $icon_name $o $n if {$new_m eq {__}} { unset file_states($path) From 6bdc929984f84318783ef320115f46151fa4f0d6 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 13:22:26 -0500 Subject: [PATCH 276/548] git-gui: Clear diff from viewer if the side changed. If the user switches the currently shown file from one side of the UI to the other then how its diff is presented would be different. And leaving the old diff up is downright confusing. Since the diff is probably not interesting to the user after the switch we should just clear the diff viewer. This saves the user time, as they won't need to wait for us to reload the diff. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 28c71c0b2d..c4d77fafe7 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -556,14 +556,17 @@ proc clear_diff {} { } proc reshow_diff {} { - global ui_status_value file_states + global ui_status_value file_states file_lists global current_diff_path current_diff_side - if {$current_diff_path eq {} - || [catch {set s $file_states($current_diff_path)}]} { + set p $current_diff_path + if {$p eq {} + || $current_diff_side eq {} + || [catch {set s $file_states($p)}] + || [lsearch -sorted $file_lists($current_diff_side) $p] == -1} { clear_diff } else { - show_diff $current_diff_path $current_diff_side + show_diff $p $current_diff_side } } From a4b1786b954917294483feb176e6ca473e01b615 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 13:25:06 -0500 Subject: [PATCH 277/548] git-gui: Correct disappearing unstaged files. A prior commit tried to use the old index state for the old working directory state during a UI refresh of a file. This caused files which were being unstaged (and thus becoming unmodified) to drop out of the working directory side of the display, at least until the user performed a rescan to force the UI to redisplay everything. 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 c4d77fafe7..aa8f0ba067 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1336,7 +1336,7 @@ proc display_file {path state} { if {[string index $old_m 0] eq {U}} { set o U } else { - set o [string index $old_m 0] + set o [string index $old_m 1] } if {[string index $new_m 0] eq {U}} { set n U From 68c30b4af1b1d6f95ae6724364641aa787247f0f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 13:27:43 -0500 Subject: [PATCH 278/548] git-gui: Add Refresh to diff viewer context menu. Sometimes you want to just force the diff to redisplay itself without rescanning every file in the filesystem (as that can be very costly on large projects and slow operating systems). Now you can force a diff-only refresh from the context menu. Previously you could also do this by reclicking on the file name in the UI, but it may not be obvious to all users, having a context menu option makes it more clear. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index aa8f0ba067..f618a60d7b 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3921,6 +3921,10 @@ $ui_diff tag conf d>>>>>>> \ # set ctxm .vpane.lower.diff.body.ctxm menu $ctxm -tearoff 0 +$ctxm add command \ + -label {Refresh} \ + -font font_ui \ + -command reshow_diff $ctxm add command \ -label {Copy} \ -font font_ui \ From 14efcc748597f8b00d362df26adf5c4b4b7777f7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 13:34:00 -0500 Subject: [PATCH 279/548] git-gui: Correct unmerged file detection at commit time. Its impossible to commit an index which has unmerged stages. Unfortunately a bug in git-gui allowed the user to try to do exactly that, as we broke out of our file scanning loop as soon as we found a valid AMD index state. That's wrong, as the files are coming back from our array in pseudo-random order; an unmerged file may get returned only after all merged files. I also noticed the grammer around here in our dialog boxes still used the term 'include', so this has been updated to reflect current usage. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index f618a60d7b..32c33672d6 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -896,12 +896,12 @@ The rescan will be automatically started now. _? {continue} A? - D? - - M? {set files_ready 1; break} + 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 include the file before committing. +You must resolve them and add the file before committing. " unlock_index return @@ -915,9 +915,9 @@ File [short_path $path] cannot be committed by this program. } } if {!$files_ready} { - error_popup {No included files to commit. + error_popup {No changes to commit. -You must include at least 1 file before you can commit. +You must add at least 1 file before you can commit. } unlock_index return From f8a1518d06378c34fa8a0d8530e5041527004af5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 13:37:53 -0500 Subject: [PATCH 280/548] git-gui: Pad new branch name input box. The new branch name input box was showing up too close to the labelframe border, it was basically right on top of it on Windows. This didn't look right when compared to the Starting Revision's expression input field, as that had a 5 pixel padding. So I've put the new name input box into its own frame and padded that frame by 5 pixels, making the UI more consistent. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 32c33672d6..f12be315b1 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1683,7 +1683,7 @@ proc do_create_branch_action {w} { global create_branch_checkout create_branch_revtype global create_branch_head create_branch_trackinghead - set newbranch [string trim [$w.name.t get 0.0 end]] + set newbranch [string trim [$w.desc.name.t get 0.0 end]] if {![catch {exec git show-ref --verify -- "refs/heads/$newbranch"}]} { tk_messageBox \ -icon error \ @@ -1691,7 +1691,7 @@ proc do_create_branch_action {w} { -title [wm title $w] \ -parent $w \ -message "Branch '$newbranch' already exists." - focus $w.name.t + focus $w.desc.name.t return } if {[catch {exec git check-ref-format "heads/$newbranch"}]} { @@ -1701,7 +1701,7 @@ proc do_create_branch_action {w} { -title [wm title $w] \ -parent $w \ -message "We do not like '$newbranch' as a branch name." - focus $w.name.t + focus $w.desc.name.t return } @@ -1775,20 +1775,21 @@ proc do_create_branch {} { pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.name \ + labelframe $w.desc \ -text {Branch Description} \ -font font_ui - label $w.name.l -text {Name:} -font font_ui - text $w.name.t \ + frame $w.desc.name + label $w.desc.name.l -text {Name:} -font font_ui + text $w.desc.name.t \ -borderwidth 1 \ -relief sunken \ -height 1 \ -width 40 \ -font font_ui - bind $w.name.t "focus $w.postActions.checkout;break" - bind $w.name.t "focus $w.from.exp.t;break" - bind $w.name.t "do_create_branch_action $w;break" - bind $w.name.t { + bind $w.desc.name.t "focus $w.postActions.checkout;break" + bind $w.desc.name.t "focus $w.from.exp.t;break" + bind $w.desc.name.t "do_create_branch_action $w;break" + bind $w.desc.name.t { if {{%K} ne {BackSpace} && {%K} ne {Tab} && {%K} ne {Escape} @@ -1797,9 +1798,10 @@ proc do_create_branch {} { if {[string first %A {~^:?*[}] >= 0} break } } - pack $w.name.l -side left -padx 5 - pack $w.name.t -side left -fill x -expand 1 - pack $w.name -anchor nw -fill x -pady 5 -padx 5 + pack $w.desc.name.l -side left -padx 5 + pack $w.desc.name.t -side left -fill x -expand 1 + pack $w.desc.name -padx 5 -fill x -expand 1 + pack $w.desc -anchor nw -fill x -pady 5 -padx 5 set all_trackings [list] foreach b [array names tracking_branches] { @@ -1846,7 +1848,7 @@ proc do_create_branch {} { -height 1 \ -width 50 \ -font font_ui - bind $w.from.exp.t "focus $w.name.t;break" + bind $w.from.exp.t "focus $w.desc.name.t;break" bind $w.from.exp.t "focus $w.postActions.checkout;break" bind $w.from.exp.t "do_create_branch_action $w;break" pack $w.from.exp.r -side left @@ -1866,7 +1868,7 @@ proc do_create_branch {} { pack $w.postActions.checkout -anchor nw pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 - bind $w "grab $w; focus $w.name.t" + bind $w "grab $w; focus $w.desc.name.t" bind $w "destroy $w" bind $w "do_create_branch_action $w;break" wm title $w "[appname] ([reponame]): Create Branch" From 66cc17d1d3bead2ebf6fbe41ea31e7ec111d891b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 13:56:38 -0500 Subject: [PATCH 281/548] git-gui: Use a grid layout for branch dialog. Using a stack of frames in the Starting Revision section of the new branch dialog turned out to be a mess. The varying lengths of each label caused the optionMenu widgets to be spread around the screen at unaligned locations, making the interface very kludgy looking. Now we layout the major sections of the branch dialog using grid rather than pack, allowing these widgets to line up vertically in a nice neat column. All extra space is given to column 1, which is where we have located the text fields. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 62 +++++++++++++++++++++++------------------------------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index f12be315b1..2e2d775464 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1683,7 +1683,7 @@ proc do_create_branch_action {w} { global create_branch_checkout create_branch_revtype global create_branch_head create_branch_trackinghead - set newbranch [string trim [$w.desc.name.t get 0.0 end]] + set newbranch [string trim [$w.desc.name_t get 0.0 end]] if {![catch {exec git show-ref --verify -- "refs/heads/$newbranch"}]} { tk_messageBox \ -icon error \ @@ -1691,7 +1691,7 @@ proc do_create_branch_action {w} { -title [wm title $w] \ -parent $w \ -message "Branch '$newbranch' already exists." - focus $w.desc.name.t + focus $w.desc.name_t return } if {[catch {exec git check-ref-format "heads/$newbranch"}]} { @@ -1701,7 +1701,7 @@ proc do_create_branch_action {w} { -title [wm title $w] \ -parent $w \ -message "We do not like '$newbranch' as a branch name." - focus $w.desc.name.t + focus $w.desc.name_t return } @@ -1709,7 +1709,7 @@ proc do_create_branch_action {w} { switch -- $create_branch_revtype { head {set rev $create_branch_head} tracking {set rev $create_branch_trackinghead} - expression {set rev [string trim [$w.from.exp.t get 0.0 end]]} + expression {set rev [string trim [$w.from.exp_t get 0.0 end]]} } if {[catch {set cmt [exec git rev-parse --verify "${rev}^0"]}]} { tk_messageBox \ @@ -1778,18 +1778,18 @@ proc do_create_branch {} { labelframe $w.desc \ -text {Branch Description} \ -font font_ui - frame $w.desc.name - label $w.desc.name.l -text {Name:} -font font_ui - text $w.desc.name.t \ + label $w.desc.name_l -text {Name:} -font font_ui + text $w.desc.name_t \ -borderwidth 1 \ -relief sunken \ -height 1 \ -width 40 \ -font font_ui - bind $w.desc.name.t "focus $w.postActions.checkout;break" - bind $w.desc.name.t "focus $w.from.exp.t;break" - bind $w.desc.name.t "do_create_branch_action $w;break" - bind $w.desc.name.t { + grid $w.desc.name_l $w.desc.name_t -stick we -padx {0 5} + bind $w.desc.name_t "focus $w.postActions.checkout;break" + bind $w.desc.name_t "focus $w.from.exp_t;break" + bind $w.desc.name_t "do_create_branch_action $w;break" + bind $w.desc.name_t { if {{%K} ne {BackSpace} && {%K} ne {Tab} && {%K} ne {Escape} @@ -1798,9 +1798,7 @@ proc do_create_branch {} { if {[string first %A {~^:?*[}] >= 0} break } } - pack $w.desc.name.l -side left -padx 5 - pack $w.desc.name.t -side left -fill x -expand 1 - pack $w.desc.name -padx 5 -fill x -expand 1 + grid columnconfigure $w.desc 1 -weight 1 pack $w.desc -anchor nw -fill x -pady 5 -padx 5 set all_trackings [list] @@ -1816,46 +1814,38 @@ proc do_create_branch {} { labelframe $w.from \ -text {Starting Revision} \ -font font_ui - frame $w.from.head - radiobutton $w.from.head.r \ + radiobutton $w.from.head_r \ -text {Local Branch:} \ -value head \ -variable create_branch_revtype \ -font font_ui - eval tk_optionMenu $w.from.head.m create_branch_head $all_heads - pack $w.from.head.r -side left - pack $w.from.head.m -side left - frame $w.from.tracking - radiobutton $w.from.tracking.r \ + eval tk_optionMenu $w.from.head_m create_branch_head $all_heads + grid $w.from.head_r $w.from.head_m -sticky w + radiobutton $w.from.tracking_r \ -text {Tracking Branch:} \ -value tracking \ -variable create_branch_revtype \ -font font_ui - eval tk_optionMenu $w.from.tracking.m \ + eval tk_optionMenu $w.from.tracking_m \ create_branch_trackinghead \ $all_trackings - pack $w.from.tracking.r -side left - pack $w.from.tracking.m -side left - frame $w.from.exp - radiobutton $w.from.exp.r \ + grid $w.from.tracking_r $w.from.tracking_m -sticky w + radiobutton $w.from.exp_r \ -text {Revision Expression:} \ -value expression \ -variable create_branch_revtype \ -font font_ui - text $w.from.exp.t \ + text $w.from.exp_t \ -borderwidth 1 \ -relief sunken \ -height 1 \ -width 50 \ -font font_ui - bind $w.from.exp.t "focus $w.desc.name.t;break" - bind $w.from.exp.t "focus $w.postActions.checkout;break" - bind $w.from.exp.t "do_create_branch_action $w;break" - pack $w.from.exp.r -side left - pack $w.from.exp.t -side left -fill x -expand 1 - pack $w.from.head -padx 5 -fill x -expand 1 - pack $w.from.tracking -padx 5 -fill x -expand 1 - pack $w.from.exp -padx 5 -fill x -expand 1 + grid $w.from.exp_r $w.from.exp_t -stick we -padx {0 5} + bind $w.from.exp_t "focus $w.desc.name_t;break" + bind $w.from.exp_t "focus $w.postActions.checkout;break" + bind $w.from.exp_t "do_create_branch_action $w;break" + grid columnconfigure $w.from 1 -weight 1 pack $w.from -anchor nw -fill x -pady 5 -padx 5 labelframe $w.postActions \ @@ -1868,7 +1858,7 @@ proc do_create_branch {} { pack $w.postActions.checkout -anchor nw pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 - bind $w "grab $w; focus $w.desc.name.t" + bind $w "grab $w; focus $w.desc.name_t" bind $w "destroy $w" bind $w "do_create_branch_action $w;break" wm title $w "[appname] ([reponame]): Create Branch" From 15e1374927b1dc42a575235c63852d444cf9c1ab Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 14:14:54 -0500 Subject: [PATCH 282/548] git-gui: Improve the merge check interface for branch deletion. Just like how we split out the local and remote branches into two different pick lists for branch creation, we should do the same thing for branch deletion. This means that there are really 3 modes of operation here: * delete only if merged into designated local branch; * delete only if merged into designated tracking (remote) branch; * delete no matter what So we now use radio buttons to select between these operations. We still default to checking for merge into the current branch, as that is probably the most commonly used behavior. It also is what core Git's command line tools do. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 73 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 2e2d775464..f80c3b9639 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1867,16 +1867,34 @@ proc do_create_branch {} { proc do_delete_branch_action {w} { global all_heads - global delete_branch_checkhead delete_branch_head + 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 [exec 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 [exec git rev-parse --verify $b]}]} continue - if {$delete_branch_checkhead} { - if {$b eq $delete_branch_head} continue - if {[catch {set m [exec git merge-base $o $delete_branch_head]}]} continue + if {$check_cmt ne {}} { + if {$b eq $check_rev} continue + if {[catch {set m [exec git merge-base $o $check_cmt]}]} continue if {$o ne $m} { lappend not_merged $b continue @@ -1885,7 +1903,7 @@ proc do_delete_branch_action {w} { lappend to_delete [list $b $o] } if {$not_merged ne {}} { - set msg "The following branches are not completely merged into $delete_branch_head: + set msg "The following branches are not completely merged into $check_rev: - [join $not_merged "\n - "]" tk_messageBox \ @@ -1896,7 +1914,7 @@ proc do_delete_branch_action {w} { -message $msg } if {$to_delete eq {}} return - if {!$delete_branch_checkhead} { + if {$delete_branch_checktype eq {always}} { set msg {Recovering deleted branches is difficult. Delete the selected branches?} @@ -1940,10 +1958,11 @@ Delete the selected branches?} proc do_delete_branch {} { global all_heads tracking_branches current_branch - global delete_branch_checkhead delete_branch_head + global delete_branch_checktype delete_branch_head delete_branch_trackinghead - set delete_branch_checkhead 1 + set delete_branch_checktype head set delete_branch_head $current_branch + set delete_branch_trackinghead {} set w .branch_editor toplevel $w @@ -1985,21 +2004,37 @@ proc do_delete_branch {} { regsub ^refs/(heads|remotes)/ $b {} b lappend all_trackings $b } + set all_trackings [lsort -unique $all_trackings] + if {$all_trackings ne {} && $delete_branch_trackinghead eq {}} { + set delete_branch_trackinghead [lindex $all_trackings 0] + } labelframe $w.validate \ - -text {Only Delete If} \ + -text {Delete Only If} \ -font font_ui - frame $w.validate.head - checkbutton $w.validate.head.r \ - -text {Already Merged Into:} \ - -variable delete_branch_checkhead \ + radiobutton $w.validate.head_r \ + -text {Merged Into Local Branch:} \ + -value head \ + -variable delete_branch_checktype \ -font font_ui - eval tk_optionMenu $w.validate.head.m delete_branch_head \ - $all_heads \ - [lsort -unique $all_trackings] - pack $w.validate.head.r -side left - pack $w.validate.head.m -side left - pack $w.validate.head -padx 5 -fill x -expand 1 + eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads + grid $w.validate.head_r $w.validate.head_m -sticky w + radiobutton $w.validate.tracking_r \ + -text {Merged Into Tracking Branch:} \ + -value tracking \ + -variable delete_branch_checktype \ + -font font_ui + 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 \ + -font font_ui + 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 bind $w "grab $w; focus $w" From 884fd059f8c86dc815ea7fd769f7190e3f248536 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 14:16:40 -0500 Subject: [PATCH 283/548] git-gui: Change rude error popup to info popup. If the user has not added any files yet they cannot commit. But telling them this isn't an error, its really just an informational note meant to push the user in the correct direction. 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 f80c3b9639..d26868beca 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -915,7 +915,7 @@ File [short_path $path] cannot be committed by this program. } } if {!$files_ready} { - error_popup {No changes to commit. + info_popup {No changes to commit. You must add at least 1 file before you can commit. } From 37d2a1c9fa2f94093cb6bd5e6b2aa6a38c95593e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 14:23:51 -0500 Subject: [PATCH 284/548] git-gui: Correctly ignore '* Unmerged path' during diff. If a path is really unmerged, such as because it has been deleted and also modifed, we cannot obtain a diff for it. Instead Git is sending back '* Unmerged path ' for file . We should display this line as-is as our tag selecting switches don't recognize it. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index d26868beca..8e664f54e8 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -708,7 +708,8 @@ proc read_diff {fd} { # if {[string match {@@@ *} $line]} {set is_3way_diff 1} - if {[string match {index *} $line]} { + if {[string match {index *} $line] + || [regexp {^\* Unmerged path } $line]} { set tags {} } elseif {$is_3way_diff} { set op [string range $line 0 1] From ca52156618ce374711e37c8d633f0ee30cdd58c3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 14:49:45 -0500 Subject: [PATCH 285/548] git-gui: Make diff viewer colors match gitk's defaults. Because users who use git-gui are likely to also be using gitk, we should at least match gitk's default colors and formatting within the diff viewer. Unfortunately this meant that I needed to change the background colors of the hunks in a 'diff --cc' output, as the green used for 'added line' was completely unreadable on the old color. We now use ivory1 to show hunks which came from HEAD/parent^1, which are the portions that the current branch has contributed, and are probably the user's own changes. We use a very light blue for the portions which came from FETCH_HEAD, as this makes the changes made by the other branch stand out more in the diff. I've also modified the hunk header lines to be blue, as that is how gitk is showing them. Apparently I forgot to raise the sel tag above everything else in the diff viewer, which meant that selections in the diff viewer were not visible if they were made on a 'diff --cc' hunk which had a background. Its now the higest priority tag, ensuring the selection is always visible and readable. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 8e664f54e8..39daa745de 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3916,24 +3916,24 @@ pack $ui_diff -side left -fill both -expand 1 pack .vpane.lower.diff.header -side top -fill x pack .vpane.lower.diff.body -side bottom -fill both -expand 1 -$ui_diff tag conf d_@ -font font_diffbold -$ui_diff tag conf d_+ -foreground blue +$ui_diff tag conf d_@ -foreground blue -font font_diffbold +$ui_diff tag conf d_+ -foreground {#00a000} $ui_diff tag conf d_- -foreground red -$ui_diff tag conf d_++ -foreground blue +$ui_diff tag conf d_++ -foreground {#00a000} $ui_diff tag conf d_-- -foreground red $ui_diff tag conf d_+s \ - -foreground blue \ - -background azure2 + -foreground {#00a000} \ + -background {#e2effa} $ui_diff tag conf d_-s \ -foreground red \ - -background azure2 + -background {#e2effa} $ui_diff tag conf d_s+ \ - -foreground blue \ - -background {light goldenrod yellow} + -foreground {#00a000} \ + -background ivory1 $ui_diff tag conf d_s- \ -foreground red \ - -background {light goldenrod yellow} + -background ivory1 $ui_diff tag conf d<<<<<<< \ -foreground orange \ @@ -3945,6 +3945,8 @@ $ui_diff tag conf d>>>>>>> \ -foreground orange \ -font font_diffbold +$ui_diff tag raise sel + # -- Diff Body Context Menu # set ctxm .vpane.lower.diff.body.ctxm From 3c2369773929e299002d5e4e7737ea769ddc3bf8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 14:58:01 -0500 Subject: [PATCH 286/548] git-gui: Never line wrap in file lists. Some of my file paths in some of my repositories are very long, this is rather typical in Java projects where the path name contains a deep package structure and then the file name itself is rather long and (hopefully) descriptive. Seeing these paths line wrap in the file lists looks absolutely horrible. The entire rendering is almost unreadable. Now we draw both horizontal and vertical scrollbars for both file lists, and we never line wrap within the list text itself. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 39daa745de..a0c87e5ab4 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3654,13 +3654,17 @@ label .vpane.files.index.title -text {Changes To Be Committed} \ -font font_ui text $ui_index -background white -borderwidth 0 \ -width 40 -height 10 \ + -wrap none \ -font font_ui \ -cursor $cursor_ptr \ - -yscrollcommand {.vpane.files.index.sb set} \ + -xscrollcommand {.vpane.files.index.sx set} \ + -yscrollcommand {.vpane.files.index.sy set} \ -state disabled -scrollbar .vpane.files.index.sb -command [list $ui_index yview] +scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview] +scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview] pack .vpane.files.index.title -side top -fill x -pack .vpane.files.index.sb -side right -fill y +pack .vpane.files.index.sx -side bottom -fill x +pack .vpane.files.index.sy -side right -fill y pack $ui_index -side left -fill both -expand 1 .vpane.files add .vpane.files.index -sticky nsew @@ -3672,13 +3676,17 @@ label .vpane.files.workdir.title -text {Changed But Not Updated} \ -font font_ui text $ui_workdir -background white -borderwidth 0 \ -width 40 -height 10 \ + -wrap none \ -font font_ui \ -cursor $cursor_ptr \ - -yscrollcommand {.vpane.files.workdir.sb set} \ + -xscrollcommand {.vpane.files.workdir.sx set} \ + -yscrollcommand {.vpane.files.workdir.sy set} \ -state disabled -scrollbar .vpane.files.workdir.sb -command [list $ui_workdir yview] +scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview] +scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview] pack .vpane.files.workdir.title -side top -fill x -pack .vpane.files.workdir.sb -side right -fill y +pack .vpane.files.workdir.sx -side bottom -fill x +pack .vpane.files.workdir.sy -side right -fill y pack $ui_workdir -side left -fill both -expand 1 .vpane.files add .vpane.files.workdir -sticky nsew From 19e283f5c25b64a55fca099342f9bebddef4e17e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 15:38:09 -0500 Subject: [PATCH 287/548] git-gui: Don't offer tracking branches if none exist. I refactored the common code related to tracking branch listing into a new procedure all_tracking_branches. This saves a few lines and should make the create and delete dialogs easier to maintain. We now don't offer a radio button to create from a tracking branch or merge-check a tracking branch if there are no tracking branches known to git-gui. This prevents us from creating an empty option list and letting the user try to shoot themselves in the foot by asking us to work against an empty initial revision. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 77 +++++++++++++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index a0c87e5ab4..c4ab824b9d 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1679,6 +1679,17 @@ proc populate_branch_menu {} { } } +proc all_tracking_branches {} { + global tracking_branches + + set all_trackings [list] + foreach b [array names tracking_branches] { + regsub ^refs/(heads|remotes)/ $b {} b + lappend all_trackings $b + } + return [lsort -unique $all_trackings] +} + proc do_create_branch_action {w} { global all_heads null_sha1 global create_branch_checkout create_branch_revtype @@ -1747,7 +1758,7 @@ proc do_create_branch_action {w} { } proc do_create_branch {} { - global all_heads current_branch tracking_branches + global all_heads current_branch global create_branch_checkout create_branch_revtype global create_branch_head create_branch_trackinghead @@ -1802,16 +1813,6 @@ proc do_create_branch {} { grid columnconfigure $w.desc 1 -weight 1 pack $w.desc -anchor nw -fill x -pady 5 -padx 5 - set all_trackings [list] - foreach b [array names tracking_branches] { - regsub ^refs/(heads|remotes)/ $b {} b - lappend all_trackings $b - } - set all_trackings [lsort -unique $all_trackings] - if {$all_trackings ne {}} { - set create_branch_trackinghead [lindex $all_trackings 0] - } - labelframe $w.from \ -text {Starting Revision} \ -font font_ui @@ -1822,15 +1823,19 @@ proc do_create_branch {} { -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 - radiobutton $w.from.tracking_r \ - -text {Tracking Branch:} \ - -value tracking \ - -variable create_branch_revtype \ - -font font_ui - 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_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 \ + -font font_ui + eval tk_optionMenu $w.from.tracking_m \ + create_branch_trackinghead \ + $all_trackings + grid $w.from.tracking_r $w.from.tracking_m -sticky w + } radiobutton $w.from.exp_r \ -text {Revision Expression:} \ -value expression \ @@ -2000,16 +2005,6 @@ proc do_delete_branch {} { pack $w.list.l -fill both -pady 5 -padx 5 pack $w.list -fill both -pady 5 -padx 5 - set all_trackings [list] - foreach b [array names tracking_branches] { - regsub ^refs/(heads|remotes)/ $b {} b - lappend all_trackings $b - } - set all_trackings [lsort -unique $all_trackings] - if {$all_trackings ne {} && $delete_branch_trackinghead eq {}} { - set delete_branch_trackinghead [lindex $all_trackings 0] - } - labelframe $w.validate \ -text {Delete Only If} \ -font font_ui @@ -2020,15 +2015,19 @@ proc do_delete_branch {} { -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 - radiobutton $w.validate.tracking_r \ - -text {Merged Into Tracking Branch:} \ - -value tracking \ - -variable delete_branch_checktype \ - -font font_ui - eval tk_optionMenu $w.validate.tracking_m \ - delete_branch_trackinghead \ - $all_trackings - grid $w.validate.tracking_r $w.validate.tracking_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 \ + -font font_ui + 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 \ From e754d6efe7810f5f2c6cbd48dca21f1bc84a6a5e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 15:40:55 -0500 Subject: [PATCH 288/548] git-gui: Give a better error message on an empty branch name. New branches must have a name. An empty one is not a valid ref, but the generic message "We do not like '' as a branch name." is just too vague or difficult to read. So detect the missing name early and tell the user it must be entered. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index c4ab824b9d..0f98d2ccea 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1696,6 +1696,16 @@ proc do_create_branch_action {w} { global create_branch_head create_branch_trackinghead set newbranch [string trim [$w.desc.name_t get 0.0 end]] + if {$newbranch eq {}} { + 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 {exec git show-ref --verify -- "refs/heads/$newbranch"}]} { tk_messageBox \ -icon error \ From c845692d7551cbcbfd8c6dda2aa2e3b135838e39 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 16:28:59 -0500 Subject: [PATCH 289/548] git-gui: Allow user to specify a branch name pattern. Typically I'm creating all new branches with the same prefix, e.g. 'sp/'. So its handy to be able to setup a repository (or global) level config option for git gui which contains this initial prefix. Once set then git-gui will load it into the new branch name field whenever a new branch is being created. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 0f98d2ccea..1a7c4d6b15 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1691,12 +1691,13 @@ proc all_tracking_branches {} { } proc do_create_branch_action {w} { - global all_heads null_sha1 + global all_heads null_sha1 repo_config global create_branch_checkout create_branch_revtype global create_branch_head create_branch_trackinghead set newbranch [string trim [$w.desc.name_t get 0.0 end]] - if {$newbranch eq {}} { + if {$newbranch eq {} + || $newbranch eq $repo_config(gui.newbranchtemplate)} { tk_messageBox \ -icon error \ -type ok \ @@ -1768,7 +1769,7 @@ proc do_create_branch_action {w} { } proc do_create_branch {} { - global all_heads current_branch + global all_heads current_branch repo_config global create_branch_checkout create_branch_revtype global create_branch_head create_branch_trackinghead @@ -1807,6 +1808,7 @@ proc do_create_branch {} { -height 1 \ -width 40 \ -font font_ui + $w.desc.name_t insert 0.0 $repo_config(gui.newbranchtemplate) grid $w.desc.name_l $w.desc.name_t -stick we -padx {0 5} bind $w.desc.name_t "focus $w.postActions.checkout;break" bind $w.desc.name_t "focus $w.from.exp_t;break" @@ -2986,7 +2988,10 @@ proc do_options {} { pack $w.buttons.restore -side left button $w.buttons.save -text Save \ -font font_ui \ - -command [list do_save_config $w] + -command " + catch {eval \[bind \[focus -displayof $w\] \]} + do_save_config $w + " pack $w.buttons.save -side right button $w.buttons.cancel -text {Cancel} \ -font font_ui \ @@ -3007,6 +3012,7 @@ proc do_options {} { {b pullsummary {Show Pull Summary}} {b trustmtime {Trust File Modification Timestamps}} {i diffcontext {Number of Diff Context Lines}} + {t newbranchtemplate {New Branch Name Template}} } { set type [lindex $option 0] set name [lindex $option 1] @@ -3030,7 +3036,29 @@ proc do_options {} { -from 1 -to 99 -increment 1 \ -width 3 \ -font font_ui - pack $w.$f.$name.v -side right -anchor e + pack $w.$f.$name.v -side right -anchor e -padx 5 + pack $w.$f.$name -side top -anchor w -fill x + } + t { + frame $w.$f.$name + label $w.$f.$name.l -text "$text:" -font font_ui + text $w.$f.$name.v \ + -borderwidth 1 \ + -relief sunken \ + -height 1 \ + -width 20 \ + -font font_ui + $w.$f.$name.v insert 0.0 [set ${f}_config_new(gui.$name)] + bind $w.$f.$name.v break + bind $w.$f.$name.v break + bind $w.$f.$name.v " + set ${f}_config_new(gui.$name) \ + \[string trim \[$w.$f.$name.v get 0.0 end\]\] + " + pack $w.$f.$name.l -side left -anchor w + pack $w.$f.$name.v -side left -anchor w \ + -fill x -expand 1 \ + -padx 5 pack $w.$f.$name -side top -anchor w -fill x } } @@ -3364,6 +3392,7 @@ proc apply_config {} { set default_config(gui.trustmtime) false set default_config(gui.pullsummary) true set default_config(gui.diffcontext) 5 +set default_config(gui.newbranchtemplate) {} set default_config(gui.fontui) [font configure font_ui] set default_config(gui.fontdiff) [font configure font_diff] set font_descs { From f250091b77949766724ffb67beb7a6adadc6b5b4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 16:37:05 -0500 Subject: [PATCH 290/548] git-gui: Improve keyboard traversal in dialogs. When we are in a dialog such as the new branch dialog or our options dialog we should permit the user to traverse around through the available widgets with their Tab/Shift-Tab key combinations. So in any single line text field where we don't want tab characters to actually be inserted into the value rebind Tab and Shift-Tab to honor what the tk_focusPrev and tk_focusNext scripts recommend. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 1a7c4d6b15..d59e720408 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1810,8 +1810,8 @@ proc do_create_branch {} { -font font_ui $w.desc.name_t insert 0.0 $repo_config(gui.newbranchtemplate) grid $w.desc.name_l $w.desc.name_t -stick we -padx {0 5} - bind $w.desc.name_t "focus $w.postActions.checkout;break" - bind $w.desc.name_t "focus $w.from.exp_t;break" + bind $w.desc.name_t {focus [tk_focusPrev %W];break} + bind $w.desc.name_t {focus [tk_focusNext %W];break} bind $w.desc.name_t "do_create_branch_action $w;break" bind $w.desc.name_t { if {{%K} ne {BackSpace} @@ -1860,8 +1860,8 @@ proc do_create_branch {} { -width 50 \ -font font_ui grid $w.from.exp_r $w.from.exp_t -stick we -padx {0 5} - bind $w.from.exp_t "focus $w.desc.name_t;break" - bind $w.from.exp_t "focus $w.postActions.checkout;break" + bind $w.from.exp_t {focus [tk_focusPrev %W];break} + bind $w.from.exp_t {focus [tk_focusNext %W];break} bind $w.from.exp_t "do_create_branch_action $w;break" grid columnconfigure $w.from 1 -weight 1 pack $w.from -anchor nw -fill x -pady 5 -padx 5 @@ -3049,7 +3049,8 @@ proc do_options {} { -width 20 \ -font font_ui $w.$f.$name.v insert 0.0 [set ${f}_config_new(gui.$name)] - bind $w.$f.$name.v break + bind $w.$f.$name.v {focus [tk_focusPrev %W];break} + bind $w.$f.$name.v {focus [tk_focusNext %W];break} bind $w.$f.$name.v break bind $w.$f.$name.v " set ${f}_config_new(gui.$name) \ From b36ffe800d830556e8df7eda8743c15dfac70e27 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 16:43:14 -0500 Subject: [PATCH 291/548] git-gui: Fully select a field when entering into it. If the user is tabbing through fields in the options dialog they are likely to want to just enter a new value for the field, rather than edit the value in-place. This is easier if we select the entire value upon focusing into the field. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index d59e720408..87fdce593d 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3036,6 +3036,7 @@ proc do_options {} { -from 1 -to 99 -increment 1 \ -width 3 \ -font font_ui + bind $w.$f.$name.v {%W selection range 0 end} pack $w.$f.$name.v -side right -anchor e -padx 5 pack $w.$f.$name -side top -anchor w -fill x } @@ -3052,6 +3053,7 @@ proc do_options {} { bind $w.$f.$name.v {focus [tk_focusPrev %W];break} bind $w.$f.$name.v {focus [tk_focusNext %W];break} bind $w.$f.$name.v break + bind $w.$f.$name.v "$w.$f.$name.v tag add sel 0.0 end" bind $w.$f.$name.v " set ${f}_config_new(gui.$name) \ \[string trim \[$w.$f.$name.v get 0.0 end\]\] @@ -3088,6 +3090,7 @@ proc do_options {} { -from 2 -to 80 -increment 1 \ -width 3 \ -font font_ui + 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 From 4343434307dca441cd2dade1ece7c0ef5d4a7de2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 17:02:25 -0500 Subject: [PATCH 292/548] git-gui: Automatically toggle the relevant radio buttons. When the user selects a starting revision from one of our offered popup lists (local branches or tracking branches) or enters in an expression in the expression input field we should automatically activate the corresponding radio button for them. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 87fdce593d..593e4fd5a2 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1768,16 +1768,26 @@ proc do_create_branch_action {w} { } } +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 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 - set create_branch_checkout 1 - set create_branch_revtype head - set create_branch_head $current_branch - set create_branch_trackinghead {} - set w .branch_editor toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" @@ -1863,6 +1873,8 @@ proc do_create_branch {} { bind $w.from.exp_t {focus [tk_focusPrev %W];break} bind $w.from.exp_t {focus [tk_focusNext %W];break} bind $w.from.exp_t "do_create_branch_action $w;break" + bind $w.from.exp_t break + bind $w.from.exp_t {set create_branch_revtype expression} grid columnconfigure $w.from 1 -weight 1 pack $w.from -anchor nw -fill x -pady 5 -padx 5 @@ -1876,6 +1888,10 @@ proc do_create_branch {} { 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 + bind $w "grab $w; focus $w.desc.name_t" bind $w "destroy $w" bind $w "do_create_branch_action $w;break" @@ -1978,10 +1994,6 @@ proc do_delete_branch {} { global all_heads tracking_branches current_branch global delete_branch_checktype delete_branch_head delete_branch_trackinghead - set delete_branch_checktype head - set delete_branch_head $current_branch - set delete_branch_trackinghead {} - set w .branch_editor toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" @@ -2049,6 +2061,9 @@ proc do_delete_branch {} { 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" From 9c10deab6ceb15b6b576d9bc77955c24d5baa709 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 17:22:40 -0500 Subject: [PATCH 293/548] git-gui: Correctly categorize tracking branches and heads. Up until now git-gui did not support the new wildcard syntax used to fetch any remote branch into a tracking branch during 'git fetch'. Now if we identify a tracking branch as ending with the string '/*' then we use for-each-ref to print out the reference names which may have been fetched by that pattern. We also now correctly filter any tracking branches out of refs/heads, if they user has placed any there. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 46 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 593e4fd5a2..c969db5fa5 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1629,16 +1629,27 @@ proc write_checkout_index {fd pathList totalCnt batch msg after} { ## ## 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 tracking_branches + global all_heads set all_heads [list] - set cmd [list git for-each-ref] - lappend cmd --format=%(refname) - lappend cmd refs/heads - set fd [open "| $cmd" r] + set fd [open "| git for-each-ref --format=%(refname) refs/heads" r] while {[gets $fd line] > 0} { - if {![catch {set info $tracking_branches($line)}]} continue + if {[is_tracking_branch $line]} continue if {![regsub ^refs/heads/ $line {} name]} continue lappend all_heads $name } @@ -1682,11 +1693,26 @@ proc populate_branch_menu {} { proc all_tracking_branches {} { global tracking_branches - set all_trackings [list] - foreach b [array names tracking_branches] { - regsub ^refs/(heads|remotes)/ $b {} b - lappend all_trackings $b + 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] } From d4dd034ab5bb13162c4a4e6c588fe530c7739a95 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 17:26:22 -0500 Subject: [PATCH 294/548] git-gui: Update todo list with finished and new items. Signed-off-by: Shawn O. Pearce --- TODO | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/TODO b/TODO index ef4f50b304..b95a137322 100644 --- a/TODO +++ b/TODO @@ -6,9 +6,11 @@ Items outstanding: * Make use of the new default merge data stored in repo-config. - * Checkout or create a different local branch. + * Checkout a different local branch. - * Delete a local branch. + * Push any local branch to a remote branch. + + * Merge any local branches through a real merge UI. * Allow user to define keyboard shortcuts for frequently used fetch or merge operations. Or maybe just define a keyboard shortcut @@ -40,10 +42,3 @@ Known bugs: process is just terminating due to a segfault or something, as the do_quit proc in git-gui doesn't run. It often seems to occur while writing a commit message in the buffer. Odd. - - * At one point after using git-gui for a while to make many commits - to a repository I reverted one file through git-gui and another - manually in my editor; during commit git-gui crashed with an - error about the icon name it was trying to update no longer - existed in the widget. I suspect something didn't update right - in file_states... From c5a1eb889ca3df51ac443916a037b59c46983239 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 17:50:42 -0500 Subject: [PATCH 295/548] git-gui: Slightly tweak new window geometry. I didn't really like the way a new git-gui launched in a new repository as the window geometry wasn't quite the best layou. So this is a minor tweak to try and get space distributed around the window better. By decreasing the widths we're also able to shrink the gui smaller without Tk clipping content at the edge of the window. A nice feature. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index c969db5fa5..7115cb5d05 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3726,17 +3726,17 @@ pack .branch -side top -fill x # panedwindow .vpane -orient vertical panedwindow .vpane.files -orient horizontal -.vpane add .vpane.files -sticky nsew -height 100 -width 400 +.vpane add .vpane.files -sticky nsew -height 100 -width 200 pack .vpane -anchor n -side top -fill both -expand 1 # -- Index File List # -frame .vpane.files.index -height 100 -width 400 +frame .vpane.files.index -height 100 -width 200 label .vpane.files.index.title -text {Changes To Be Committed} \ -background green \ -font font_ui text $ui_index -background white -borderwidth 0 \ - -width 40 -height 10 \ + -width 20 -height 10 \ -wrap none \ -font font_ui \ -cursor $cursor_ptr \ @@ -3753,12 +3753,12 @@ pack $ui_index -side left -fill both -expand 1 # -- Working Directory File List # -frame .vpane.files.workdir -height 100 -width 100 +frame .vpane.files.workdir -height 100 -width 200 label .vpane.files.workdir.title -text {Changed But Not Updated} \ -background red \ -font font_ui text $ui_workdir -background white -borderwidth 0 \ - -width 40 -height 10 \ + -width 20 -height 10 \ -wrap none \ -font font_ui \ -cursor $cursor_ptr \ From f5925d934fdd1ffc3ded7272ea278437608f5f50 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 18:00:03 -0500 Subject: [PATCH 296/548] git-gui: Create missing branch head on initial commit. If we are making an initial commit our branch head did not exist when we scanned for all heads during startup. Consequently we won't have it in our branch menu. So force it to be put there after the ref was created. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index 7115cb5d05..d71fcd8220 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -997,7 +997,7 @@ proc commit_writetree {curHEAD msg} { proc commit_committree {fd_wt curHEAD msg} { global HEAD PARENT MERGE_HEAD commit_type - global single_commit + global single_commit all_heads current_branch global ui_status_value ui_comm selected_commit_type global file_states selected_paths rescan_active @@ -1049,6 +1049,14 @@ proc commit_committree {fd_wt curHEAD msg} { return } + # -- 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 + } + # -- Cleanup after ourselves. # catch {file delete [gitdir MERGE_HEAD]} From 8ce03164842d250f1cf1dfbf9a245fd030195e24 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 21 Jan 2007 23:11:47 -0500 Subject: [PATCH 297/548] git-gui: Don't format the mode line of a diff. We sometimes see a mode line show up in a diff if the file mode was changed. But its not something we format specially. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/git-gui.sh b/git-gui.sh index d71fcd8220..396a44e654 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -709,6 +709,7 @@ proc read_diff {fd} { if {[string match {@@@ *} $line]} {set is_3way_diff 1} if {[string match {index *} $line] + || [string match {mode *} $line] || [regexp {^\* Unmerged path } $line]} { set tags {} } elseif {$is_3way_diff} { From 46aaf90b4901cbae9da9d59ba8cc914dcfc38aa3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 22 Jan 2007 17:10:38 -0500 Subject: [PATCH 298/548] git-gui: Force an update-index --refresh on unchanged files. Its possible for external programs to update file modification dates of many files within a repository. I've seen this on Windows with a popular virus scanner, sadly enough. If the user has Trust File Modification Timestamp enabled and the virus scanner touches a large number of files it can be annoying trying to clear them out of the 'Changed But Not Updated' file list by clicking on them one at a time to load the diff. So now we force a rescan as soon as one such file is found, and for just that rescan we disable the Trust File Modification Timestamp option thereby allowing Git to update the modification dates in the index. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 396a44e654..2350baa3a3 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -335,7 +335,7 @@ proc PARENT {} { return $empty_tree } -proc rescan {after} { +proc rescan {after {honor_trustmtime 1}} { global HEAD PARENT MERGE_HEAD commit_type global ui_index ui_workdir ui_status_value ui_comm global rescan_active file_states @@ -366,7 +366,7 @@ proc rescan {after} { $ui_comm edit modified false } - if {$repo_config(gui.trustmtime) eq {true}} { + if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} { rescan_stage2 {} $after } else { set rescan_active 1 @@ -586,17 +586,11 @@ by another application and you currently have the Trust File Modification Timestamps option enabled, so Git did not automatically detect that there are no content differences in this -file. - -This file will now be removed from the modified -files list, to prevent possible confusion. -" - if {[catch {exec git update-index -- $path} err]} { - error_popup "Failed to refresh index:\n\n$err" - } +file." clear_diff display_file $path __ + rescan {set ui_status_value {Ready.}} 0 } proc show_diff {path w {lno {}}} { From e0c781b34756ce47ab9a6ae89eba8067733d6e5c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 22 Jan 2007 18:24:45 -0500 Subject: [PATCH 299/548] git-gui: Don't attempt to tag new file/deleted file headers in diffs. We don't want to tag these new file/delete file lines, as they aren't actually that interesting. Its quite clear from the diff itself that the file is a new file or is a deleted file (as the entire thing will appear in the diff). Signed-off-by: Shawn O. Pearce --- git-gui.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index 2350baa3a3..d697d1ebe2 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -704,6 +704,8 @@ proc read_diff {fd} { if {[string match {index *} $line] || [string match {mode *} $line] + || [string match {new file *} $line] + || [string match {deleted file *} $line] || [regexp {^\* Unmerged path } $line]} { set tags {} } elseif {$is_3way_diff} { From 75e78c8a1bb273461c14a5b37f9b06215f502e67 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 22 Jan 2007 18:31:12 -0500 Subject: [PATCH 300/548] git-gui: Fix 'Select All' action on Windows. Sometimes the Select All action from our context menus doesn't work unless the text field its supposed to act on has focus. I'm not really sure why adding the sel tag requires having focus. It technically should not be required to update the sel tag membership, but perhaps there is a bug in Tcl/Tk 8.4.1 on Windows which is causing this odd behavior. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index d697d1ebe2..bbf57b96a0 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2539,7 +2539,7 @@ proc console_init {w} { -command "tk_textCopy $w.m.t" $w.ctxm add command -label "Select All" \ -font font_ui \ - -command "$w.m.t tag add sel 0.0 end" + -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 " @@ -3913,7 +3913,7 @@ $ctxm add separator $ctxm add command \ -label {Select All} \ -font font_ui \ - -command {$ui_comm tag add sel 0.0 end} + -command {focus $ui_comm;$ui_comm tag add sel 0.0 end} $ctxm add command \ -label {Copy All} \ -font font_ui \ @@ -4059,7 +4059,7 @@ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ -label {Select All} \ -font font_ui \ - -command {$ui_diff tag add sel 0.0 end} + -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} \ From e54a1bd122007af30fcf174a57f892157501673f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 22 Jan 2007 19:18:39 -0500 Subject: [PATCH 301/548] git-gui: Ignore 'No newline at end of file' marker line. If one or both versions of the file don't have a newline at the end of the file we get a line telling us so in the diff output. This shouldn't be tagged, nor should it generate a warning about not being tagged. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/git-gui.sh b/git-gui.sh index bbf57b96a0..54204aee78 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -706,6 +706,7 @@ proc read_diff {fd} { || [string match {mode *} $line] || [string match {new file *} $line] || [string match {deleted file *} $line] + || $line eq {\ No newline at end of file} || [regexp {^\* Unmerged path } $line]} { set tags {} } elseif {$is_3way_diff} { From 124355d32c0612192e729e12f1d3d68849754f29 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 22 Jan 2007 22:41:13 -0500 Subject: [PATCH 302/548] git-gui: Always start a rescan on an empty diff. If we got an empty diff its probably because the modification time of the file was changed but the file content hasn't been changed. Typically this happens because an outside program modified the file and git-gui was told to not run 'update-index --refresh', as the user generally trusts file modification timestamps. But we can also get an empty diff when a program undos a file change and still updates the modification timestamp upon saving, but has undone the file back to the same as what is in the index or in PARENT. So even if gui.trustmtime is false we should still run a rescan on an empty diff. This change also lets us cleanup the dialog message that we show when this case occurs, as its no longer got anything to do with Trust File Modification Timestamps. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 54204aee78..6ccd4113d8 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -582,11 +582,11 @@ proc handle_empty_diff {} { [short_path $path] has no changes. The modification date of this file was updated -by another application and you currently have -the Trust File Modification Timestamps option -enabled, so Git did not automatically detect -that there are no content differences in this -file." +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 __ @@ -683,7 +683,6 @@ proc show_diff {path w {lno {}}} { proc read_diff {fd} { global ui_diff ui_status_value is_3way_diff diff_active - global repo_config $ui_diff conf -state normal while {[gets $fd line] >= 0} { @@ -763,8 +762,7 @@ proc read_diff {fd} { unlock_index set ui_status_value {Ready.} - if {$repo_config(gui.trustmtime) eq {true} - && [$ui_diff index end] eq {2.0}} { + if {[$ui_diff index end] eq {2.0}} { handle_empty_diff } } From 464c9ffee45520c8be3ec656f926224501bcf977 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 23 Jan 2007 02:08:09 -0500 Subject: [PATCH 303/548] git-gui: Don't show content of untracked binary files. A binary file can be very large, and showing the complete content of one is horribly ugly and confusing. So we now use the same rule that core Git uses; if there is a NUL byte (\0) within the first 8000 bytes of the file we assume it is binary and refuse to show the content. Given that we have loaded the entire content of the file into memory we probably could just afford to search the whole thing, but we also probably should not load multi-megabyte binary files either. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index 6ccd4113d8..9136e7fe98 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -637,6 +637,9 @@ proc show_diff {path w {lno {}}} { error_popup "Error loading file:\n\n$err" return } + if {[string first "\0" [string range $content 0 8000]] != -1} { + set content {* Binary file (not showing content).} + } $ui_diff conf -state normal $ui_diff insert end $content $ui_diff conf -state disabled From 19b41e455932d56a578becdfd91095a9a041f21c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 23 Jan 2007 02:33:58 -0500 Subject: [PATCH 304/548] git-gui: Limit display of large untracked files. Our internal diff viewer displays untracked files to help users see if they should become tracked, or not. It is not meant as a full file viewer that handles any sort of input. Consequently it is rather unreasonable for users to expect us to show them very large files. Some users may click on a very big file (and not know its very big) then get surprised when Tk takes a long time to load the content and render it, especially if their memory is tight and their OS starts to swap processes out. Instead we now limit the amount of data we load to the first 128 KiB of any untracked file. If the file is larger than 128 KiB we display a warning message at the top of our diff viewer to notify the user that we are not going to load the entire thing. Users should be able to recognize a file just by its first 128 KiB and determine if it should be added to the repository or not. Since we are loading 128 KiB we may as well scan it to see if the file is binary. So I've removed the "first 8000 bytes" rule and just allowed git-gui to scan the entire data chunk that it read in. This is probably faster anyway if Tcl's [string range] command winds up making a copy of the data. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 9136e7fe98..f71dabe68a 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -626,10 +626,12 @@ proc show_diff {path w {lno {}}} { # - 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] + set content [read $fd $max_sz] close $fd + set sz [file size $path] } err ]} { set diff_active 0 unlock_index @@ -637,11 +639,27 @@ proc show_diff {path w {lno {}}} { error_popup "Error loading file:\n\n$err" return } - if {[string first "\0" [string range $content 0 8000]] != -1} { - set content {* Binary file (not showing content).} - } $ui_diff conf -state normal - $ui_diff insert end $content + 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 From d3596fd9486b602252d5b2c3e14ac55a07a2bdb9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 23 Jan 2007 03:18:37 -0500 Subject: [PATCH 305/548] git-gui: When possible show the type of an untracked file. Users may want to know what a file is before they add it to the repository, especially if its a binary file. So when possible invoke 'file' on the path and try to get its output. Since this is strictly advice to the user we won't bother to report any failures from our attempt to run `file`. Since some file commands also output the path name they were given we look for that case and strip it off the front of the returned output before placing it into the diff viewer. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index f71dabe68a..37757cfb65 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -640,6 +640,14 @@ proc show_diff {path w {lno {}}} { 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)." \ From 4e62e2725e487b515a57e1c6ae5ef9b81546884d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 23 Jan 2007 03:25:17 -0500 Subject: [PATCH 306/548] git-gui: Don't try to tag the 'Binary files * and * differ' line. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/git-gui.sh b/git-gui.sh index 37757cfb65..c6757045ab 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -734,6 +734,7 @@ proc read_diff {fd} { || [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 {} From 0565246a7c886475878ea08cd501ea7ac4de6ada Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 23 Jan 2007 03:30:02 -0500 Subject: [PATCH 307/548] git-gui: Remove spurious newline in untracked file display. This newline is stupid; it doesn't get put here unless the file is very large, and then its just sort of out of place. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index c6757045ab..2ebc463b31 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -657,7 +657,6 @@ proc show_diff {path w {lno {}}} { $ui_diff insert end \ "* Untracked file is $sz bytes. * Showing only first $max_sz bytes. - " d_@ } $ui_diff insert end $content From 51a989ba5a4d1299d08ddad19c6a45485bdb7dd8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 23 Jan 2007 04:07:18 -0500 Subject: [PATCH 308/548] git-gui: Honor system encoding for filenames. Since git operates on filenames using the operating system encoding any data we are receiving from it by way of a pipe, or sending to it by way of a pipe must be formatted in that encoding. This should be the same as the Tcl system encoding, as its the encoding that applications should be using to converse with the operating system. Sadly this does not fix the gitweb/test file in git.git on Macs; that's due to something really broken happening in the filesystem. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 2ebc463b31..386ae989b8 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -410,9 +410,9 @@ proc rescan_stage2 {fd after} { set fd_df [open "| git diff-files -z" r] set fd_lo [open $ls_others r] - fconfigure $fd_di -blocking 0 -translation binary - fconfigure $fd_df -blocking 0 -translation binary - fconfigure $fd_lo -blocking 0 -translation binary + fconfigure $fd_di -blocking 0 -translation binary -encoding binary + fconfigure $fd_df -blocking 0 -translation binary -encoding binary + fconfigure $fd_lo -blocking 0 -translation binary -encoding binary fileevent $fd_di readable [list read_diff_index $fd_di $after] fileevent $fd_df readable [list read_diff_files $fd_df $after] fileevent $fd_lo readable [list read_ls_others $fd_lo $after] @@ -450,8 +450,9 @@ proc read_diff_index {fd after} { incr c set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }] + set p [string range $buf_rdi $z1 [expr {$z2 - 1}]] merge_state \ - [string range $buf_rdi $z1 [expr {$z2 - 1}]] \ + [encoding convertfrom $p] \ [lindex $i 4]? \ [list [lindex $i 0] [lindex $i 2]] \ [list] @@ -482,8 +483,9 @@ proc read_diff_files {fd after} { incr c set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }] + set p [string range $buf_rdf $z1 [expr {$z2 - 1}]] merge_state \ - [string range $buf_rdf $z1 [expr {$z2 - 1}]] \ + [encoding convertfrom $p] \ ?[lindex $i 4] \ [list] \ [list [lindex $i 0] [lindex $i 2]] @@ -506,7 +508,7 @@ proc read_ls_others {fd after} { set pck [split $buf_rlo "\0"] set buf_rlo [lindex $pck end] foreach p [lrange $pck 0 end-1] { - merge_state $p ?O + merge_state [encoding convertfrom $p] ?O } rescan_done $fd buf_rlo $after } @@ -1459,6 +1461,7 @@ proc update_indexinfo {msg pathList after} { -blocking 0 \ -buffering full \ -buffersize 512 \ + -encoding binary \ -translation binary fileevent $fd writable [list \ write_update_indexinfo \ @@ -1499,7 +1502,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch msg after} { set info [lindex $s 2] if {$info eq {}} continue - puts -nonewline $fd "$info\t$path\0" + puts -nonewline $fd "$info\t[encoding convertto $path]\0" display_file $path $new } @@ -1531,6 +1534,7 @@ proc update_index {msg pathList after} { -blocking 0 \ -buffering full \ -buffersize 512 \ + -encoding binary \ -translation binary fileevent $fd writable [list \ write_update_index \ @@ -1575,7 +1579,7 @@ proc write_update_index {fd pathList totalCnt batch msg after} { ?M {set new M_} ?? {continue} } - puts -nonewline $fd "$path\0" + puts -nonewline $fd "[encoding convertto $path]\0" display_file $path $new } @@ -1613,6 +1617,7 @@ proc checkout_index {msg pathList after} { -blocking 0 \ -buffering full \ -buffersize 512 \ + -encoding binary \ -translation binary fileevent $fd writable [list \ write_checkout_index \ @@ -1645,7 +1650,7 @@ proc write_checkout_index {fd pathList totalCnt batch msg after} { U? {continue} ?M - ?D { - puts -nonewline $fd "$path\0" + puts -nonewline $fd "[encoding convertto $path]\0" display_file $path ?_ } } From 59885273c337df937430b8731633c2e9020ce8e3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 23 Jan 2007 04:40:21 -0500 Subject: [PATCH 309/548] git-gui: Handle commit encoding better. Git prefers that all log messages are encoding in UTF-8. So now when git-gui generates the commit message it converts the commit message text from the internal Tcl Unicode representation into a UTF-8 file. The file is then fed as stdin to git-commit-tree. I had to start using a file here rather than feeding the message in with << as << uses the system encoding, which we may not want. When we reload a commit message via git-cat-file we are getting the raw byte stream, with no encoding performed by Git itself. So unless the new 'encoding' header appears in the message we should probably assume it is utf-8 encoded; but if the header is present we need to use whatever it claims. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index 386ae989b8..6a4086d475 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -805,6 +805,7 @@ proc read_diff {fd} { 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. @@ -831,11 +832,18 @@ current merge activity. 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]] } } + fconfigure $fd -encoding $enc set msg [string trim [read $fd]] close $fd } err]} { @@ -1027,6 +1035,7 @@ proc commit_committree {fd_wt curHEAD msg} { global single_commit 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]} { @@ -1036,6 +1045,17 @@ proc commit_committree {fd_wt curHEAD msg} { 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 $enc -translation binary + puts -nonewline $msg_wt $msg + close $msg_wt + # -- Create the commit. # set cmd [list git commit-tree $tree_id] @@ -1048,7 +1068,7 @@ proc commit_committree {fd_wt curHEAD msg} { # git commit-tree writes to stderr during initial commit. lappend cmd 2>/dev/null } - lappend cmd << $msg + lappend cmd <$msg_p if {[catch {set cmt_id [eval exec $cmd]} err]} { error_popup "commit-tree failed:\n\n$err" set ui_status_value {Commit failed.} @@ -1086,6 +1106,7 @@ proc commit_committree {fd_wt curHEAD msg} { # -- 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]} From 0fd49d0a7d579722bd1a0a1645afc16646b05794 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 24 Jan 2007 15:21:01 -0500 Subject: [PATCH 310/548] git-gui: Display database stats (count-objects -v) on demand. Its nice to know how many loose objects and roughly how much disk space they are taking up, so that you can guestimate about when might be a good time to run 'Compress Database'. The same is true of packfiles, especially once the automatic keep-pack code in git-fetch starts to be more widely used. We now offer the output of count-objects -v in a nice little dialog hung off the Repository menu. Our labels are slightly more verbose than those of `count-objects -v`, so users will hopefully be able to make better sense of what we are showing them here. We probably should also offer pack file size information, and data about *.idx files which exist which lack corresponding *.pack files (a situation caused by the HTTP fetch client). But in the latter case we should only offer the data once we have way to let the user clean up old and inactive index files. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 6a4086d475..5171db6f27 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1904,7 +1904,7 @@ proc do_create_branch {} { -width 40 \ -font font_ui $w.desc.name_t insert 0.0 $repo_config(gui.newbranchtemplate) - grid $w.desc.name_l $w.desc.name_t -stick we -padx {0 5} + grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5} bind $w.desc.name_t {focus [tk_focusPrev %W];break} bind $w.desc.name_t {focus [tk_focusNext %W];break} bind $w.desc.name_t "do_create_branch_action $w;break" @@ -1954,7 +1954,7 @@ proc do_create_branch {} { -height 1 \ -width 50 \ -font font_ui - grid $w.from.exp_r $w.from.exp_t -stick we -padx {0 5} + grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5} bind $w.from.exp_t {focus [tk_focusPrev %W];break} bind $w.from.exp_t {focus [tk_focusNext %W];break} bind $w.from.exp_t "do_create_branch_action $w;break" @@ -2719,6 +2719,63 @@ 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 w .stats_view + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text {Database Statistics} \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons -border 1 + button $w.buttons.close -text Close \ + -font font_ui \ + -command [list destroy $w] + button $w.buttons.gc -text {Compress Database} \ + -font font_ui \ + -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}} + {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 -font font_ui + label $w.stat.v_$name -text $value -anchor w -font font_ui + grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5} + } + pack $w.stat + + bind $w "grab $w; focus $w" + 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_exec $w {git gc} @@ -3542,6 +3599,10 @@ if {![is_MacOSX]} { .mbar.repository add separator if {!$single_commit} { + .mbar.repository add command -label {Database Statistics} \ + -command do_stats \ + -font font_ui + .mbar.repository add command -label {Compress Database} \ -command do_gc \ -font font_ui @@ -3847,7 +3908,7 @@ frame .vpane.lower.commarea frame .vpane.lower.diff -relief sunken -borderwidth 1 pack .vpane.lower.commarea -side top -fill x pack .vpane.lower.diff -side bottom -fill both -expand 1 -.vpane add .vpane.lower -stick nsew +.vpane add .vpane.lower -sticky nsew # -- Commit Area Buttons # From b5b6b434527e5f0df43f994d690a48dad1fb4555 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 24 Jan 2007 16:51:59 -0500 Subject: [PATCH 311/548] git-gui: Implement basic branch switching through read-tree. If the user selects a different branch from the Branch menu, or asks us to create a new branch and immediately checkout that branch we now perform the update of the working directory by way of a 2 way read-tree invocation. This emulates the behavior of `git checkout branch` or the behavior of `git checkout -b branch initrev`. We don't however support the -m style behavior, where a switch can occur with file level merging performed by merge-recursive. Support for this is planned for a future update. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 116 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 101 insertions(+), 15 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 5171db6f27..b5c2c7406d 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2155,16 +2155,11 @@ proc do_delete_branch {} { tkwait window $w } -proc switch_branch {b} { - global HEAD commit_type file_states current_branch - global selected_commit_type ui_comm +proc switch_branch {new_branch} { + global HEAD commit_type current_branch repo_config if {![lock_index switch]} return - # -- Backup the selected branch (repository_state resets it) - # - set new_branch $current_branch - # -- Our in memory state should match the repository. # repository_state curType curHEAD curMERGE_HEAD @@ -2185,19 +2180,110 @@ The rescan will be automatically started now. return } - # -- Toss the message buffer if we are in amend mode. + 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. # - if {[string match amend* $curType]} { + 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 {exec 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'." } - - set selected_commit_type new - set current_branch $new_branch - - unlock_index - error "NOT FINISHED" } ###################################################################### From f747133720d26e52b6ed83dfd57cdd1a74668b3d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 24 Jan 2007 17:01:49 -0500 Subject: [PATCH 312/548] git-gui: Use system default labelframe bordering. In the new branch dialog and delete branch dialog we are using the system default labelframe border settings (whatever those are) and they look reasonable on both Windows and Mac OS X. But for some unknown reason to me I used a raised border for the options dialog. It doesn't look consistent anymore, so I'm switching it to the defaults. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index b5c2c7406d..842fccc9f8 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3243,11 +3243,9 @@ proc do_options {} { pack $w.buttons -side bottom -fill x -pady 10 -padx 10 labelframe $w.repo -text "[reponame] Repository" \ - -font font_ui \ - -relief raised -borderwidth 2 + -font font_ui labelframe $w.global -text {Global (All Repositories)} \ - -font font_ui \ - -relief raised -borderwidth 2 + -font font_ui 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 From bb816c5a25e3a87d4d9588ddcccf83b8a10d8d31 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 24 Jan 2007 19:08:49 -0500 Subject: [PATCH 313/548] git-gui: Display the size of the pack directory. Just as we show the amount of disk space taken by the loose objects, its interesting to know how much space is taken by the packs directory. So show that in our Database Statistics dialog. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index 842fccc9f8..79f980c470 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2814,6 +2814,16 @@ proc do_stats {} { } 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 .]" @@ -2839,6 +2849,7 @@ proc do_stats {} { {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}} } { From 86773d9bfc6124dec7b33094103f4819684bc4ae Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 24 Jan 2007 20:39:30 -0500 Subject: [PATCH 314/548] git-gui: Only allow Refresh in diff context menu when we have a diff. There is no reason to attempt refreshing an empty diff viewer, so the Refresh option of our diff context menu should be disabled when there is no diff currently shown. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/git-gui.sh b/git-gui.sh index 79f980c470..b9e3d563e2 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -4261,6 +4261,7 @@ $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 \ From a25c51893317bcbd8e8a85b6da3a573fcd096d86 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 24 Jan 2007 21:20:57 -0500 Subject: [PATCH 315/548] git-gui: Allow staging/unstaging individual diff hunks. Just like `git-add --interactive` we can now stage and unstage individual hunks within a file, rather than the entire file at once. This works on the basic idea of scanning backwards from the mouse position to find the hunk header, then going forwards to find the end of the hunk. Everything in that is sent to `git apply --cached`, prefixed by the diff header lines. We ignore whitespace errors while applying a hunk, as we expect the user's pre-commit hook to catch any possible problems. This matches our existing behavior with regards to adding an entire file with no whitespace error checking. Applying hunks means that we now have to capture and save the diff header lines, rather than chucking them. Not really a big deal, we just needed a new global to hang onto that current header information. We probably could have recreated it on demand during apply_hunk but that would mean we need to implement all of the funny rules about how to encode weird path names (e.g. ones containing LF) into a diff header so that the `git apply` process would understand what we are asking it to do. Much simpler to just store this small amount of data in a global and replay it when needed. I'm making absolutely no attempt to correct the line numbers on the remaining hunk headers after one hunk has been applied. This may cause some hunks to fail, as the position information would not be correct. Users can always refresh the current diff before applying a failing hunk to work around the issue. Perhaps if we ever implement hunk splitting we could also fix the remaining hunk headers. Applying hunks directly means that we need to process the diff data in binary, rather than using the system encoding and an automatic linefeed translation. This ensures that CRLF formatted files will be able to be fed directly to `git apply` without failures. Unfortunately it also means we will see CRs show up in the GUI as ugly little boxes at the end of each line in a CRLF file. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 122 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 12 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index b9e3d563e2..c8098ac9f6 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -545,13 +545,15 @@ proc prune_selection {} { ## diff proc clear_diff {} { - global ui_diff current_diff_path ui_index ui_workdir + 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 @@ -599,7 +601,7 @@ 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 + global current_diff_path current_diff_side current_diff_header if {$diff_active || ![lock_index read]} return @@ -623,6 +625,7 @@ proc show_diff {path w {lno {}}} { 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! @@ -707,22 +710,30 @@ proc show_diff {path w {lno {}}} { return } - fconfigure $fd -blocking 0 -translation auto + 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 is_3way_diff diff_active + 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]} continue - if {[string match {diff --cc *} $line]} continue - if {[string match {diff --combined *} $line]} continue - if {[string match {--- *} $line]} continue - if {[string match {+++ *} $line]} continue + 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" } @@ -731,8 +742,7 @@ proc read_diff {fd} { # if {[string match {@@@ *} $line]} {set is_3way_diff 1} - if {[string match {index *} $line] - || [string match {mode *} $line] + if {[string match {mode *} $line] || [string match {new file *} $line] || [string match {deleted file *} $line] || [string match {Binary files * and * differ} $line] @@ -799,6 +809,77 @@ proc read_diff {fd} { } } +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 @@ -4142,6 +4223,7 @@ bind_button3 $ui_comm "tk_popup $ctxm %X %Y" # -- Diff Header # set current_diff_path {} +set current_diff_side {} set diff_actions [list] proc trace_current_diff_path {varname args} { global current_diff_path diff_actions file_states @@ -4282,6 +4364,13 @@ $ctxm add command \ } 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 \ @@ -4313,7 +4402,16 @@ $ctxm add separator $ctxm add command -label {Options...} \ -font font_ui \ -command do_options -bind_button3 $ui_diff "tk_popup $ctxm %X %Y" +bind_button3 $ui_diff " + set cursorX %x + set cursorY %y + if {\$ui_index eq \$current_diff_side} { + $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit} + } else { + $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit} + } + tk_popup $ctxm %X %Y +" # -- Status Bar # From 30b14ed390f543e52665c8fc4f626aab78e3dcab Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 24 Jan 2007 21:30:23 -0500 Subject: [PATCH 316/548] git-gui: Elide CRs appearing in diff output from display. If we are displaying a diff for a DOS-style (CRLF) formatted file then the Tk text widget would normally show the CR at the end of every line; in most fonts this will come out as a square box. Rather than showing this character we'll tag it with a tag which forces the character to be elided away, so its not displayed. However since the character is still within the text buffer we can still obtain it and supply it over to `git apply` when staging or unstaging an individual hunk, ensuring that the file contents is always fully preserved as-is. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index c8098ac9f6..ee7fdaabfd 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -793,6 +793,9 @@ proc read_diff {fd} { } } $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 @@ -4304,6 +4307,7 @@ pack $ui_diff -side left -fill both -expand 1 pack .vpane.lower.diff.header -side top -fill x pack .vpane.lower.diff.body -side bottom -fill both -expand 1 +$ui_diff tag conf d_cr -elide true $ui_diff tag conf d_@ -foreground blue -font font_diffbold $ui_diff tag conf d_+ -foreground {#00a000} $ui_diff tag conf d_- -foreground red From 4e55d19a1377de55c950a90c10b31ae37b79363d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 25 Jan 2007 12:54:59 -0500 Subject: [PATCH 317/548] git-gui: Cleanup end-of-line whitespace in commit messages. When committing changes its useless to have trailing whitespace on the end of a line within the commit message itself; this serves no purpose beyond wasting space in the repository. But it happens a lot on my Mac OS X system if I copy text out of a Terminal.app window and paste it into git-gui. We now clip any trailing whitespace from the commit buffer when loading it from a file, when saving it out to our backup file, or when making the actual commit object. I also fixed a bug where we lost the commit message buffer if you quit without editing the text region. This can happen if you quit and restart git-gui frequently in the middle of an editing session. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index ee7fdaabfd..5d418b99c8 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -428,6 +428,7 @@ proc load_message {file} { } set content [string trim [read $fd]] close $fd + regsub -all -line {[ \r\t]+$} $content {} content $ui_comm delete 0.0 end $ui_comm insert end $content return 1 @@ -1046,6 +1047,7 @@ You must add at least 1 file before you can commit. # -- 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. @@ -2984,12 +2986,13 @@ proc do_quit {} { # set save [gitdir GITGUI_MSG] set msg [string trim [$ui_comm get 0.0 end]] - if {![string match amend* $commit_type] - && [$ui_comm edit modified] + regsub -all -line {[ \r\t]+$} $msg {} msg + if {(![string match amend* $commit_type] + || [$ui_comm edit modified]) && $msg ne {}} { catch { set fd [open $save w] - puts $fd [string trim [$ui_comm get 0.0 end]] + puts -nonewline $fd $msg close $fd } } else { From b9a75e3a979cc39f5c8d0e6d79e19ae31aef17e3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 25 Jan 2007 12:55:20 -0500 Subject: [PATCH 318/548] git-gui: Unset unnecessary UI setup variable. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/git-gui.sh b/git-gui.sh index 5d418b99c8..f587f31a2c 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -4419,6 +4419,7 @@ bind_button3 $ui_diff " } tk_popup $ctxm %X %Y " +unset ui_diff_applyhunk # -- Status Bar # From 23effa79f7697cd27f04cb6edc9680e11e15f02a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 25 Jan 2007 12:57:57 -0500 Subject: [PATCH 319/548] git-gui: Force focus to the diff viewer on mouse click. Apparently a "feature" of Tcl/Tk on Mac OS X is that a disabled text widget cannot receive focus or receive a selection within it. This makes the diff viewer almost useless on that platform as you cannot select individual parts of the buffer. Now we force focus into the diff viewer when its clicked on with button 1. This works around the feature and allows selection to work within the viewer just like it does on other less sane systems, like Microsoft Windows. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/git-gui.sh b/git-gui.sh index f587f31a2c..ea4136c98b 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -4472,6 +4472,7 @@ 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 {focus %W} if {!$single_commit} { bind . <$M1B-Key-n> do_create_branch From 5753ef1a4eca7b4ce04d1e4ea2250442bba4dbb9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 25 Jan 2007 13:01:16 -0500 Subject: [PATCH 320/548] git-gui: Support 'Visualize All Branches' on Mac OS X. Now that recent versions of gitk (shipping with at least git 1.5.0-rc1 and later) actually accept command line revision specifiers without crashing on internal Tk errors we can offer the 'Visualize All Branches' menu item in the Repository menu on Mac OS X. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index ea4136c98b..639f380784 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3772,12 +3772,10 @@ menu .mbar.repository -label {Visualize Current Branch} \ -command {do_gitk {}} \ -font font_ui -if {![is_MacOSX]} { - .mbar.repository add command \ - -label {Visualize All Branches} \ - -command {do_gitk {--all}} \ - -font font_ui -} +.mbar.repository add command \ + -label {Visualize All Branches} \ + -command {do_gitk {--all}} \ + -font font_ui .mbar.repository add separator if {!$single_commit} { From 68567679a2d7cec04be1a62e09874189e46de4b6 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 25 Jan 2007 13:07:53 -0500 Subject: [PATCH 321/548] git-gui: Pad the database statistics dialog window. The stat frame was right on the edge of the window on Mac OS X, making the frame's border blend in with the window border. Not exactly the effect I had in mind. 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 639f380784..9dbe1156c5 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2950,7 +2950,7 @@ proc do_stats {} { label $w.stat.v_$name -text $value -anchor w -font font_ui grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5} } - pack $w.stat + pack $w.stat -pady 10 -padx 10 bind $w "grab $w; focus $w" bind $w [list destroy $w] From fb08baca3340099d149a88f6ede19a6870870c08 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 25 Jan 2007 16:50:15 -0500 Subject: [PATCH 322/548] git-gui: Prefer Tk's entry widget over a 1 line text field. I'm a fool and previously used a text widget configured with a height of 1 and special bindings to handle focus traversal rather than the already built (and properly behaved) entry widget. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 75 ++++++++++++++++++++++++------------------------------ 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 9dbe1156c5..811e20217d 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1865,8 +1865,9 @@ 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 - set newbranch [string trim [$w.desc.name_t get 0.0 end]] + set newbranch $create_branch_name if {$newbranch eq {} || $newbranch eq $repo_config(gui.newbranchtemplate)} { tk_messageBox \ @@ -1903,7 +1904,7 @@ proc do_create_branch_action {w} { switch -- $create_branch_revtype { head {set rev $create_branch_head} tracking {set rev $create_branch_trackinghead} - expression {set rev [string trim [$w.from.exp_t get 0.0 end]]} + expression {set rev $create_branch_revexp} } if {[catch {set cmt [exec git rev-parse --verify "${rev}^0"]}]} { tk_messageBox \ @@ -1958,6 +1959,7 @@ 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 set w .branch_editor toplevel $w @@ -1983,26 +1985,18 @@ proc do_create_branch {} { -text {Branch Description} \ -font font_ui label $w.desc.name_l -text {Name:} -font font_ui - text $w.desc.name_t \ + entry $w.desc.name_t \ -borderwidth 1 \ -relief sunken \ - -height 1 \ -width 40 \ - -font font_ui - $w.desc.name_t insert 0.0 $repo_config(gui.newbranchtemplate) - grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5} - bind $w.desc.name_t {focus [tk_focusPrev %W];break} - bind $w.desc.name_t {focus [tk_focusNext %W];break} - bind $w.desc.name_t "do_create_branch_action $w;break" - bind $w.desc.name_t { - if {{%K} ne {BackSpace} - && {%K} ne {Tab} - && {%K} ne {Escape} - && {%K} ne {Return}} { - if {%k <= 32} break - if {[string first %A {~^:?*[}] >= 0} break + -textvariable create_branch_name \ + -font font_ui \ + -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 @@ -2034,18 +2028,21 @@ proc do_create_branch {} { -value expression \ -variable create_branch_revtype \ -font font_ui - text $w.from.exp_t \ + entry $w.from.exp_t \ -borderwidth 1 \ -relief sunken \ - -height 1 \ -width 50 \ - -font font_ui + -textvariable create_branch_revexp \ + -font font_ui \ + -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} - bind $w.from.exp_t {focus [tk_focusPrev %W];break} - bind $w.from.exp_t {focus [tk_focusNext %W];break} - bind $w.from.exp_t "do_create_branch_action $w;break" - bind $w.from.exp_t break - bind $w.from.exp_t {set create_branch_revtype expression} grid columnconfigure $w.from 1 -weight 1 pack $w.from -anchor nw -fill x -pady 5 -padx 5 @@ -2062,8 +2059,14 @@ proc do_create_branch {} { 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; focus $w.desc.name_t" + 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" @@ -3329,10 +3332,7 @@ proc do_options {} { pack $w.buttons.restore -side left button $w.buttons.save -text Save \ -font font_ui \ - -command " - catch {eval \[bind \[focus -displayof $w\] \]} - do_save_config $w - " + -command [list do_save_config $w] pack $w.buttons.save -side right button $w.buttons.cancel -text {Cancel} \ -font font_ui \ @@ -3382,21 +3382,12 @@ proc do_options {} { t { frame $w.$f.$name label $w.$f.$name.l -text "$text:" -font font_ui - text $w.$f.$name.v \ + entry $w.$f.$name.v \ -borderwidth 1 \ -relief sunken \ - -height 1 \ -width 20 \ + -textvariable ${f}_config_new(gui.$name) \ -font font_ui - $w.$f.$name.v insert 0.0 [set ${f}_config_new(gui.$name)] - bind $w.$f.$name.v {focus [tk_focusPrev %W];break} - bind $w.$f.$name.v {focus [tk_focusNext %W];break} - bind $w.$f.$name.v break - bind $w.$f.$name.v "$w.$f.$name.v tag add sel 0.0 end" - bind $w.$f.$name.v " - set ${f}_config_new(gui.$name) \ - \[string trim \[$w.$f.$name.v get 0.0 end\]\] - " pack $w.$f.$name.l -side left -anchor w pack $w.$f.$name.v -side left -anchor w \ -fill x -expand 1 \ From 3f7fd924a92c07c0fd4e8bd80b941171097db7f0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 25 Jan 2007 17:16:22 -0500 Subject: [PATCH 323/548] git-gui: Remove Pull menu and cleanup Branch/Fetch/Push menus. The Pull menu as it stands right now is a really horrible idea. Most users will have too many branches show up in this menu, and what with the new globbing syntax for fetch entries we were offering up possible merging that just isn't really valid. So this menu is dead and will be rewritten to support better merge capabilities. The Branch menu shouldn't include a separator entry if there are no branches, it just looks too damn weird. This can happen in an initial repository before any branches have been created and before the first commit. The Fetch and Push menus should just be organized around their own menus rather than being given the menu to populate. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 54 +++++++++--------------------------------------------- 1 file changed, 9 insertions(+), 45 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 811e20217d..7792eb4d7f 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1822,7 +1822,9 @@ proc populate_branch_menu {} { } } - $m add separator + if {$all_heads ne {}} { + $m add separator + } foreach b $all_heads { $m add radiobutton \ -label $b \ @@ -2429,9 +2431,10 @@ proc load_all_remotes {} { set all_remotes [lsort -unique $all_remotes] } -proc populate_fetch_menu {m} { +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)}]} { @@ -2460,9 +2463,10 @@ proc populate_fetch_menu {m} { } } -proc populate_push_menu {m} { +proc populate_push_menu {} { global all_remotes repo_config + set m .mbar.push foreach r $all_remotes { set enable 0 if {![catch {set a $repo_config(remote.$r.url)}]} { @@ -2491,43 +2495,6 @@ proc populate_push_menu {m} { } } -proc populate_pull_menu {m} { - global repo_config all_remotes disable_on_lock - - foreach remote $all_remotes { - set rb_list [list] - if {[array get repo_config remote.$remote.url] ne {}} { - if {[array get repo_config remote.$remote.fetch] ne {}} { - foreach line $repo_config(remote.$remote.fetch) { - if {[regexp {^([^:]+):} $line line rb]} { - lappend rb_list $rb - } - } - } - } else { - catch { - set fd [open [gitdir remotes $remote] r] - while {[gets $fd line] >= 0} { - if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} { - lappend rb_list $rb - } - } - close $fd - } - } - - foreach rb $rb_list { - regsub ^refs/heads/ $rb {} rb_short - $m add command \ - -label "Branch $rb_short from $remote..." \ - -command [list pull_remote $remote $rb] \ - -font font_ui - lappend disable_on_lock \ - [list $m entryconf [$m index last] -state] - } - } -} - ###################################################################### ## ## icons @@ -3751,7 +3718,6 @@ if {!$single_commit} { .mbar add cascade -label Commit -menu .mbar.commit if {!$single_commit} { .mbar add cascade -label Fetch -menu .mbar.fetch - .mbar add cascade -label Pull -menu .mbar.pull .mbar add cascade -label Push -menu .mbar.push } . configure -menu .mbar @@ -3929,7 +3895,6 @@ lappend disable_on_lock \ # if {!$single_commit} { menu .mbar.fetch - menu .mbar.pull menu .mbar.push } @@ -4565,9 +4530,8 @@ if {!$single_commit} { load_all_heads populate_branch_menu - populate_fetch_menu .mbar.fetch - populate_pull_menu .mbar.pull - populate_push_menu .mbar.push + populate_fetch_menu + populate_push_menu } # -- Only suggest a gc run if we are going to stay running. From d070c4cb17e807c157aaeb24d9b80a1112334e57 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 25 Jan 2007 17:07:03 -0500 Subject: [PATCH 324/548] git-gui: Don't switch branches if changing to the current branch. Its pointless to switch to the current branch, so don't do it. We are already on it and the current index and working directory should just be left alone. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index 7792eb4d7f..faae6ce7c5 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2271,6 +2271,13 @@ The rescan will be automatically started now. 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 { From 5f8b70b1dc3d149782564e04d5d55565a5157d93 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 25 Jan 2007 21:33:06 -0500 Subject: [PATCH 325/548] git-gui: Maintain the same file list for diff during refresh. I just noticed that a file was always jumping to compare against HEAD and the index during a refresh, even if the diff viewer was comparing the index against the working directory prior to the refresh. The bug turned out to be caused by a foreach loop going through all file list names searching for the path. Since $ui_index was the first one searched and the file was contained in that file list the loop broke out, leaving $w set to $ui_index when it had been set by the caller to $ui_workdir. Silly bug caused by using a parameter as a loop index. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index faae6ce7c5..b0a195ce8a 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -607,16 +607,13 @@ proc show_diff {path w {lno {}}} { if {$diff_active || ![lock_index read]} return clear_diff - if {$w eq {} || $lno == {}} { - foreach w [array names file_lists] { - set lno [lsearch -sorted $file_lists($w) $path] - if {$lno >= 0} { - incr lno - break - } + if {$lno == {}} { + set lno [lsearch -sorted $file_lists($w) $path] + if {$lno >= 0} { + incr lno } } - if {$w ne {} && $lno >= 1} { + if {$lno >= 1} { $w tag add in_diff $lno.0 [expr {$lno + 1}].0 } From 156b29211ae4f8143f143fd6cce6e93b60c51f43 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 25 Jan 2007 22:38:59 -0500 Subject: [PATCH 326/548] git-gui: Always use lsearch -exact, to prevent globbing. Anytime we are using lsearch we are doing [lsearch -sorted] and we are applying it to file paths (or file path like things). Its valid for these to contain special glob characters, but when that happens we do not want globbing to occur. Instead we really need exact match semantics. Always supplying -exact to lsearch will ensure that is the case. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index b0a195ce8a..b203c56a98 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -568,7 +568,7 @@ proc reshow_diff {} { if {$p eq {} || $current_diff_side eq {} || [catch {set s $file_states($p)}] - || [lsearch -sorted $file_lists($current_diff_side) $p] == -1} { + || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} { clear_diff } else { show_diff $p $current_diff_side @@ -608,7 +608,7 @@ proc show_diff {path w {lno {}}} { clear_diff if {$lno == {}} { - set lno [lsearch -sorted $file_lists($w) $path] + set lno [lsearch -sorted -exact $file_lists($w) $path] if {$lno >= 0} { incr lno } @@ -1427,7 +1427,7 @@ proc display_file_helper {w path icon_name old_m new_m} { global file_lists if {$new_m eq {_}} { - set lno [lsearch -sorted $file_lists($w) $path] + set lno [lsearch -sorted -exact $file_lists($w) $path] if {$lno >= 0} { set file_lists($w) [lreplace $file_lists($w) $lno $lno] incr lno @@ -1438,7 +1438,7 @@ proc display_file_helper {w path icon_name old_m new_m} { } elseif {$old_m eq {_} && $new_m ne {_}} { lappend file_lists($w) $path set file_lists($w) [lsort -unique $file_lists($w)] - set lno [lsearch -sorted $file_lists($w) $path] + set lno [lsearch -sorted -exact $file_lists($w) $path] incr lno $w conf -state normal $w image create $lno.0 \ @@ -2142,7 +2142,7 @@ Delete the selected branches?} if {[catch {exec git update-ref -d "refs/heads/$b" $o} err]} { append failed " - $b: $err\n" } else { - set x [lsearch -sorted $all_heads $b] + set x [lsearch -sorted -exact $all_heads $b] if {$x >= 0} { set all_heads [lreplace $all_heads $x $x] } From 1d6a9787526372609d3b105659e9cb5f3718df4a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 25 Jan 2007 23:50:27 -0500 Subject: [PATCH 327/548] git-gui: Added arbitrary branch pushing support. Because its common for some users to push topic branches to a remote repository for review and merging by other parties, users need an easy way to push one or more branches to a remote repository without needing to edit their .git/config file anytime their set of active branches changes. We now provide a basic 'Push...' menu action in the Push menu which opens a dialog allowing the user to select from their set of local branches (refs/heads, minus tracking branches). The user can designate which repository to send the changes to by selecting from an already configured remote or by entering any valid Git URL. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index b203c56a98..de4cec20aa 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2471,6 +2471,7 @@ 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)}]} { @@ -2491,14 +2492,167 @@ proc populate_push_menu {} { } if {$enable} { + if {!$fast_count} { + $m add separator + } $m add command \ -label "Push to $r..." \ -command [list push_to $r] \ -font font_ui + 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 + 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 \ + -font font_ui \ + -command [list start_push_anywhere_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 + listbox $w.source.l \ + -height 10 \ + -width 50 \ + -selectmode extended \ + -font font_ui + foreach h $all_heads { + $w.source.l insert end $h + if {$h eq $current_branch} { + $w.source.l select set end + } + } + pack $w.source.l -fill both -pady 5 -padx 5 + pack $w.source -fill both -pady 5 -padx 5 + + labelframe $w.dest \ + -text {Destination Repository} \ + -font font_ui + if {$all_remotes ne {}} { + radiobutton $w.dest.remote_r \ + -text {Remote:} \ + -value remote \ + -variable push_urltype \ + -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 + } 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 \ + -font font_ui + 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} + 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} \ + -font font_ui + checkbutton $w.options.thin \ + -text {Use thin pack (for slow network connections)} \ + -variable push_thin \ + -font font_ui + grid $w.options.thin -columnspan 2 -sticky w + checkbutton $w.options.tags \ + -text {Include tags} \ + -variable push_tags \ + -font font_ui + 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" + bind $w "destroy $w" + wm title $w "[appname] ([reponame]): Push" + tkwait window $w +} + ###################################################################### ## ## icons @@ -3900,6 +4054,10 @@ lappend disable_on_lock \ if {!$single_commit} { menu .mbar.fetch menu .mbar.push + + .mbar.push add command -label {Push...} \ + -command do_push_anywhere \ + -font font_ui } if {[is_MacOSX]} { From 86a2af608700896a004be50c939a76a539bdba4d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 26 Jan 2007 00:47:44 -0500 Subject: [PATCH 328/548] git-gui: Remove no longer used pull from remote code. Because we aren't going to support single click pulling of changes from an existing remote anytime in the near future, I'm moving the code which used to perform that action. Hopefully we'll be able to do something like it in the near-future, but also support local branches just as easily. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 73 ++++-------------------------------------------------- 1 file changed, 5 insertions(+), 68 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index de4cec20aa..c42673c8e3 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1269,83 +1269,20 @@ proc commit_committree {fd_wt curHEAD msg} { ###################################################################### ## -## fetch pull push +## fetch push proc fetch_from {remote} { - set w [new_console "fetch $remote" \ + set w [new_console \ + "fetch $remote" \ "Fetching new changes from $remote"] set cmd [list git fetch] lappend cmd $remote console_exec $w $cmd } -proc pull_remote {remote branch} { - global HEAD commit_type file_states repo_config - - if {![lock_index update]} return - - # -- 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 pull operation can be started. - -The rescan will be automatically started now. -} - unlock_index - rescan {set ui_status_value {Ready.}} - return - } - - # -- No differences should exist before a pull. - # - if {[array size file_states] != 0} { - error_popup {Uncommitted but modified files are present. - -You should not perform a pull with unmodified -files in your working directory as Git will be -unable to recover from an incorrect merge. - -You should commit or revert all changes before -starting a pull operation. -} - unlock_index - return - } - - set w [new_console "pull $remote $branch" \ - "Pulling new changes from branch $branch in $remote"] - set cmd [list git pull] - if {$repo_config(gui.pullsummary) eq {false}} { - lappend cmd --no-summary - } - lappend cmd $remote - lappend cmd $branch - console_exec $w $cmd [list post_pull_remote $remote $branch] -} - -proc post_pull_remote {remote branch success} { - global HEAD PARENT MERGE_HEAD commit_type selected_commit_type - global ui_status_value - - unlock_index - if {$success} { - repository_state commit_type HEAD MERGE_HEAD - set PARENT $HEAD - set selected_commit_type new - set ui_status_value "Pulling $branch from $remote complete." - } else { - rescan [list set ui_status_value \ - "Conflicts detected while pulling $branch from $remote."] - } -} - proc push_to {remote} { - set w [new_console "push $remote" \ + set w [new_console \ + "push $remote" \ "Pushing changes to $remote"] set cmd [list git push] lappend cmd $remote From b972ea59e422588963bda7be8e04c59728accadf Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 26 Jan 2007 00:49:17 -0500 Subject: [PATCH 329/548] git-gui: Always use -v option to push. Right now `git-push -v` is actually not that verbose; it merely adds the URL it is pushing to. This can be informative if you are pushing to a configured remote, as you may not actually remember what URL that remote is connected to. That detail can be important if the push fails and you attempt to communicate the errors to a 3rd party to help you resolve the issue. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/git-gui.sh b/git-gui.sh index c42673c8e3..48e1f821de 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1285,6 +1285,7 @@ proc push_to {remote} { "push $remote" \ "Pushing changes to $remote"] set cmd [list git push] + lappend cmd -v lappend cmd $remote console_exec $w $cmd } From 6c3d1481bab96659a80771194d935cb6db02300e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 26 Jan 2007 01:29:00 -0500 Subject: [PATCH 330/548] git-gui: Refactor console success/failure handling. Because I want to be able to run multiple output-producing commands in a single 'console' window within git-gui I'm refactoring the console handling routines to require the "after" argument of console_exec. This should specify a procedure to execute which will receive two args, the first is the console window handle and the second is the status of the last command (0 on failure, 1 on success). A new procedure console_done can be passed to the last console_exec command to forward perform all cleanup and enable the Close button. Its status argument is used to update the final status bar on the bottom of the console window. This isn't any real logic changing, and no new functionality is in this patch. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 48e1f821de..d264558076 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1277,7 +1277,7 @@ proc fetch_from {remote} { "Fetching new changes from $remote"] set cmd [list git fetch] lappend cmd $remote - console_exec $w $cmd + console_exec $w $cmd console_done } proc push_to {remote} { @@ -1287,7 +1287,7 @@ proc push_to {remote} { set cmd [list git push] lappend cmd -v lappend cmd $remote - console_exec $w $cmd + console_exec $w $cmd console_done } ###################################################################### @@ -2476,7 +2476,7 @@ proc start_push_anywhere_action {w} { } set cons [new_console "push $r_url" "Pushing $cnt $unit to $r_url"] - console_exec $cons $cmd + console_exec $cons $cmd console_done destroy $w } @@ -2854,7 +2854,7 @@ proc console_init {w} { return $w } -proc console_exec {w cmd {after {}}} { +proc console_exec {w cmd after} { # -- Windows tosses the enviroment when we exec our child. # But most users need that so we have to relogin. :-( # @@ -2873,7 +2873,7 @@ proc console_exec {w cmd {after {}}} { } proc console_read {w fd after} { - global console_cr console_data + global console_cr set buf [read $fd] if {$buf ne {}} { @@ -2907,25 +2907,36 @@ proc console_read {w fd after} { fconfigure $fd -blocking 1 if {[eof $fd]} { if {[catch {close $fd}]} { - if {![winfo exists $w]} {console_init $w} - $w.m.s conf -background red -text {Error: Command Failed} - $w.ok conf -state normal set ok 0 - } elseif {[winfo exists $w]} { - $w.m.s conf -background green -text {Success} - $w.ok conf -state normal + } else { set ok 1 } - array unset console_cr $w - array unset console_data $w - if {$after ne {}} { - uplevel #0 $after $ok - } + uplevel #0 $after $w $ok return } fconfigure $fd -blocking 0 } +proc console_done {w ok} { + global console_cr console_data + + if {$ok} { + if {[winfo exists $w]} { + $w.m.s conf -background green -text {Success} + $w.ok conf -state normal + } + } else { + if {![winfo exists $w]} { + console_init $w + } + $w.m.s conf -background red -text {Error: Command Failed} + $w.ok conf -state normal + } + + array unset console_cr $w + array unset console_data $w +} + ###################################################################### ## ## ui commands @@ -3027,7 +3038,7 @@ proc do_stats {} { proc do_gc {} { set w [new_console {gc} {Compressing the object database}] - console_exec $w {git gc} + console_exec $w {git gc} console_done } proc do_fsck_objects {} { @@ -3037,7 +3048,7 @@ proc do_fsck_objects {} { lappend cmd --full lappend cmd --cache lappend cmd --strict - console_exec $w $cmd + console_exec $w $cmd console_done } set is_quitting 0 From bc7452f5e7c0c472bc580e2e00d84f31d1af50ce Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 26 Jan 2007 02:02:09 -0500 Subject: [PATCH 331/548] git-gui: Use builtin version of 'git gc'. Technically the new git-gc command is strictly Porcelain; its invoking multiple plumbing commands to do its work. Since git-gui tries to not rely on Porclain we shouldn't be invoking git-gc directly, instead we should perform its tasks on our own. To make this easy I've created console_chain, which takes a list of tasks to perform and runs them all in the same console window. If any individual task fails then the chain stops running and the window shows a failure bar. Only once all tasks have been completed will it invoke console_done with a successful status. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index d264558076..0982da5690 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2917,9 +2917,45 @@ proc console_read {w fd after} { fconfigure $fd -blocking 0 } -proc console_done {w ok} { +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} @@ -3038,7 +3074,12 @@ proc do_stats {} { proc do_gc {} { set w [new_console {gc} {Compressing the object database}] - console_exec $w {git gc} console_done + 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 {} { From e4834837a8a02708efb6a7f8a248087a8cd49ab3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 26 Jan 2007 03:33:56 -0500 Subject: [PATCH 332/548] git-gui: Implement local merge operations. To allow users to merge local heads and tracking branches we now offer a dialog which lets the user select 1-15 branches and merge them using the stock `git merge` Grand Unified Merge Driver. Originally I had wanted to implement this merge internally within git-gui as I consider GUMD to be mostly Porcelain-ish, but the truth is it does its job exceedingly well and its a relatively complex chunk of code. I'll probably circle back later and try to remove the invocation of GUMD from git-gui, but right now it lets me get the job done faster. Users cannot start a merge if they are currently in the middle of one, or if they are amending a commit. Trying to do either is just stupid and should be stopped as early as possible. I've also made it simple for users to startup a gitk session prior to a merge by offering a Visualize button which runs `gitk $revs --not HEAD`, where $revs is the list of branches currently selected in the merge dialog. This makes it quite simple to find out what the damage will be to the current branch if you were to carry out the currently proposed merge. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 213 insertions(+), 12 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 0982da5690..c25cc4f145 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -983,8 +983,8 @@ proc commit_tree {} { global HEAD commit_type file_states ui_comm repo_config global ui_status_value pch_error - if {![lock_index update]} return if {[committer_ident] eq {}} return + if {![lock_index update]} return # -- Our in memory state should match the repository. # @@ -2591,6 +2591,201 @@ proc do_push_anywhere {} { 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] { + 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 + } + } + } + + 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 + + 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 + } 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 cons [new_console "Merge" "Merging [join $names {, }]"] + console_exec $cons $cmd finish_merge + bind $w {} + destroy $w +} + +proc finish_merge {w ok} { + console_done $w $ok + if {$ok} { + set msg {Merge completed successfully.} + } else { + 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 \ + -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 + listbox $w.source.l \ + -height 10 \ + -width 25 \ + -selectmode extended \ + -yscrollcommand [list $w.source.sby set] \ + -font font_ui + 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) %(refname)} + lappend cmd refs/heads + lappend cmd refs/remotes + 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 1] + } + 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)/ $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 +} + ###################################################################### ## ## icons @@ -3865,6 +4060,7 @@ if {!$single_commit} { } .mbar add cascade -label Commit -menu .mbar.commit if {!$single_commit} { + .mbar add cascade -label Merge -menu .mbar.merge .mbar add cascade -label Fetch -menu .mbar.fetch .mbar add cascade -label Push -menu .mbar.push } @@ -4039,17 +4235,6 @@ lappend disable_on_lock \ lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] -# -- Transport menus -# -if {!$single_commit} { - menu .mbar.fetch - menu .mbar.push - - .mbar.push add command -label {Push...} \ - -command do_push_anywhere \ - -font font_ui -} - if {[is_MacOSX]} { # -- Apple Menu (Mac OS X only) # @@ -4130,6 +4315,22 @@ pack .branch.l1 -side left pack .branch.cb -side left -fill x pack .branch -side top -fill x +if {!$single_commit} { + menu .mbar.merge + .mbar.merge add command -label {Local Merge...} \ + -command do_local_merge \ + -font font_ui + lappend disable_on_lock \ + [list .mbar.merge entryconf [.mbar.merge index last] -state] + + menu .mbar.fetch + + menu .mbar.push + .mbar.push add command -label {Push...} \ + -command do_push_anywhere \ + -font font_ui +} + # -- Main Window Layout # panedwindow .vpane -orient vertical From ce9735dfbd77ab7cbcb97ba8749b2f6eaa7f2527 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 26 Jan 2007 03:54:05 -0500 Subject: [PATCH 333/548] git-gui: Let users abort with `reset --hard` type logic. If you get into the middle of a merge that turns out to be horrible and just not something you want to do right now, odds are you need to run `git reset --hard` to recover your working directory to a pre-merge state. We now offer Merge->Abort Merge for exactly this purpose, however its also useful to thow away a non-merge, as its basically the same logic as `git reset --hard`. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index c25cc4f145..0c2dbbebe1 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2786,6 +2786,61 @@ proc do_local_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.}} + } +} + ###################################################################### ## ## icons @@ -4322,6 +4377,12 @@ if {!$single_commit} { -font font_ui 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 + lappend disable_on_lock \ + [list .mbar.merge entryconf [.mbar.merge index last] -state] + menu .mbar.fetch From ee3cfb595415e2f076d5b90efba83088e5af961c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 26 Jan 2007 03:58:56 -0500 Subject: [PATCH 334/548] git-gui: Update status bar during a merge. I got slightly confused when I did two merges in a row, as the status bar said "merge completed successfully" while the second merge was still running. Now we show what branches are actively being merged. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 0c2dbbebe1..6675d3018b 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2658,7 +2658,7 @@ proc visualize_local_merge {w} { } proc start_local_merge_action {w} { - global HEAD + global HEAD ui_status_value current_branch set cmd [list git merge] set names {} @@ -2694,7 +2694,9 @@ than 15 branches, merge the branches in batches. return } - set cons [new_console "Merge" "Merging [join $names {, }]"] + set msg "Merging $current_branch, [join $names {, }]" + set ui_status_value "$msg..." + set cons [new_console "Merge" $msg] console_exec $cons $cmd finish_merge bind $w {} destroy $w From dff7e88febf85b1b8b1b789cd7c99434b70fb19b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 26 Jan 2007 04:07:34 -0500 Subject: [PATCH 335/548] git-gui: Don't allow users to commit a bad octopus merge. If an octopus merge goes horribly wrong git-merge will leave the working directory and index dirty, but will not leave behind a MERGE_HEAD file for a later commit. Consequently we won't know its a merge commit and instead would let the user resolve the conflicts and commit a single-parent commit, which is wrong. So now if an octopus merge fails we notify the user that the merge did not work, tell them we will reset the working directory, and suggest that they merge one branch at a time. This prevents the user from committing a bad octopus merge. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 6675d3018b..31ef0e60ac 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -187,13 +187,13 @@ proc warn_popup {msg} { eval $cmd } -proc info_popup {msg} { +proc info_popup {msg {parent .}} { set title [appname] if {[reponame] ne {}} { append title " ([reponame])" } tk_messageBox \ - -parent . \ + -parent $parent \ -icon info \ -type ok \ -title $title \ @@ -2697,16 +2697,36 @@ than 15 branches, merge the branches in batches. set msg "Merging $current_branch, [join $names {, }]" set ui_status_value "$msg..." set cons [new_console "Merge" $msg] - console_exec $cons $cmd finish_merge + console_exec $cons $cmd [list finish_merge $revcnt] bind $w {} destroy $w } -proc finish_merge {w ok} { +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 From 5f39dbf64f5e57c6ab7b20ba8c397df1074bc30c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 26 Jan 2007 04:11:10 -0500 Subject: [PATCH 336/548] git-gui: Don't allow merges in the middle of other things. If the user is in the middle of a commit they have files which are modified. These may conflict with any merge that they may want to perform, which would cause problems if the user wants to abort a bad merge as we wouldn't have a checkpoint to roll back onto. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index 31ef0e60ac..ae883f90bd 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2629,6 +2629,9 @@ The rescan will be automatically started now. 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. @@ -2637,6 +2640,18 @@ 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 From 729a6f60ddd0b3317c581a01687a32e585fa0c3e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 26 Jan 2007 04:16:39 -0500 Subject: [PATCH 337/548] git-gui: Always offer scrollbars for branch lists. Anytime we use a listbox to show branch names its possible for the listbox to exceed 10 entries (actually its probably very common). So we should always offer a scrollbar for the Y axis on these listboxes. I just forgot to add it when I defined them. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index ae883f90bd..68fd7ebf18 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2129,16 +2129,19 @@ proc do_delete_branch {} { -font font_ui listbox $w.list.l \ -height 10 \ - -width 50 \ + -width 70 \ -selectmode extended \ + -yscrollcommand [list $w.list.sby set] \ -font font_ui foreach h $all_heads { if {$h ne $current_branch} { $w.list.l insert end $h } } - pack $w.list.l -fill both -pady 5 -padx 5 - pack $w.list -fill both -pady 5 -padx 5 + 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} \ @@ -2510,8 +2513,9 @@ proc do_push_anywhere {} { -font font_ui listbox $w.source.l \ -height 10 \ - -width 50 \ + -width 70 \ -selectmode extended \ + -yscrollcommand [list $w.source.sby set] \ -font font_ui foreach h $all_heads { $w.source.l insert end $h @@ -2519,8 +2523,10 @@ proc do_push_anywhere {} { $w.source.l select set end } } - pack $w.source.l -fill both -pady 5 -padx 5 - pack $w.source -fill both -pady 5 -padx 5 + 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} \ @@ -2782,7 +2788,7 @@ proc do_local_merge {} { -font font_ui listbox $w.source.l \ -height 10 \ - -width 25 \ + -width 70 \ -selectmode extended \ -yscrollcommand [list $w.source.sby set] \ -font font_ui From c539449b2df0381de1d34c70b093b68370e89a4e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 26 Jan 2007 04:43:43 -0500 Subject: [PATCH 338/548] git-gui: Support merge.summary, merge.verbosity. Changed our private merge summary config option to be the same as the merge.summary option supported by core Git. This means setting the "Show Merge Summary" flag in git-gui will have the same effect on the command line. In the same vein I've also added merge.verbosity to the gui options, allowing the user to adjust the verbosity level of the recursive merge strategy. I happen to like level 1 and suggest that other users use that, but level 2 is the core Git default right now so we'll use the same default in git-gui. Unfortunately it appears as though core Git has broken support for the merge.summary option, even though its still in the documentation For the time being we should pass along --no-summary to git-merge if merge.summary is false. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 77 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 68fd7ebf18..1ba7f5a293 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -60,6 +60,17 @@ proc is_many_config {name} { } } +proc is_config_true {name} { + global repo_config + if {[catch {set v $repo_config($name)}]} { + return 0 + } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} { + return 1 + } else { + return 0 + } +} + proc load_config {include_global} { global repo_config global_config default_config @@ -2682,6 +2693,10 @@ proc start_local_merge_action {w} { global HEAD ui_status_value current_branch set cmd [list git merge] + if {![is_config_true merge.summary]} { + lappend cmd --no-summary + } + set names {} set revcnt 0 foreach i [$w.source.l curselection] { @@ -3755,52 +3770,59 @@ proc do_options {} { 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 { - {b pullsummary {Show Pull Summary}} - {b trustmtime {Trust File Modification Timestamps}} - {i diffcontext {Number of Diff Context Lines}} - {t newbranchtemplate {New Branch Name Template}} + {b merge.summary {Show Merge Summary}} + {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 $type { + switch -glob -- $type { b { - checkbutton $w.$f.$name -text $text \ - -variable ${f}_config_new(gui.$name) \ + checkbutton $w.$f.$optid -text $text \ + -variable ${f}_config_new($name) \ -onvalue true \ -offvalue false \ -font font_ui - pack $w.$f.$name -side top -anchor w + pack $w.$f.$optid -side top -anchor w } - i { - frame $w.$f.$name - label $w.$f.$name.l -text "$text:" -font font_ui - pack $w.$f.$name.l -side left -anchor w -fill x - spinbox $w.$f.$name.v \ - -textvariable ${f}_config_new(gui.$name) \ - -from 1 -to 99 -increment 1 \ - -width 3 \ + i-* { + regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max + frame $w.$f.$optid + label $w.$f.$optid.l -text "$text:" -font font_ui + 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 - bind $w.$f.$name.v {%W selection range 0 end} - pack $w.$f.$name.v -side right -anchor e -padx 5 - pack $w.$f.$name -side top -anchor w -fill x + 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.$name - label $w.$f.$name.l -text "$text:" -font font_ui - entry $w.$f.$name.v \ + frame $w.$f.$optid + label $w.$f.$optid.l -text "$text:" -font font_ui + entry $w.$f.$optid.v \ -borderwidth 1 \ -relief sunken \ -width 20 \ - -textvariable ${f}_config_new(gui.$name) \ + -textvariable ${f}_config_new($name) \ -font font_ui - pack $w.$f.$name.l -side left -anchor w - pack $w.$f.$name.v -side left -anchor w \ + 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.$name -side top -anchor w -fill x + pack $w.$f.$optid -side top -anchor w -fill x } } } @@ -4131,8 +4153,9 @@ proc apply_config {} { } } +set default_config(merge.summary) true +set default_config(merge.verbosity) 2 set default_config(gui.trustmtime) false -set default_config(gui.pullsummary) true set default_config(gui.diffcontext) 5 set default_config(gui.newbranchtemplate) {} set default_config(gui.fontui) [font configure font_ui] From 6b90d39186dbfabfff94692d33d0a6e94f02016c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 27 Jan 2007 02:31:01 -0500 Subject: [PATCH 339/548] git-gui: Reword meaning of merge.summary. OK, its official, I'm not reading documentation as well as I should be. Core Git's merge.summary configuration option is used to control the generation of the text appearing within the merge commit itself. It is not (and never has been) used to default the --no-summary command line option, which disables the diffstat at the end of the merge. I completely blame Git for naming two unrelated options almost the exact same thing. But its my own fault for allowing git-gui to confuse the two. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 1ba7f5a293..be92fa9626 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2693,10 +2693,6 @@ proc start_local_merge_action {w} { global HEAD ui_status_value current_branch set cmd [list git merge] - if {![is_config_true merge.summary]} { - lappend cmd --no-summary - } - set names {} set revcnt 0 foreach i [$w.source.l curselection] { @@ -3772,7 +3768,7 @@ proc do_options {} { set optid 0 foreach option { - {b merge.summary {Show Merge Summary}} + {b merge.summary {Summarize Merge Commits}} {i-1..5 merge.verbosity {Merge Verbosity}} {b gui.trustmtime {Trust File Modification Timestamps}} @@ -4153,7 +4149,7 @@ proc apply_config {} { } } -set default_config(merge.summary) true +set default_config(merge.summary) false set default_config(merge.verbosity) 2 set default_config(gui.trustmtime) false set default_config(gui.diffcontext) 5 From 273984fc4f26ab92aa8ebf5b033b9cdb56322b71 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 28 Jan 2007 20:00:36 -0500 Subject: [PATCH 340/548] git-gui: Offer quick access to the HTML formatted documentation. Users may want to be able to read Git documentation, even if they are not command line users. There are many important concepts and terms covered within the standard Git documentation which would be useful to even non command line using people. We now try to offer an 'Online Documentation' menu option within the Help menu. First we try to guess to see what browser the user has setup. We default to instaweb.browser, if set, as this is probably accurate for the user's configuration. If not then we try to guess based on the operating system and the available browsers for each. We prefer documentation which is installed parallel to Git's own executables, e.g. `git --exec-path`/../Documentation/index.html, as that is how I typically install the HTML docs. If those are not found then we open the documentation published on kernel.org. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 48 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index be92fa9626..c168826ecc 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -4401,17 +4401,57 @@ if {[is_MacOSX]} { lappend disable_on_lock \ [list .mbar.tools entryconf [.mbar.tools index last] -state] } +} - # -- Help Menu - # - .mbar add cascade -label Help -menu .mbar.help - menu .mbar.help +# -- Help Menu +# +.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 } +set browser {} +catch {set browser $repo_config(instaweb.browser)} +set doc_path [file dirname [exec git --exec-path]] +set doc_path [file join $doc_path Documentation index.html] + +if {[is_Windows]} { + set doc_path [exec cygpath --windows $doc_path] +} + +if {$browser eq {}} { + if {[is_MacOSX]} { + set browser open + } elseif {[is_Windows]} { + set program_files [file dirname [exec cygpath --windir]] + set program_files [file join $program_files {Program Files}] + set firefox [file join $program_files {Mozilla Firefox} firefox.exe] + set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE] + if {[file exists $firefox]} { + set browser $firefox + } elseif {[file exists $ie]} { + set browser $ie + } + unset program_files firefox ie + } +} + +if {[file isfile $doc_path]} { + set doc_url "file:$doc_path" +} else { + set doc_url {http://www.kernel.org/pub/software/scm/git/docs/} +} + +if {$browser ne {}} { + .mbar.help add command -label {Online Documentation} \ + -command [list exec $browser $doc_url &] \ + -font font_ui +} +unset browser doc_path doc_url # -- Branch Control # From 20ddfcaa7e7f91816f1195a2dbb1b5268fd71e3d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 28 Jan 2007 20:58:47 -0500 Subject: [PATCH 341/548] git-gui: Test for Cygwin differently than from Windows. Running on Cygwin is different than if we were running through MinGW. In the Cygwin case we have cygpath available to us, we need to perform UNIX<->Windows path translation sometimes, and we need to perform odd things like spawning our own login shells to perform network operations. But in the MinGW case these don't occur. Git knows native Windows file paths, and login shells may not even exist. Now git-gui will avoid running cygpath unless it knows its on Cygwin. It also uses a different shortcut type when Cygwin is not present, and it avoids invoking /bin/sh to execute hooks if Cygwin is not present. This latter part probably needs more testing in the MinGW case. This change also improves how we start gitk. If the user is on any type of Windows system its known that gitk won't start right if ~/.gitk exists. So we delete it before starting if we are running on any type of Windows operating system. We always use the same wish executable which launched git-gui to start gitk; this way on Windows we don't have to jump back to /bin/sh just to go into the first wish found in the user's PATH. This should help on MinGW when we probably don't want to spawn a shell just to start gitk. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 138 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 104 insertions(+), 34 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index c168826ecc..46e019becd 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -26,7 +26,9 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA} set _appname [lindex [file split $argv0] end] set _gitdir {} +set _gitexec {} set _reponame {} +set _iscygwin {} proc appname {} { global _appname @@ -41,11 +43,56 @@ proc gitdir {args} { return [eval [concat [list file join $_gitdir] $args]] } +proc gitexec {args} { + global _gitexec + if {$_gitexec eq {}} { + if {[catch {set _gitexec [exec git --exec-path]} err]} { + error "Git not installed?\n\n$err" + } + } + if {$args eq {}} { + return $_gitexec + } + return [eval [concat [list file join $_gitexec] $args]] +} + proc reponame {} { global _reponame return $_reponame } +proc is_MacOSX {} { + global tcl_platform tk_library + if {[tk windowingsystem] eq {aqua}} { + return 1 + } + return 0 +} + +proc is_Windows {} { + global tcl_platform + if {$tcl_platform(platform) eq {windows}} { + return 1 + } + return 0 +} + +proc is_Cygwin {} { + global tcl_platform _iscygwin + if {$_iscygwin eq {}} { + if {$tcl_platform(platform) eq {windows}} { + if {[catch {set p [exec cygpath --windir]} err]} { + set _iscygwin 0 + } else { + set _iscygwin 1 + } + } else { + set _iscygwin 0 + } + } + return $_iscygwin +} + ###################################################################### ## ## config @@ -234,6 +281,9 @@ if { [catch {set _gitdir $env(GIT_DIR)}] error_popup "Cannot find the git directory:\n\n$err" exit 1 } +if {![file isdirectory $_gitdir] && [is_Cygwin]} { + catch {set _gitdir [exec cygpath --unix $_gitdir]} +} if {![file isdirectory $_gitdir]} { catch {wm withdraw .} error_popup "Git directory not found:\n\n$_gitdir" @@ -241,7 +291,7 @@ if {![file isdirectory $_gitdir]} { } if {[lindex [file split $_gitdir] end] ne {.git}} { catch {wm withdraw .} - error_popup "Cannot use funny .git directory:\n\n$gitdir" + error_popup "Cannot use funny .git directory:\n\n$_gitdir" exit 1 } if {[catch {cd [file dirname $_gitdir]} err]} { @@ -1076,7 +1126,7 @@ A good commit message has the following format: # On Cygwin [file executable] might lie so we need to ask # the shell if the hook is executable. Yes that's annoying. # - if {[is_Windows] && [file isfile $pchook]} { + if {[is_Cygwin] && [file isfile $pchook]} { set pchook [list sh -c [concat \ "if test -x \"$pchook\";" \ "then exec \"$pchook\" 2>&1;" \ @@ -1215,7 +1265,7 @@ proc commit_committree {fd_wt curHEAD msg} { # -- Run the post-commit hook. # set pchook [gitdir hooks post-commit] - if {[is_Windows] && [file isfile $pchook]} { + if {[is_Cygwin] && [file isfile $pchook]} { set pchook [list sh -c [concat \ "if test -x \"$pchook\";" \ "then exec \"$pchook\";" \ @@ -3020,22 +3070,6 @@ unset i ## ## util -proc is_MacOSX {} { - global tcl_platform tk_library - if {[tk windowingsystem] eq {aqua}} { - return 1 - } - return 0 -} - -proc is_Windows {} { - global tcl_platform - if {$tcl_platform(platform) eq {windows}} { - return 1 - } - return 0 -} - proc bind_button3 {w cmd} { bind $w $cmd if {[is_MacOSX]} { @@ -3159,10 +3193,10 @@ proc console_init {w} { } proc console_exec {w cmd after} { - # -- Windows tosses the enviroment when we exec our child. + # -- Cygwin's Tcl tosses the enviroment when we exec our child. # But most users need that so we have to relogin. :-( # - if {[is_Windows]} { + if {[is_Cygwin]} { set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"] } @@ -3284,19 +3318,27 @@ proc console_done {args} { set starting_gitk_msg {Starting gitk... please wait...} proc do_gitk {revs} { - global ui_status_value starting_gitk_msg + global env ui_status_value starting_gitk_msg - set cmd gitk + # -- On Windows gitk is severly broken, and right now it seems like + # nobody cares about fixing it. The only known workaround is to + # always delete ~/.gitk before starting the program. + # + if {[is_Windows]} { + catch {file delete [file join $env(HOME) .gitk]} + } + + # -- Always start gitk through whatever we were loaded with. This + # lets us bypass using shell process on Windows systems. + # + set cmd [info nameofexecutable] + lappend cmd [gitexec gitk] if {$revs ne {}} { append cmd { } append cmd $revs } - if {[is_Windows]} { - set cmd "sh -c \"exec $cmd\"" - } - append cmd { &} - if {[catch {eval exec $cmd} err]} { + if {[catch {eval exec $cmd &} err]} { error_popup "Failed to start gitk:\n\n$err" } else { set ui_status_value $starting_gitk_msg @@ -3894,6 +3936,29 @@ proc do_save_config {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 \ @@ -3985,7 +4050,7 @@ proc do_macosx_app {} { set fd [open $exe w] set gd [file normalize [gitdir]] - set ep [file normalize [exec git --exec-path]] + set ep [file normalize [gitexec]] regsub -all ' $gd "'\\''" gd regsub -all ' $ep "'\\''" ep puts $fd "#!/bin/sh" @@ -4211,7 +4276,12 @@ if {!$single_commit} { .mbar.repository add separator - if {[is_Windows]} { + if {[is_Cygwin]} { + .mbar.repository add command \ + -label {Create Desktop Icon} \ + -command do_cygwin_shortcut \ + -font font_ui + } elseif {[is_Windows]} { .mbar.repository add command \ -label {Create Desktop Icon} \ -command do_windows_shortcut \ @@ -4416,17 +4486,17 @@ if {![is_MacOSX]} { set browser {} catch {set browser $repo_config(instaweb.browser)} -set doc_path [file dirname [exec git --exec-path]] +set doc_path [file dirname [gitexec]] set doc_path [file join $doc_path Documentation index.html] -if {[is_Windows]} { +if {[is_Cygwin]} { set doc_path [exec cygpath --windows $doc_path] } if {$browser eq {}} { if {[is_MacOSX]} { set browser open - } elseif {[is_Windows]} { + } elseif {[is_Cygwin]} { set program_files [file dirname [exec cygpath --windir]] set program_files [file join $program_files {Program Files}] set firefox [file join $program_files {Mozilla Firefox} firefox.exe] @@ -4988,7 +5058,7 @@ focus -force $ui_comm # does *not* pass its env array onto any processes it spawns. # This means that git processes get none of our environment. # -if {[is_Windows]} { +if {[is_Cygwin]} { set ignored_env 0 set suggest_user {} set msg "Possible environment issues exist. From 35874c163e2f5c1def3cc5c3465beceea0355b8f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 29 Jan 2007 00:50:41 -0500 Subject: [PATCH 342/548] git-gui: Implemented file browser and incremental blame. This rather huge change provides a browser for the current branch. The browser simply shows the contents of tree HEAD, and lets the user drill down through the tree. The icons used really stink, as I just copied in icon which we already had. I really need to replace the file_dir and file_uplevel icons with something more useful. If the user double clicks on a file within the browser we open it in a blame viewer. This makes use of the new incremental blame feature that Linus just added yesterday to core Git. Fortunately the feature will be in 1.5.0 final so we can rely on having it available here. Since the blame engine is incremental the user will get blame data for groups which can be determined early. Git will slowly fill in the remaining lines as it goes. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 459 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 459 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index 46e019becd..0192f80dd7 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2945,6 +2945,431 @@ proc reset_hard_wait {fd} { } } +###################################################################### +## +## browser + +set next_browser_id 0 + +proc new_browser {} { + global next_browser_id cursor_ptr + global browser_commit browser_status browser_stack browser_path browser_busy + + set w .browser[incr next_browser_id] + set w_list $w.list.l + set browser_commit($w_list) HEAD + 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 + + toplevel $w + 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] \ + -font font_ui + $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 \ + -font font_ui + 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 "focus $w" + bind $w " + 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 $w "[appname] ([reponame]): File Browser" + ls_tree $w_list $browser_commit($w_list) {} +} + +proc browser_click {was_double_click w pos} { + 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 $pos] .] 0] + set info [lindex $browser_files($w) [expr {$lno - 1}]] + + $w conf -state normal + $w tag remove sel 0.0 end + $w tag remove in_sel 0.0 end + if {$info ne {}} { + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + if {$was_double_click} { + switch -- [lindex $info 0] { + 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] + } + 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 + } + } + } + } + $w conf -state disabled +} + +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 tag remove 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 fd [open "| git ls-tree -z $tree_id" 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_plain + } + 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 + } +} + +proc show_blame {commit path} { + global next_browser_id blame_status blame_data + + set w .browser[incr next_browser_id] + set blame_status($w) {Loading current file content...} + set texts [list] + + toplevel $w + panedwindow $w.out -orient horizontal + + label $w.path -text "$commit:$path" \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken \ + -font font_uibold + pack $w.path -anchor w -side top -fill x + + text $w.out.commit -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 8 \ + -font font_diff + $w.out add $w.out.commit + lappend texts $w.out.commit + + text $w.out.author -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 20 \ + -font font_diff + $w.out add $w.out.author + lappend texts $w.out.author + + text $w.out.date -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width [string length "yyyy-mm-dd hh:mm:ss"] \ + -font font_diff + $w.out add $w.out.date + lappend texts $w.out.date + + text $w.out.linenumber -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 5 \ + -font font_diff + $w.out.linenumber tag conf linenumber -justify right + $w.out add $w.out.linenumber + lappend texts $w.out.linenumber + + text $w.out.file -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 80 \ + -font font_diff + $w.out add $w.out.file + lappend texts $w.out.file + + label $w.status -textvariable blame_status($w) \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken \ + -font font_ui + pack $w.status -anchor w -side bottom -fill x + + scrollbar $w.sby -orient v -command [list scrollbar2many $texts yview] + pack $w.sby -side right -fill y + pack $w.out -side left -fill both -expand 1 + + menu $w.ctxm -tearoff 0 + $w.ctxm add command -label "Copy Commit" \ + -font font_ui \ + -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY" + + foreach i $texts { + $i tag conf in_sel \ + -background [$i cget -foreground] \ + -foreground [$i cget -background] + $i conf -yscrollcommand [list many2scrollbar $texts yview $w.sby] + bind $i "blame_highlight $i @%x,%y $texts;break" + bind_button3 $i " + set cursorX %x + set cursorY %y + set cursorW %W + tk_popup $w.ctxm %X %Y + " + } + + bind $w "focus $w" + bind $w " + array unset blame_status $w + array unset blame_data $w,* + " + wm title $w "[appname] ([reponame]): File Viewer" + + set blame_data($w,total_lines) 0 + set fd [open "| git cat-file blob $commit:$path" r] + fconfigure $fd -blocking 0 -translation lf -encoding binary + fileevent $fd readable [list read_blame_catfile $fd $w $commit $path \ + $texts $w.out.linenumber $w.out.file] +} + +proc read_blame_catfile {fd w commit path texts w_lno w_file} { + global blame_status blame_data + + if {![winfo exists $w_file]} { + catch {close $fd} + return + } + + set n $blame_data($w,total_lines) + foreach i $texts {$i conf -state normal} + while {[gets $fd line] >= 0} { + regsub "\r\$" $line {} line + incr n + $w_lno insert end $n linenumber + $w_file insert end $line + foreach i $texts {$i insert end "\n"} + } + foreach i $texts {$i conf -state disabled} + set blame_data($w,total_lines) $n + + if {[eof $fd]} { + close $fd + set blame_status($w) {Loading annotations...} + set fd [open "| git blame --incremental $commit -- $path" r] + fconfigure $fd -blocking 0 -translation lf -encoding binary + fileevent $fd readable "read_blame_incremental $fd $w $texts" + } +} + +proc read_blame_incremental {fd w w_commit w_author w_date w_lno w_file} { + global blame_status blame_data + + if {![winfo exists $w_commit]} { + catch {close $fd} + return + } + + $w_commit conf -state normal + $w_author conf -state normal + $w_date conf -state normal + + while {[gets $fd line] >= 0} { + if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \ + commit original_line final_line line_count]} { + set blame_data($w,commit) $commit + set blame_data($w,original_line) $original_line + set blame_data($w,final_line) $final_line + set blame_data($w,line_count) $line_count + } elseif {[string match {filename *} $line]} { + set n $blame_data($w,line_count) + set lno $blame_data($w,final_line) + set file [string range $line 9 end] + set commit $blame_data($w,commit) + set abbrev [string range $commit 0 8] + + if {[catch {set author $blame_data($w,$commit,author)} err]} { + puts $err + set author {} + } + + if {[catch {set atime $blame_data($w,$commit,author-time)}]} { + set atime {} + } else { + set atime [clock format $atime -format {%Y-%m-%d %T}] + } + + while {$n > 0} { + $w_commit delete $lno.0 "$lno.0 lineend" + $w_author delete $lno.0 "$lno.0 lineend" + $w_date delete $lno.0 "$lno.0 lineend" + + $w_commit insert $lno.0 $abbrev + $w_author insert $lno.0 $author + $w_date insert $lno.0 $atime + set blame_data($w,line$lno,commit) $commit + + incr n -1 + incr lno + } + } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} { + set blame_data($w,$blame_data($w,commit),$header) $data + } + } + + $w_commit conf -state disabled + $w_author conf -state disabled + $w_date conf -state disabled + + if {[eof $fd]} { + close $fd + set blame_status($w) {Annotation complete.} + } +} + +proc blame_highlight {w pos args} { + set lno [lindex [split [$w index $pos] .] 0] + foreach i $args { + $i tag remove in_sel 0.0 end + } + if {$lno eq {}} return + foreach i $args { + $i tag add in_sel $lno.0 "$lno.0 + 1 line" + } +} + +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 @@ -3021,6 +3446,24 @@ static unsigned char file_merge_bits[] = { 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; } -maskdata $filemask +image create bitmap file_dir -background white -foreground blue -data { +#define mod_width 14 +#define mod_height 15 +static unsigned char mod_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10, + 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, + 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_uplevel -background white -foreground blue -data { +#define mod_width 14 +#define mod_height 15 +static unsigned char mod_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10, + 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, + 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + set ui_index .vpane.files.index.list set ui_workdir .vpane.files.workdir.list @@ -3077,6 +3520,15 @@ proc bind_button3 {w cmd} { } } +proc scrollbar2many {list mode args} { + foreach w $list {eval $w $mode $args} +} + +proc many2scrollbar {list mode sb top bottom} { + $sb set $top $bottom + foreach w $list {$w $mode moveto $top} +} + proc incr_font_size {font {amt 1}} { set sz [font configure $font -size] incr sz $amt @@ -4251,6 +4703,13 @@ if {!$single_commit} { # -- Repository Menu # menu .mbar.repository + +.mbar.repository add command \ + -label {Browse Current Branch} \ + -command new_browser \ + -font font_ui +.mbar.repository add separator + .mbar.repository add command \ -label {Visualize Current Branch} \ -command {do_gitk {}} \ From 3eddda98435f67ffb0afc8baf4bfb51ed8160f2c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 29 Jan 2007 02:50:10 -0500 Subject: [PATCH 343/548] git-gui: Improve the icons used in the browser display. Real icons which seem to indicate going up to the parent (an up arrow) and a subdirectory (an open folder). Files are now drawn with the file_mod icon, like a modified file is. This just looks better as it is more consistent with the rest of our UI. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 0192f80dd7..9892ec32ae 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3115,7 +3115,7 @@ proc read_ls_tree {fd w} { switch -- $type { blob { - set image file_plain + set image file_mod } tree { set image file_dir @@ -3446,23 +3446,31 @@ static unsigned char file_merge_bits[] = { 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; } -maskdata $filemask -image create bitmap file_dir -background white -foreground blue -data { -#define mod_width 14 -#define mod_height 15 -static unsigned char mod_bits[] = { - 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10, - 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, - 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; -} -maskdata $filemask +set file_dir_data { +#define file_width 18 +#define file_height 18 +static unsigned char file_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00, + 0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00, + 0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00, + 0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +} +image create bitmap file_dir -background white -foreground blue \ + -data $file_dir_data -maskdata $file_dir_data +unset file_dir_data -image create bitmap file_uplevel -background white -foreground blue -data { -#define mod_width 14 -#define mod_height 15 -static unsigned char mod_bits[] = { - 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10, - 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, - 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; -} -maskdata $filemask +set file_uplevel_data { +#define up_width 15 +#define up_height 15 +static unsigned char up_bits[] = { + 0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f, + 0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, + 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00}; +} +image create bitmap file_uplevel -background white -foreground red \ + -data $file_uplevel_data -maskdata $file_uplevel_data +unset file_uplevel_data set ui_index .vpane.files.index.list set ui_workdir .vpane.files.workdir.list From c94dd1c8c2da1601c83443292b4ab34be3ab3358 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 29 Jan 2007 02:52:06 -0500 Subject: [PATCH 344/548] git-gui: Display the current branch name in browsers. Rather than using HEAD for the current branch, use the actual name of the current branch in the browser. This way the user knows what a browser is browsing if they open up different browsers while on different branches. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 9892ec32ae..fc74d9e6ea 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2951,13 +2951,13 @@ proc reset_hard_wait {fd} { set next_browser_id 0 -proc new_browser {} { +proc new_browser {commit} { global next_browser_id cursor_ptr global browser_commit browser_status browser_stack browser_path browser_busy set w .browser[incr next_browser_id] set w_list $w.list.l - set browser_commit($w_list) HEAD + 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): @@ -4714,7 +4714,7 @@ menu .mbar.repository .mbar.repository add command \ -label {Browse Current Branch} \ - -command new_browser \ + -command {new_browser $current_branch} \ -font font_ui .mbar.repository add separator From db45378165f499dcc4412b4f4b0f4ec5cbc65914 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 29 Jan 2007 02:56:07 -0500 Subject: [PATCH 345/548] git-gui: Allow users to edit user.name, user.email from options. Users may need to be able to alter their user.name or user.email configuration settings. If they are mostly a git-gui user they should be able to view/set these important values from within the git-gui environment, rather than needing to edit a raw text file on their local filesystem. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index fc74d9e6ea..09c1b74e75 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -4270,6 +4270,9 @@ proc do_options {} { 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}} @@ -4676,6 +4679,9 @@ proc apply_config {} { set default_config(merge.summary) false set default_config(merge.verbosity) 2 +set default_config(user.name) {} +set default_config(user.email) {} + set default_config(gui.trustmtime) false set default_config(gui.diffcontext) 5 set default_config(gui.newbranchtemplate) {} From 463ca37b61b0d12aae8949c730bd38cc2149923f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 29 Jan 2007 03:03:29 -0500 Subject: [PATCH 346/548] git-gui: Use -M and -C when running blame. Since we run blame incrementally in the background we might as well get as much data as we can from the file. Adding -M and -C definately makes it take longer to compute the revision annotations, but since they are streamed in and updated as they are discovered we'll get recent data almost immediately anyway. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index 09c1b74e75..d5490cdde0 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3276,7 +3276,9 @@ proc read_blame_catfile {fd w commit path texts w_lno w_file} { if {[eof $fd]} { close $fd set blame_status($w) {Loading annotations...} - set fd [open "| git blame --incremental $commit -- $path" r] + 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 "read_blame_incremental $fd $w $texts" } From 8f6c07b902b297329e63799d5332758530ec4d99 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 29 Jan 2007 03:09:28 -0500 Subject: [PATCH 347/548] git-gui: Correctly handle spaces in filepaths. Anytime are about to open a pipe on what may be user data we need to make sure the value is escaped correctly into a Tcl list, so that the executed subprocess will receive the right arguments. For the most part we were already doing this correctly, but a handful of locations did not. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index d5490cdde0..160309b2c4 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3087,7 +3087,8 @@ proc ls_tree {w tree_id name} { lappend browser_stack($w) [list $tree_id $name] $w conf -state disabled - set fd [open "| git ls-tree -z $tree_id" r] + 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] } @@ -3247,7 +3248,8 @@ proc show_blame {commit path} { wm title $w "[appname] ([reponame]): File Viewer" set blame_data($w,total_lines) 0 - set fd [open "| git cat-file blob $commit:$path" r] + 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 \ $texts $w.out.linenumber $w.out.file] From 915616e4ebaedc486a2dd235e70f25ceb39b4515 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 29 Jan 2007 05:33:27 -0500 Subject: [PATCH 348/548] git-gui: Display original filename and line number in blame. When we annotate a file and show its line data, we're already asking for copy and movement detection (-M -C). This costs extra time, but gives extra data. Since we are asking for the extra data we really should show it to the user. Now the blame UI has two additional columns, one for the original filename (in the case of a move/copy between files) and one for the original line number of the current line of code. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index 160309b2c4..f247d40a79 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3190,6 +3190,25 @@ proc show_blame {commit path} { $w.out add $w.out.date lappend texts $w.out.date + text $w.out.filename -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 20 \ + -font font_diff + $w.out add $w.out.filename + lappend texts $w.out.filename + + text $w.out.origlinenumber -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 5 \ + -font font_diff + $w.out.origlinenumber tag conf linenumber -justify right + $w.out add $w.out.origlinenumber + lappend texts $w.out.origlinenumber + text $w.out.linenumber -background white -borderwidth 0 \ -state disabled \ -wrap none \ @@ -3286,7 +3305,9 @@ proc read_blame_catfile {fd w commit path texts w_lno w_file} { } } -proc read_blame_incremental {fd w w_commit w_author w_date w_lno w_file} { +proc read_blame_incremental {fd w + w_commit w_author w_date w_filename w_olno + w_lno w_file} { global blame_status blame_data if {![winfo exists $w_commit]} { @@ -3297,6 +3318,8 @@ proc read_blame_incremental {fd w w_commit w_author w_date w_lno w_file} { $w_commit conf -state normal $w_author conf -state normal $w_date conf -state normal + $w_filename conf -state normal + $w_olno conf -state normal while {[gets $fd line] >= 0} { if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \ @@ -3308,6 +3331,7 @@ proc read_blame_incremental {fd w w_commit w_author w_date w_lno w_file} { } elseif {[string match {filename *} $line]} { set n $blame_data($w,line_count) set lno $blame_data($w,final_line) + set ol $blame_data($w,original_line) set file [string range $line 9 end] set commit $blame_data($w,commit) set abbrev [string range $commit 0 8] @@ -3327,14 +3351,20 @@ proc read_blame_incremental {fd w w_commit w_author w_date w_lno w_file} { $w_commit delete $lno.0 "$lno.0 lineend" $w_author delete $lno.0 "$lno.0 lineend" $w_date delete $lno.0 "$lno.0 lineend" + $w_filename delete $lno.0 "$lno.0 lineend" + $w_olno delete $lno.0 "$lno.0 lineend" $w_commit insert $lno.0 $abbrev $w_author insert $lno.0 $author $w_date insert $lno.0 $atime + $w_filename insert $lno.0 $file + $w_olno insert $lno.0 $ol linenumber + set blame_data($w,line$lno,commit) $commit incr n -1 incr lno + incr ol } } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} { set blame_data($w,$blame_data($w,commit),$header) $data @@ -3344,6 +3374,8 @@ proc read_blame_incremental {fd w w_commit w_author w_date w_lno w_file} { $w_commit conf -state disabled $w_author conf -state disabled $w_date conf -state disabled + $w_filename conf -state disabled + $w_olno conf -state disabled if {[eof $fd]} { close $fd From e7fb6c69f7612d4e8545f1812a2830f97f183f87 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 29 Jan 2007 05:51:49 -0500 Subject: [PATCH 349/548] git-gui: Install column headers in blame viewer. I started to get confused about what each column meant in the blame viewer, and I'm the guy who wrote the code! So now git-gui hints to the user about what each column is by drawing headers at the top. Unfortunately this meant I had to use those dreaded frame objects which seem to cause so much pain on Windows. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 95 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index f247d40a79..0fef7295ad 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3163,70 +3163,120 @@ proc show_blame {commit path} { -font font_uibold pack $w.path -anchor w -side top -fill x - text $w.out.commit -background white -borderwidth 0 \ + set hbg #e2effa + frame $w.out.commit -width 10 -height 10 + label $w.out.commit.l -text Commit \ + -background $hbg \ + -font font_uibold + text $w.out.commit.t \ + -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ - -width 8 \ + -width 9 \ -font font_diff + pack $w.out.commit.l -side top -fill x + pack $w.out.commit.t -fill both $w.out add $w.out.commit - lappend texts $w.out.commit + lappend texts $w.out.commit.t - text $w.out.author -background white -borderwidth 0 \ + frame $w.out.author -width 10 -height 10 + label $w.out.author.l -text Author \ + -background $hbg \ + -font font_uibold + text $w.out.author.t \ + -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ -width 20 \ -font font_diff + pack $w.out.author.l -side top -fill x + pack $w.out.author.t -fill both $w.out add $w.out.author - lappend texts $w.out.author + lappend texts $w.out.author.t - text $w.out.date -background white -borderwidth 0 \ + frame $w.out.date -width 10 -height 10 + label $w.out.date.l -text Date \ + -background $hbg \ + -font font_uibold + text $w.out.date.t \ + -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ -width [string length "yyyy-mm-dd hh:mm:ss"] \ -font font_diff + pack $w.out.date.l -side top -fill x + pack $w.out.date.t -fill both $w.out add $w.out.date - lappend texts $w.out.date + lappend texts $w.out.date.t - text $w.out.filename -background white -borderwidth 0 \ + frame $w.out.filename -width 10 -height 10 + label $w.out.filename.l -text Filename \ + -background $hbg \ + -font font_uibold + text $w.out.filename.t \ + -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ -width 20 \ -font font_diff + pack $w.out.filename.l -side top -fill x + pack $w.out.filename.t -fill both $w.out add $w.out.filename - lappend texts $w.out.filename + lappend texts $w.out.filename.t - text $w.out.origlinenumber -background white -borderwidth 0 \ + frame $w.out.origlinenumber -width 10 -height 10 + label $w.out.origlinenumber.l -text {Orig Line} \ + -background $hbg \ + -font font_uibold + text $w.out.origlinenumber.t \ + -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ -width 5 \ -font font_diff - $w.out.origlinenumber tag conf linenumber -justify right + $w.out.origlinenumber.t tag conf linenumber -justify right + pack $w.out.origlinenumber.l -side top -fill x + pack $w.out.origlinenumber.t -fill both $w.out add $w.out.origlinenumber - lappend texts $w.out.origlinenumber + lappend texts $w.out.origlinenumber.t - text $w.out.linenumber -background white -borderwidth 0 \ + frame $w.out.linenumber -width 10 -height 10 + label $w.out.linenumber.l -text {Curr Line} \ + -background $hbg \ + -font font_uibold + text $w.out.linenumber.t \ + -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ -width 5 \ -font font_diff - $w.out.linenumber tag conf linenumber -justify right + $w.out.linenumber.t tag conf linenumber -justify right + pack $w.out.linenumber.l -side top -fill x + pack $w.out.linenumber.t -fill both $w.out add $w.out.linenumber - lappend texts $w.out.linenumber + lappend texts $w.out.linenumber.t - text $w.out.file -background white -borderwidth 0 \ + frame $w.out.file -width 10 -height 10 + label $w.out.file.l -text {File Content} \ + -background $hbg \ + -font font_uibold + text $w.out.file.t \ + -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ -width 80 \ -font font_diff + pack $w.out.file.l -side top -fill x + pack $w.out.file.t -fill both $w.out add $w.out.file - lappend texts $w.out.file + lappend texts $w.out.file.t label $w.status -textvariable blame_status($w) \ -anchor w \ @@ -3236,7 +3286,8 @@ proc show_blame {commit path} { -font font_ui pack $w.status -anchor w -side bottom -fill x - scrollbar $w.sby -orient v -command [list scrollbar2many $texts yview] + scrollbar $w.sby -orient v \ + -command [list scrollbar2many $texts yview] pack $w.sby -side right -fill y pack $w.out -side left -fill both -expand 1 @@ -3249,7 +3300,8 @@ proc show_blame {commit path} { $i tag conf in_sel \ -background [$i cget -foreground] \ -foreground [$i cget -background] - $i conf -yscrollcommand [list many2scrollbar $texts yview $w.sby] + $i conf -yscrollcommand \ + [list many2scrollbar $texts yview $w.sby] bind $i "blame_highlight $i @%x,%y $texts;break" bind_button3 $i " set cursorX %x @@ -3270,8 +3322,9 @@ proc show_blame {commit path} { 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 \ - $texts $w.out.linenumber $w.out.file] + fileevent $fd readable [list read_blame_catfile \ + $fd $w $commit $path \ + $texts $w.out.linenumber.t $w.out.file.t] } proc read_blame_catfile {fd w commit path texts w_lno w_file} { From 747c0cf93c75207da095709a92a615aae0553289 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 29 Jan 2007 06:23:12 -0500 Subject: [PATCH 350/548] git-gui: Use a grid layout for the blame viewer. Using a panedwindow to display the blame viewer's individual columns just doesn't make sense. Most of the important data fits within the columns we have allocated, and those that don't the leading part fits and that's good enough. There are just too many columns within this viewer to let the user sanely control individual column widths. This change shouldn't really be an issue for most git-gui users as their displays should be large enough to accept this massive dump of data. We now also have a properly working horizontal scrollbar for the current file data area. This makes it easier to get away with a narrow window when screen space is limited, as you can still scroll around within the file content. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 120 ++++++++++++++++++++++++++--------------------------- 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 0fef7295ad..1f13f7f9b2 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3153,7 +3153,6 @@ proc show_blame {commit path} { set texts [list] toplevel $w - panedwindow $w.out -orient horizontal label $w.path -text "$commit:$path" \ -anchor w \ @@ -3161,122 +3160,126 @@ proc show_blame {commit path} { -borderwidth 1 \ -relief sunken \ -font font_uibold - pack $w.path -anchor w -side top -fill x + pack $w.path -side top -fill x set hbg #e2effa - frame $w.out.commit -width 10 -height 10 - label $w.out.commit.l -text Commit \ + frame $w.out + label $w.out.commit_l -text Commit \ + -relief solid \ + -borderwidth 1 \ -background $hbg \ -font font_uibold - text $w.out.commit.t \ + text $w.out.commit_t \ -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ -width 9 \ -font font_diff - pack $w.out.commit.l -side top -fill x - pack $w.out.commit.t -fill both - $w.out add $w.out.commit - lappend texts $w.out.commit.t + lappend texts $w.out.commit_t - frame $w.out.author -width 10 -height 10 - label $w.out.author.l -text Author \ + label $w.out.author_l -text Author \ + -relief solid \ + -borderwidth 1 \ -background $hbg \ -font font_uibold - text $w.out.author.t \ + text $w.out.author_t \ -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ -width 20 \ -font font_diff - pack $w.out.author.l -side top -fill x - pack $w.out.author.t -fill both - $w.out add $w.out.author - lappend texts $w.out.author.t + lappend texts $w.out.author_t - frame $w.out.date -width 10 -height 10 - label $w.out.date.l -text Date \ + label $w.out.date_l -text Date \ + -relief solid \ + -borderwidth 1 \ -background $hbg \ -font font_uibold - text $w.out.date.t \ + text $w.out.date_t \ -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ -width [string length "yyyy-mm-dd hh:mm:ss"] \ -font font_diff - pack $w.out.date.l -side top -fill x - pack $w.out.date.t -fill both - $w.out add $w.out.date - lappend texts $w.out.date.t + lappend texts $w.out.date_t - frame $w.out.filename -width 10 -height 10 - label $w.out.filename.l -text Filename \ + label $w.out.filename_l -text Filename \ + -relief solid \ + -borderwidth 1 \ -background $hbg \ -font font_uibold - text $w.out.filename.t \ + text $w.out.filename_t \ -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ -width 20 \ -font font_diff - pack $w.out.filename.l -side top -fill x - pack $w.out.filename.t -fill both - $w.out add $w.out.filename - lappend texts $w.out.filename.t + lappend texts $w.out.filename_t - frame $w.out.origlinenumber -width 10 -height 10 - label $w.out.origlinenumber.l -text {Orig Line} \ + label $w.out.origlinenumber_l -text {Orig Line} \ + -relief solid \ + -borderwidth 1 \ -background $hbg \ -font font_uibold - text $w.out.origlinenumber.t \ + text $w.out.origlinenumber_t \ -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ -width 5 \ -font font_diff - $w.out.origlinenumber.t tag conf linenumber -justify right - pack $w.out.origlinenumber.l -side top -fill x - pack $w.out.origlinenumber.t -fill both - $w.out add $w.out.origlinenumber - lappend texts $w.out.origlinenumber.t + $w.out.origlinenumber_t tag conf linenumber -justify right + lappend texts $w.out.origlinenumber_t - frame $w.out.linenumber -width 10 -height 10 - label $w.out.linenumber.l -text {Curr Line} \ + label $w.out.linenumber_l -text {Curr Line} \ + -relief solid \ + -borderwidth 1 \ -background $hbg \ -font font_uibold - text $w.out.linenumber.t \ + 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 - pack $w.out.linenumber.l -side top -fill x - pack $w.out.linenumber.t -fill both - $w.out add $w.out.linenumber - lappend texts $w.out.linenumber.t + $w.out.linenumber_t tag conf linenumber -justify right + lappend texts $w.out.linenumber_t - frame $w.out.file -width 10 -height 10 - label $w.out.file.l -text {File Content} \ + label $w.out.file_l -text {File Content} \ + -relief solid \ + -borderwidth 1 \ -background $hbg \ -font font_uibold - text $w.out.file.t \ + 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 - pack $w.out.file.l -side top -fill x - pack $w.out.file.t -fill both - $w.out add $w.out.file - lappend texts $w.out.file.t + lappend texts $w.out.file_t + + scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview] + scrollbar $w.out.sby -orient v \ + -command [list scrollbar2many $texts yview] + set labels [list] + foreach i $texts { + regsub {_t$} $i _l l + lappend labels $l + } + set file_col [expr {[llength $texts] - 1}] + eval grid $labels -sticky we + eval grid $texts $w.out.sby -sticky nsew + grid conf $w.out.sbx -column $file_col -sticky we + grid columnconfigure $w.out $file_col -weight 1 + grid rowconfigure $w.out 1 -weight 1 + pack $w.out -fill both -expand 1 label $w.status -textvariable blame_status($w) \ -anchor w \ @@ -3284,12 +3287,7 @@ proc show_blame {commit path} { -borderwidth 1 \ -relief sunken \ -font font_ui - pack $w.status -anchor w -side bottom -fill x - - scrollbar $w.sby -orient v \ - -command [list scrollbar2many $texts yview] - pack $w.sby -side right -fill y - pack $w.out -side left -fill both -expand 1 + pack $w.status -side bottom -fill x menu $w.ctxm -tearoff 0 $w.ctxm add command -label "Copy Commit" \ @@ -3301,7 +3299,7 @@ proc show_blame {commit path} { -background [$i cget -foreground] \ -foreground [$i cget -background] $i conf -yscrollcommand \ - [list many2scrollbar $texts yview $w.sby] + [list many2scrollbar $texts yview $w.out.sby] bind $i "blame_highlight $i @%x,%y $texts;break" bind_button3 $i " set cursorX %x @@ -3324,7 +3322,7 @@ proc show_blame {commit path} { fconfigure $fd -blocking 0 -translation lf -encoding binary fileevent $fd readable [list read_blame_catfile \ $fd $w $commit $path \ - $texts $w.out.linenumber.t $w.out.file.t] + $texts $w.out.linenumber_t $w.out.file_t] } proc read_blame_catfile {fd w commit path texts w_lno w_file} { From 37f1db80a46cf753308bfc9b5de9dd3b3a551218 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 29 Jan 2007 06:56:00 -0500 Subject: [PATCH 351/548] git-gui: Assign background colors to each blame hunk. To help the user visually see which lines are associated with each other in the file we attempt to sign a unique background color to each commit and then render all text associated with that commit using that color. This works out OK for a file which has very few commits in it; but most files don't have that property. What we really need to do is look at what colors are used by our neighboring commits (if known yet) and pick a color which does not conflict with our neighbor. If we have run out of colors then we should force our neighbor to recolor too. Yes, its the graph coloring problem. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 71 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 1f13f7f9b2..ef353319ec 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3309,6 +3309,8 @@ proc show_blame {commit path} { " } + set blame_data($w,colors) {} + bind $w "focus $w" bind $w " array unset blame_status $w @@ -3366,6 +3368,15 @@ proc read_blame_incremental {fd w return } + set all [list \ + $w_commit \ + $w_author \ + $w_date \ + $w_filename \ + $w_olno \ + $w_lno \ + $w_file] + $w_commit conf -state normal $w_author conf -state normal $w_date conf -state normal @@ -3374,36 +3385,65 @@ proc read_blame_incremental {fd w while {[gets $fd line] >= 0} { if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \ - commit original_line final_line line_count]} { - set blame_data($w,commit) $commit + 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,seen)}]} { + if {$blame_data($w,colors) eq {}} { + set blame_data($w,colors) { + yellow + red + pink + orange + green + grey + } + } + set c [lindex $blame_data($w,colors) 0] + set blame_data($w,colors) \ + [lrange $blame_data($w,colors) 1 end] + foreach t $all { + $t tag conf g$cmit -background $c + } + } else { + set blame_data($w,$cmit,seen) 1 + } } elseif {[string match {filename *} $line]} { set n $blame_data($w,line_count) set lno $blame_data($w,final_line) set ol $blame_data($w,original_line) set file [string range $line 9 end] - set commit $blame_data($w,commit) - set abbrev [string range $commit 0 8] + set cmit $blame_data($w,commit) + set abbrev [string range $cmit 0 8] - if {[catch {set author $blame_data($w,$commit,author)} err]} { - puts $err + if {[catch {set author $blame_data($w,$cmit,author)} err]} { set author {} } - if {[catch {set atime $blame_data($w,$commit,author-time)}]} { + if {[catch {set atime $blame_data($w,$cmit,author-time)}]} { set atime {} } else { set atime [clock format $atime -format {%Y-%m-%d %T}] } while {$n > 0} { - $w_commit delete $lno.0 "$lno.0 lineend" - $w_author delete $lno.0 "$lno.0 lineend" - $w_date delete $lno.0 "$lno.0 lineend" - $w_filename delete $lno.0 "$lno.0 lineend" - $w_olno delete $lno.0 "$lno.0 lineend" + if {![catch {set g g$blame_data($w,line$lno,commit)}]} { + foreach t $all { + $t tag remove $g $lno.0 "$lno.0 lineend + 1c" + } + } + + foreach t [list \ + $w_commit \ + $w_author \ + $w_date \ + $w_filename \ + $w_olno] { + $t delete $lno.0 "$lno.0 lineend" + } $w_commit insert $lno.0 $abbrev $w_author insert $lno.0 $author @@ -3411,7 +3451,12 @@ proc read_blame_incremental {fd w $w_filename insert $lno.0 $file $w_olno insert $lno.0 $ol linenumber - set blame_data($w,line$lno,commit) $commit + set g g$cmit + foreach t $all { + $t tag add $g $lno.0 "$lno.0 lineend + 1c" + } + + set blame_data($w,line$lno,commit) $cmit incr n -1 incr lno From 92cf95696f0837b555d90fe6ef0ffd2b8348b4d0 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Thu, 1 Feb 2007 12:30:28 -0500 Subject: [PATCH 352/548] reword the detached head message a little again Seems clearer this way, to me at least. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- git-checkout.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-checkout.sh b/git-checkout.sh index deb479524a..97c26adba9 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -155,7 +155,7 @@ then detached="$new" if test -n "$oldbranch" then - detach_warn="Note: you are not on any branch and are at commit \"$new_name\" + detach_warn="Note: moving to \"$new_name\" which isn't a local branch If you want to create a new branch from this checkout, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b " From 6124aee5d903150a649a75bf51a68b4bdfd67140 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Thu, 1 Feb 2007 12:31:26 -0500 Subject: [PATCH 353/548] add a quiet option to git-checkout Those new messages are certainly nice, but there might be cases where they are simply unwelcome, like when git-commit is used within scripts. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- Documentation/git-checkout.txt | 5 ++++- git-checkout.sh | 23 ++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 4ea2b315d2..55c9289438 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -8,7 +8,7 @@ git-checkout - Checkout and switch to a branch SYNOPSIS -------- [verse] -'git-checkout' [-f] [-b [-l]] [-m] [] +'git-checkout' [-q] [-f] [-b [-l]] [-m] [] 'git-checkout' [] ... DESCRIPTION @@ -33,6 +33,9 @@ working tree. OPTIONS ------- +-q:: + Quiet, supress feedback messages. + -f:: Force a re-read of everything. diff --git a/git-checkout.sh b/git-checkout.sh index 97c26adba9..99a81f509a 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -1,6 +1,6 @@ #!/bin/sh -USAGE='[-f] [-b ] [-m] [] [...]' +USAGE='[-q] [-f] [-b ] [-m] [] [...]' SUBDIRECTORY_OK=Sometimes . git-sh-setup require_work_tree @@ -15,6 +15,7 @@ branch= newbranch= newbranch_log= merge= +quiet= LF=' ' while [ "$#" != "0" ]; do @@ -40,6 +41,9 @@ while [ "$#" != "0" ]; do -m) merge=1 ;; + "-q") + quiet=1 + ;; --) break ;; @@ -153,7 +157,7 @@ detach_warn= if test -z "$branch$newbranch" && test "$new" != "$old" then detached="$new" - if test -n "$oldbranch" + if test -n "$oldbranch" && test -z "$quiet" then detach_warn="Note: moving to \"$new_name\" which isn't a local branch If you want to create a new branch from this checkout, you may do so @@ -180,8 +184,11 @@ fi if [ "X$old" = X ] then - echo >&2 "warning: You appear to be on a branch yet to be born." - echo >&2 "warning: Forcing checkout of $new_name." + if test -z "$quiet" + then + echo >&2 "warning: You appear to be on a branch yet to be born." + echo >&2 "warning: Forcing checkout of $new_name." + fi force=1 fi @@ -226,7 +233,7 @@ else exit 0 ) saved_err=$? - if test "$saved_err" = 0 + if test "$saved_err" = 0 && test -z "$quiet" then git diff-index --name-status "$new" fi @@ -251,11 +258,9 @@ if [ "$?" -eq 0 ]; then if test -n "$branch" then GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch" - if test -n "$newbranch" + if test -z "$quiet" then - echo >&2 "Switched to a new branch \"$branch\"" - else - echo >&2 "Switched to branch \"$branch\"" + echo >&2 "Switched to${newbranch:+ a new} branch \"$branch\"" fi elif test -n "$detached" then From 08f1675059c09b7ee0b8ed16bc059472d07bb9e5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 1 Feb 2007 21:47:34 -0800 Subject: [PATCH 354/548] Use "git checkout -q" in git-bisect Converts one use of git-checkout in git-bisect not to say "switching to branch". It looks like all the other cases it is friendlier to give notice to the end user. Signed-off-by: Junio C Hamano --- git-bisect.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-bisect.sh b/git-bisect.sh index 6da31e87a0..e8d3418988 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -152,7 +152,7 @@ bisect_next() { nr=$(eval "git-rev-list $rev $good -- $(cat $GIT_DIR/BISECT_NAMES)" | wc -l) || exit echo "Bisecting: $nr revisions left to test after this" echo "$rev" > "$GIT_DIR/refs/heads/new-bisect" - git checkout new-bisect || exit + git checkout -q new-bisect || exit mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" && GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect git-show-branch "$rev" From fe55851624bbdb4f08e17e27ef8197ed610ac586 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Thu, 1 Feb 2007 12:33:23 -0500 Subject: [PATCH 355/548] prevent HEAD reflog to be interpreted as current branch reflog The work in progress to enable separate reflog for HEAD will make it independent from reflog of any branch HEAD might be pointing to. In the mean time disallow HEAD@{...} until that work is completed. Otherwise people might get used to the current behavior which makes HEAD@{...} an alias for @{...} which won't be the case later. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- sha1_name.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sha1_name.c b/sha1_name.c index 9dfb3ac574..70c6e42b04 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -301,12 +301,26 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) fprintf(stderr, warning, len, str); if (reflog_len) { - /* Is it asking for N-th entry, or approxidate? */ int nth, i; unsigned long at_time; unsigned long co_time; int co_tz, co_cnt; + /* + * We'll have an independent reflog for "HEAD" eventually + * which won't be a synonym for the current branch reflog. + * In the mean time prevent people from getting used to + * such a synonym until the work is completed. + */ + if (!strncmp("HEAD", str, len) && + !strncmp(real_ref, "refs/", 5)) { + error("reflog for HEAD has not been implemented yet\n" + "Maybe you could try %s%s instead.", + strchr(real_ref+5, '/')+1, str + len); + exit(-1); + } + + /* Is it asking for N-th entry, or approxidate? */ for (i = nth = 0; 0 <= nth && i < reflog_len; i++) { char ch = str[at+2+i]; if ('0' <= ch && ch <= '9') From 11cf8801d7d58cc1532f11a827ce130c10d149be Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Thu, 1 Feb 2007 17:29:33 -0500 Subject: [PATCH 356/548] provide a nice @{...} syntax to always mean the current branch reflog This is shorter than HEAD@{...} and being nameless it has no semantic issues. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- sha1_name.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index 70c6e42b04..de8caf8cf6 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -279,7 +279,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) /* basic@{time or number} format to query ref-log */ reflog_len = at = 0; if (str[len-1] == '}') { - for (at = 1; at < len - 1; at++) { + for (at = 0; at < len - 1; at++) { if (str[at] == '@' && str[at+1] == '{') { reflog_len = (len-1) - (at+2); len = at; @@ -289,10 +289,14 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) } /* Accept only unambiguous ref paths. */ - if (ambiguous_path(str, len)) + if (len && ambiguous_path(str, len)) return -1; - refs_found = dwim_ref(str, len, sha1, &real_ref); + if (!len && reflog_len) { + /* allow "@{...}" to mean the current branch reflog */ + refs_found = dwim_ref("HEAD", 4, sha1, &real_ref); + } else + refs_found = dwim_ref(str, len, sha1, &real_ref); if (!refs_found) return -1; @@ -312,11 +316,12 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) * In the mean time prevent people from getting used to * such a synonym until the work is completed. */ - if (!strncmp("HEAD", str, len) && + if (len && !strncmp("HEAD", str, len) && !strncmp(real_ref, "refs/", 5)) { error("reflog for HEAD has not been implemented yet\n" - "Maybe you could try %s%s instead.", - strchr(real_ref+5, '/')+1, str + len); + "Maybe you could try %s%s instead, " + "or just %s for current branch..", + strchr(real_ref+5, '/')+1, str+len, str+len); exit(-1); } From d271fd5311da7e475b02ef11507155fa912f0553 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 2 Feb 2007 00:07:24 +0100 Subject: [PATCH 357/548] Teach the '@{...}' notation to git-log -g Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- reflog-walk.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/reflog-walk.c b/reflog-walk.c index 82621601d6..653ec956f0 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -165,6 +165,14 @@ void add_reflog_for_walk(struct reflog_walk_info *info, if (item) reflogs = item->util; else { + if (*branch == '\0') { + unsigned char sha1[20]; + const char *head = resolve_ref("HEAD", sha1, 0, NULL); + if (!head) + die ("No current branch"); + free(branch); + branch = xstrdup(head); + } reflogs = read_complete_reflog(branch); if (!reflogs || reflogs->nr == 0) die("No reflogs found for '%s'", branch); From 1e5db3075ae1f22cbffc273321e94b60d5f23927 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 2 Feb 2007 00:21:49 +0100 Subject: [PATCH 358/548] Update the documentation for the new '@{...}' syntax Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/git-rev-parse.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index aeb37b65d2..4041a16070 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -160,6 +160,10 @@ blobs contained in a commit. immediately following a ref name and the ref must have an existing log ($GIT_DIR/logs/). +* You can use the '@' construct with an empty ref part to get at a + reflog of the current branch. For example, if you are on the + branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'. + * A suffix '{caret}' to a revision parameter means the first parent of that commit object. '{caret}' means the th parent (i.e. 'rev{caret}' From 22600a2515cc92f66fd76f2aae05637697c713b9 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 13:12:26 -0800 Subject: [PATCH 359/548] git-svn: do not let Git.pm warn if we prematurely close pipes This mainly quiets down warnings when running git svn log. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano --- git-svn.perl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 68156fc8a5..8ebaae9ff8 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -681,7 +681,7 @@ sub show_log { process_commit($_, $r_min, $r_max) foreach reverse @k; } out: - eval { command_close_pipe($log) }; + close $log; print '-' x72,"\n" unless $_incremental || $_oneline; } @@ -1475,7 +1475,7 @@ sub map_tree_joins { $seen{$commit} = 1; } } - eval { command_close_pipe($pipe) }; + close $pipe; } } @@ -1669,7 +1669,7 @@ sub write_grafts { last unless /^\S/; } } - eval { command_close_pipe($ch) }; # breaking the pipe + close $ch; # breaking the pipe # if real parents are the only ones in the grafts, drop it next if join(' ',sort keys %$p) eq join(' ',sort keys %x); @@ -1766,7 +1766,7 @@ sub get_commit_time { } elsif ($tz =~ s/^\-//) { $s -= tz_to_s_offset($tz); } - eval { command_close_pipe($fh) }; + close $fh; return $s; } die "Can't get commit time for commit: $cmt\n"; @@ -2846,7 +2846,7 @@ sub rmdirs { delete $rm->{join '/', @dn}; } unless (%$rm) { - eval { command_close_pipe($fh) }; + close $fh; return; } } From 7a8c9ec1a95a368c5f32d430058d67109feccfee Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 2 Feb 2007 00:05:55 -0500 Subject: [PATCH 360/548] Pull out remote listing functions in git-remote. I want to reuse the stale branch detection to implement a new 'git remote prune' subcommand. Easiest way to do that is to use the same logic that 'git remote show' uses to determine the stale tracking branches, then delete those. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- git-remote.perl | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/git-remote.perl b/git-remote.perl index c813fe1451..969d33bc5e 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -130,7 +130,7 @@ sub update_ls_remote { $info->{'LS_REMOTE'} = \@ref; } -sub show_wildcard_mapping { +sub list_wildcard_mapping { my ($forced, $ours, $ls) = @_; my %refs; for (@$ls) { @@ -156,25 +156,14 @@ sub show_wildcard_mapping { push @tracked, $_; } } - if (@new) { - print " New remote branches (next fetch will store in remotes/$ours)\n"; - print " @new\n"; - } - if (@stale) { - print " Stale tracking branches in remotes/$ours (you'd better remove them)\n"; - print " @stale\n"; - } - if (@tracked) { - print " Tracked remote branches\n"; - print " @tracked\n"; - } + return \@new, \@stale, \@tracked; } -sub show_mapping { +sub list_mapping { my ($name, $info) = @_; my $fetch = $info->{'FETCH'}; my $ls = $info->{'LS_REMOTE'}; - my (@stale, @tracked); + my (@new, @stale, @tracked); for (@$fetch) { next unless (/(\+)?([^:]+):(.*)/); @@ -182,7 +171,11 @@ sub show_mapping { if ($theirs eq 'refs/heads/*' && $ours =~ /^refs\/remotes\/(.*)\/\*$/) { # wildcard mapping - show_wildcard_mapping($forced, $1, $ls); + my ($w_new, $w_stale, $w_tracked) + = list_wildcard_mapping($forced, $1, $ls); + push @new, @$w_new; + push @stale, @$w_stale; + push @tracked, @$w_tracked; } elsif ($theirs =~ /\*/ || $ours =~ /\*/) { print STDERR "Warning: unrecognized mapping in remotes.$name.fetch: $_\n"; @@ -196,13 +189,23 @@ sub show_mapping { } } } - if (@stale) { - print " Stale tracking branches in remotes/$name (you'd better remove them)\n"; - print " @stale\n"; + return \@new, \@stale, \@tracked; +} + +sub show_mapping { + my ($name, $info) = @_; + my ($new, $stale, $tracked) = list_mapping($name, $info); + if (@$new) { + print " New remote branches (next fetch will store in remotes/$name)\n"; + print " @$new\n"; } - if (@tracked) { + if (@$stale) { + print " Stale tracking branches in remotes/$name (you'd better remove them)\n"; + print " @$stale\n"; + } + if (@$tracked) { print " Tracked remote branches\n"; - print " @tracked\n"; + print " @$tracked\n"; } } From 859607dfe04260d55f5901974fdd660aebc427fa Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 2 Feb 2007 00:06:08 -0500 Subject: [PATCH 361/548] Teach 'git remote' how to cleanup stale tracking branches. Since it can be annoying to manually cleanup 40 tracking branches which were removed by the remote system, 'git remote prune ' can now be used to delete any tracking branches under which are no longer available on the remote system. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-remote.txt | 5 +++++ git-remote.perl | 39 +++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 358c1acfc3..817651eaa4 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -12,6 +12,7 @@ SYNOPSIS 'git-remote' 'git-remote' add 'git-remote' show +'git-remote' prune DESCRIPTION ----------- @@ -26,6 +27,10 @@ update remote-tracking branches /. In the third form, gives some information about the remote . +In the fourth form, deletes all stale tracking branches under . +These stale branches have already been removed from the remote repository +referenced by , but are still locally available in "remotes/". + The remote configuration is achieved using the `remote.origin.url` and `remote.origin.fetch` configuration variables. (See gitlink:git-config[1]). diff --git a/git-remote.perl b/git-remote.perl index 969d33bc5e..f16ff21b8b 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -200,7 +200,7 @@ sub show_mapping { print " @$new\n"; } if (@$stale) { - print " Stale tracking branches in remotes/$name (you'd better remove them)\n"; + print " Stale tracking branches in remotes/$name (use 'git remote prune')\n"; print " @$stale\n"; } if (@$tracked) { @@ -209,6 +209,23 @@ sub show_mapping { } } +sub prune_remote { + my ($name, $ls_remote) = @_; + if (!exists $remote->{$name}) { + print STDERR "No such remote $name\n"; + return; + } + my $info = $remote->{$name}; + update_ls_remote($ls_remote, $info); + + my ($new, $stale, $tracked) = list_mapping($name, $info); + my $prefix = "refs/remotes/$name"; + foreach my $to_prune (@$stale) { + my @v = $git->command(qw(rev-parse --verify), "$prefix/$to_prune"); + $git->command(qw(update-ref -d), "$prefix/$to_prune", $v[0]); + } +} + sub show_remote { my ($name, $ls_remote) = @_; if (!exists $remote->{$name}) { @@ -270,6 +287,25 @@ elsif ($ARGV[0] eq 'show') { show_remote($ARGV[$i], $ls_remote); } } +elsif ($ARGV[0] eq 'prune') { + my $ls_remote = 1; + my $i; + for ($i = 1; $i < @ARGV; $i++) { + if ($ARGV[$i] eq '-n') { + $ls_remote = 0; + } + else { + last; + } + } + if ($i >= @ARGV) { + print STDERR "Usage: git remote prune \n"; + exit(1); + } + for (; $i < @ARGV; $i++) { + prune_remote($ARGV[$i], $ls_remote); + } +} elsif ($ARGV[0] eq 'add') { if (@ARGV != 3) { print STDERR "Usage: git remote add \n"; @@ -281,5 +317,6 @@ else { print STDERR "Usage: git remote\n"; print STDERR " git remote add \n"; print STDERR " git remote show \n"; + print STDERR " git remote prune \n"; exit(1); } From 625e9421df6317a015abeb8d51c6dbb937d99880 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 1 Feb 2007 15:52:22 -0500 Subject: [PATCH 362/548] Cleanup prepare_packed_git_one to reuse install_packed_git. There is little point in having the linked list insertion code appearing in install_packed_git, and then again just 30 lines further down in the same file. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- sha1_file.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sha1_file.c b/sha1_file.c index 498665e50c..a42f94ac94 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -790,8 +790,7 @@ static void prepare_packed_git_one(char *objdir, int local) p = add_packed_git(path, len + namelen, local); if (!p) continue; - p->next = packed_git; - packed_git = p; + install_packed_git(p); } closedir(dir); } From 54a15a8df269315adf7110a87391bd5bef7e90f0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 1 Feb 2007 15:52:27 -0500 Subject: [PATCH 363/548] Correct comment in prepare_packed_git_one. After staring at the comment and the associated for loop, I realized the comment was completely bogus. The section of code its talking about is trying to avoid duplicate mapping of the same packfile. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- sha1_file.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sha1_file.c b/sha1_file.c index a42f94ac94..29c260cf65 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -779,7 +779,7 @@ static void prepare_packed_git_one(char *objdir, int local) if (!has_extension(de->d_name, ".idx")) continue; - /* we have .idx. Is it a file we can map? */ + /* Don't reopen a pack we already have. */ strcpy(path + len, de->d_name); for (p = packed_git; p; p = p->next) { if (!memcmp(path, p->pack_name, len + namelen - 4)) @@ -787,6 +787,9 @@ static void prepare_packed_git_one(char *objdir, int local) } if (p) continue; + /* See if it really is a valid .idx file with corresponding + * .pack file that we can map. + */ p = add_packed_git(path, len + namelen, local); if (!p) continue; From 072db2789c21314cfbf43215186341d0b2b47523 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 1 Feb 2007 15:52:33 -0500 Subject: [PATCH 364/548] Refactor open_packed_git to return an error code. Because I want to reuse open_packed_git in a context where I don't want the process to die if the packfile in question is bogus, I'm changing its behavior to return error("...") rather than die("...") when it detects something is wrong with the packfile it was given. Right now we still must die out of use_pack should open_packed_git fail, as none of use_pack's callers are prepared to handle a failure from that function. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- sha1_file.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/sha1_file.c b/sha1_file.c index 29c260cf65..eec4f418bc 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -552,7 +552,7 @@ void unuse_pack(struct pack_window **w_cursor) } } -static void open_packed_git(struct packed_git *p) +static int open_packed_git(struct packed_git *p) { struct stat st; struct pack_header hdr; @@ -562,49 +562,50 @@ static void open_packed_git(struct packed_git *p) p->pack_fd = open(p->pack_name, O_RDONLY); if (p->pack_fd < 0 || fstat(p->pack_fd, &st)) - die("packfile %s cannot be opened", p->pack_name); + return -1; /* If we created the struct before we had the pack we lack size. */ if (!p->pack_size) { if (!S_ISREG(st.st_mode)) - die("packfile %s not a regular file", p->pack_name); + return error("packfile %s not a regular file", p->pack_name); p->pack_size = st.st_size; } else if (p->pack_size != st.st_size) - die("packfile %s size changed", p->pack_name); + return error("packfile %s size changed", p->pack_name); /* We leave these file descriptors open with sliding mmap; * there is no point keeping them open across exec(), though. */ fd_flag = fcntl(p->pack_fd, F_GETFD, 0); if (fd_flag < 0) - die("cannot determine file descriptor flags"); + return error("cannot determine file descriptor flags"); fd_flag |= FD_CLOEXEC; if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1) - die("cannot set FD_CLOEXEC"); + return error("cannot set FD_CLOEXEC"); /* Verify we recognize this pack file format. */ if (read_in_full(p->pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr)) - die("file %s is far too short to be a packfile", p->pack_name); + return error("file %s is far too short to be a packfile", p->pack_name); if (hdr.hdr_signature != htonl(PACK_SIGNATURE)) - die("file %s is not a GIT packfile", p->pack_name); + return error("file %s is not a GIT packfile", p->pack_name); if (!pack_version_ok(hdr.hdr_version)) - die("packfile %s is version %u and not supported" + return error("packfile %s is version %u and not supported" " (try upgrading GIT to a newer version)", p->pack_name, ntohl(hdr.hdr_version)); /* Verify the pack matches its index. */ if (num_packed_objects(p) != ntohl(hdr.hdr_entries)) - die("packfile %s claims to have %u objects" + return error("packfile %s claims to have %u objects" " while index size indicates %u objects", p->pack_name, ntohl(hdr.hdr_entries), num_packed_objects(p)); if (lseek(p->pack_fd, p->pack_size - sizeof(sha1), SEEK_SET) == -1) - die("end of packfile %s is unavailable", p->pack_name); + return error("end of packfile %s is unavailable", p->pack_name); if (read_in_full(p->pack_fd, sha1, sizeof(sha1)) != sizeof(sha1)) - die("packfile %s signature is unavailable", p->pack_name); + return error("packfile %s signature is unavailable", p->pack_name); idx_sha1 = ((unsigned char *)p->index_base) + p->index_size - 40; if (hashcmp(sha1, idx_sha1)) - die("packfile %s does not match index", p->pack_name); + return error("packfile %s does not match index", p->pack_name); + return 0; } static int in_window(struct pack_window *win, unsigned long offset) @@ -627,8 +628,8 @@ unsigned char* use_pack(struct packed_git *p, { struct pack_window *win = *w_cursor; - if (p->pack_fd == -1) - open_packed_git(p); + if (p->pack_fd == -1 && open_packed_git(p)) + die("packfile %s cannot be accessed", p->pack_name); /* Since packfiles end in a hash of their content and its * pointless to ask for an offset into the middle of that From c715f783696346ff325dbdb88a5db69825f0d672 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 1 Feb 2007 15:52:38 -0500 Subject: [PATCH 365/548] Don't find objects in packs which aren't available anymore. Matthias Lederhofer identified a race condition where a Git reader process was able to locate an object in a packed_git index, but was then preempted while a `git repack -a -d` ran and completed. By the time the reader was able to seek in the packfile to get the object data, the packfile no longer existed on disk. In this particular case the reader process did not attempt to open the packfile before it was deleted, so it did not already have the pack_fd field popuplated. With the packfile itself gone, there was no way for the reader to open it and fetch the data. I'm fixing the race condition by teaching find_pack_entry to ignore a packed_git whose packfile is not currently open and which cannot be opened. If none of the currently known packs can supply the object, we will return 0 and the caller will decide the object is not available. If this is the first attempt at finding an object, the caller will reprepare_packed_git and try again. If it was the second attempt, the caller will typically return NULL back, and an error message about a missing object will be reported. This patch does not address the situation of a reader which is being starved out by a tight sequence of `git repack -a -d` runs. In this particular case the reader will try twice, probably fail both times, and declare the object in question cannot be found. As it is highly unlikely that a real world `git repack -a -d` can complete faster than a reader can open a packfile, so I don't think this is a huge concern. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- sha1_file.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sha1_file.c b/sha1_file.c index eec4f418bc..2eff14ac87 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1408,6 +1408,18 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, cons } offset = find_pack_entry_one(sha1, p); if (offset) { + /* + * We are about to tell the caller where they can + * locate the requested object. We better make + * sure the packfile is still here and can be + * accessed before supplying that answer, as + * it may have been deleted since the index + * was loaded! + */ + if (p->pack_fd == -1 && open_packed_git(p)) { + error("packfile %s cannot be accessed", p->pack_name); + continue; + } e->offset = offset; e->p = p; hashcpy(e->sha1, sha1); From aacd404e775ad73188ae9157041d7cc530d5625c Mon Sep 17 00:00:00 2001 From: Mike Coleman Date: Fri, 2 Feb 2007 00:25:30 -0600 Subject: [PATCH 366/548] Fix some documentation typos and grammar Also suggest user manual mention .gitignore. Signed-off-by: Michael Coleman Signed-off-by: Junio C Hamano --- Documentation/core-tutorial.txt | 6 +++--- Documentation/user-manual.txt | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt index 86a9c7521a..1cd834b0ff 100644 --- a/Documentation/core-tutorial.txt +++ b/Documentation/core-tutorial.txt @@ -624,7 +624,7 @@ name for the state at that point. Copying repositories -------------------- -git repositories are normally totally self-sufficient and relocatable +git repositories are normally totally self-sufficient and relocatable. Unlike CVS, for example, there is no separate notion of "repository" and "working tree". A git repository normally *is* the working tree, with the local git information hidden in the `.git` @@ -1118,7 +1118,7 @@ You could do without using any branches at all, by keeping as many local repositories as you would like to have branches, and merging between them with `git pull`, just like you merge between branches. The advantage of this approach is -that it lets you keep set of files for each `branch` checked +that it lets you keep a set of files for each `branch` checked out and you may find it easier to switch back and forth if you juggle multiple lines of development simultaneously. Of course, you will pay the price of more disk usage to hold @@ -1300,7 +1300,7 @@ differences since stage 2 (i.e. your version). Publishing your work -------------------- -So we can use somebody else's work from a remote repository; but +So, we can use somebody else's work from a remote repository, but how can *you* prepare a repository to let other people pull from it? diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index b6916d11b2..6576625fa0 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -398,7 +398,7 @@ branch name, but this longer name can also be useful. Most importantly, it is a globally unique name for this commit: so if you tell somebody else the object name (for example in email), then you are guaranteed that name will refer to the same commit in their repository -that you it does in yours (assuming their repository has that commit at +that it does in yours (assuming their repository has that commit at all). Understanding history: commits, parents, and reachability @@ -617,7 +617,7 @@ the relationships between these snapshots. Git provides extremely flexible and fast tools for exploring the history of a project. -We start with one specialized tool which is useful for finding the +We start with one specialized tool that is useful for finding the commit that introduced a bug into a project. How to use bisect to find a regression @@ -1492,7 +1492,7 @@ dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5 ... ------------------------------------------------- -and watch for output that mentions "dangling commits". You can examine +You can examine one of those dangling commits with, for example, ------------------------------------------------ @@ -2923,6 +2923,8 @@ Think about how to create a clear chapter dependency graph that will allow people to get to important topics without necessarily reading everything in between. +Say something about .gitignore. + Scan Documentation/ for other stuff left out; in particular: howto's some of technical/? From b6936205e73c058784288d21d1937e5bba26b91b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 2 Feb 2007 05:10:25 -0800 Subject: [PATCH 367/548] Disallow invalid --pretty= abbreviations --pretty=o is a valid abbreviation, --pretty=omfg is not Noticed by: Nicolas Vilz Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano --- commit.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/commit.c b/commit.c index 9b2b842e7d..3e8c87294b 100644 --- a/commit.c +++ b/commit.c @@ -47,7 +47,8 @@ enum cmit_fmt get_commit_format(const char *arg) if (*arg == '=') arg++; for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) { - if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len)) + if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) && + !strncmp(arg, cmt_fmts[i].n, strlen(arg))) return cmt_fmts[i].v; } From 0d18e41e008e9ac1f371d090e2e1b85481221796 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Fri, 2 Feb 2007 23:56:08 +0000 Subject: [PATCH 368/548] doc: hooks.txt said post-commit default sends an email, it doesn't The default post-commit hook is actually empty; it is the update hook that sends an email. This patch corrects hooks.txt to reflect that. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano --- Documentation/hooks.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt index e3b76f96eb..b083290d12 100644 --- a/Documentation/hooks.txt +++ b/Documentation/hooks.txt @@ -90,9 +90,6 @@ parameter, and is invoked after a commit is made. This hook is meant primarily for notification, and cannot affect the outcome of `git-commit`. -The default 'post-commit' hook, when enabled, demonstrates how to -send out a commit notification e-mail. - update ------ @@ -130,6 +127,8 @@ The standard output of this hook is sent to `stderr`, so if you want to report something to the `git-send-pack` on the other end, you can simply `echo` your messages. +The default 'update' hook, when enabled, demonstrates how to +send out a notification e-mail. post-update ----------- From 3cf8b462d2dbe78233bba5c0765ecaa2c6b6cd99 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 2 Feb 2007 03:00:03 -0500 Subject: [PATCH 369/548] Don't leak file descriptors from unavailable pack files. If open_packed_git failed it may have been because the packfile actually exists and is readable, but some sort of verification did not pass. In this case open_packed_git left pack_fd filled in, as the file descriptor is valid. We don't want to leak the file descriptor, nor do we want to allow someone in the future to use this packed_git. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- sha1_file.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/sha1_file.c b/sha1_file.c index 2eff14ac87..45e410e883 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -552,7 +552,11 @@ void unuse_pack(struct pack_window **w_cursor) } } -static int open_packed_git(struct packed_git *p) +/* + * Do not call this directly as this leaks p->pack_fd on error return; + * call open_packed_git() instead. + */ +static int open_packed_git_1(struct packed_git *p) { struct stat st; struct pack_header hdr; @@ -608,6 +612,17 @@ static int open_packed_git(struct packed_git *p) return 0; } +static int open_packed_git(struct packed_git *p) +{ + if (!open_packed_git_1(p)) + return 0; + if (p->pack_fd != -1) { + close(p->pack_fd); + p->pack_fd = -1; + } + return -1; +} + static int in_window(struct pack_window *win, unsigned long offset) { /* We must promise at least 20 bytes (one hash) after the From 953202a3fd68f84210cfe9bf102c534ac3ee40e4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 2 Feb 2007 22:19:17 -0800 Subject: [PATCH 370/548] Tutorial: fix asciidoc formatting of "git add" section. Signed-off-by: Junio C Hamano --- Documentation/tutorial.txt | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt index adb1e32750..ea3418909e 100644 --- a/Documentation/tutorial.txt +++ b/Documentation/tutorial.txt @@ -101,27 +101,27 @@ want to commit together. This can be done in a few different ways: 1) By using 'git add ...' - This can be performed multiple times before a commit. Note that this - is not only for adding new files. Even modified files must be - added to the set of changes about to be committed. The "git status" - command gives you a summary of what is included so far for the - next commit. When done you should use the 'git commit' command to - make it real. +This can be performed multiple times before a commit. Note that this +is not only for adding new files. Even modified files must be +added to the set of changes about to be committed. The "git status" +command gives you a summary of what is included so far for the +next commit. When done you should use the 'git commit' command to +make it real. - Note: don't forget to 'add' a file again if you modified it after the - first 'add' and before 'commit'. Otherwise only the previous added - state of that file will be committed. This is because git tracks - content, so what you're really 'add'ing to the commit is the *content* - of the file in the state it is in when you 'add' it. +Note: don't forget to 'add' a file again if you modified it after the +first 'add' and before 'commit'. Otherwise only the previous added +state of that file will be committed. This is because git tracks +content, so what you're really 'add'ing to the commit is the *content* +of the file in the state it is in when you 'add' it. 2) By using 'git commit -a' directly - This is a quick way to automatically 'add' the content from all files - that were modified since the previous commit, and perform the actual - commit without having to separately 'add' them beforehand. This will - not add content from new files i.e. files that were never added before. - Those files still have to be added explicitly before performing a - commit. +This is a quick way to automatically 'add' the content from all files +that were modified since the previous commit, and perform the actual +commit without having to separately 'add' them beforehand. This will +not add content from new files i.e. files that were never added before. +Those files still have to be added explicitly before performing a +commit. But here's a twist. If you do 'git commit ...' then only the changes belonging to those explicitly specified files will be From bf3478de973607ebe5b7689fa76e05c3f344aabd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 2 Feb 2007 22:55:07 -0800 Subject: [PATCH 371/548] Tutorial-2: Adjust git-status output to recent reality. Signed-off-by: Junio C Hamano --- Documentation/tutorial-2.txt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt index f363d17f0b..8d89992712 100644 --- a/Documentation/tutorial-2.txt +++ b/Documentation/tutorial-2.txt @@ -352,24 +352,23 @@ situation: ------------------------------------------------ $ git status -# -# Added but not yet committed: -# (will commit) +# On branch master +# Changes to be committed: +# (use "git reset HEAD ..." to unstage) # # new file: closing.txt # -# -# Changed but not added: -# (use "git add file1 file2" to include for commit) +# Changed but not updated: +# (use "git add ..." to update what will be committed) # # modified: file.txt # ------------------------------------------------ Since the current state of closing.txt is cached in the index file, -it is listed as "added but not yet committed". Since file.txt has +it is listed as "Changes to be committed". Since file.txt has changes in the working directory that aren't reflected in the index, -it is marked "changed but not added". At this point, running "git +it is marked "changed but not updated". At this point, running "git commit" would create a commit that added closing.txt (with its new contents), but that didn't modify file.txt. From 505739f6c0c67b7426ffbc723734c794b0a810d9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 2 Feb 2007 23:17:34 -0800 Subject: [PATCH 372/548] core-tutorial: http reference link fix Signed-off-by: Junio C Hamano --- Documentation/core-tutorial.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt index 1cd834b0ff..9c28bea62e 100644 --- a/Documentation/core-tutorial.txt +++ b/Documentation/core-tutorial.txt @@ -1469,8 +1469,8 @@ Working with Others Although git is a truly distributed system, it is often convenient to organize your project with an informal hierarchy of developers. Linux kernel development is run this way. There -is a nice illustration (page 17, "Merges to Mainline") in Randy -Dunlap's presentation (`http://tinyurl.com/a2jdg`). +is a nice illustration (page 17, "Merges to Mainline") in +link:http://tinyurl.com/a2jdg[Randy Dunlap's presentation]. It should be stressed that this hierarchy is purely *informal*. There is nothing fundamental in git that enforces the "chain of From eb8381c88518b10d683a29deea1d43ed671f14ec Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 3 Feb 2007 13:25:43 -0500 Subject: [PATCH 373/548] scan reflogs independently from refs Currently, the search for all reflogs depends on the existence of corresponding refs under the .git/refs/ directory. Let's scan the .git/logs/ directory directly instead. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- builtin-reflog.c | 7 ++----- fsck-objects.c | 9 +++++++-- reachable.c | 4 ++-- refs.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ refs.h | 6 ++++++ 5 files changed, 67 insertions(+), 9 deletions(-) diff --git a/builtin-reflog.c b/builtin-reflog.c index b6612a90ed..bfb169ac04 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -245,14 +245,11 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, char *log_file, *newlog_path = NULL; int status = 0; - if (strncmp(ref, "refs/", 5)) - return error("not a ref '%s'", ref); - memset(&cb, 0, sizeof(cb)); /* we take the lock for the ref itself to prevent it from * getting updated. */ - lock = lock_ref_sha1(ref + 5, sha1); + lock = lock_any_ref_for_update(ref, sha1); if (!lock) return error("cannot lock ref '%s'", ref); log_file = xstrdup(git_path("logs/%s", ref)); @@ -353,7 +350,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) } if (do_all) - status |= for_each_ref(expire_reflog, &cb); + status |= for_each_reflog(expire_reflog, &cb); while (i < argc) { const char *ref = argv[i++]; unsigned char sha1[20]; diff --git a/fsck-objects.c b/fsck-objects.c index ecfb014fff..c9b4a39807 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -477,6 +477,12 @@ static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, return 0; } +static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, int flag, void *cb_data) +{ + for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL); + return 0; +} + static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct object *obj; @@ -495,14 +501,13 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f obj->used = 1; mark_reachable(obj, REACHABLE); - for_each_reflog_ent(refname, fsck_handle_reflog_ent, NULL); - return 0; } static void get_default_heads(void) { for_each_ref(fsck_handle_ref, NULL); + for_each_reflog(fsck_handle_reflog, NULL); /* * Not having any default heads isn't really fatal, but diff --git a/reachable.c b/reachable.c index a6a334822a..01760d7046 100644 --- a/reachable.c +++ b/reachable.c @@ -188,9 +188,9 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog) /* Add all external refs */ for_each_ref(add_one_ref, revs); - /* Add all reflog info from refs */ + /* Add all reflog info */ if (mark_reflog) - for_each_ref(add_one_reflog, revs); + for_each_reflog(add_one_reflog, revs); /* * Set up the revision walk - this will move all commits diff --git a/refs.c b/refs.c index 4a523086e3..da09e434c7 100644 --- a/refs.c +++ b/refs.c @@ -1201,3 +1201,53 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) return ret; } +static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data) +{ + DIR *dir = opendir(git_path("logs/%s", base)); + int retval = errno; + + if (dir) { + struct dirent *de; + int baselen = strlen(base); + char *log = xmalloc(baselen + 257); + + memcpy(log, base, baselen); + if (baselen && base[baselen-1] != '/') + log[baselen++] = '/'; + + while ((de = readdir(dir)) != NULL) { + struct stat st; + int namelen; + + if (de->d_name[0] == '.') + continue; + namelen = strlen(de->d_name); + if (namelen > 255) + continue; + if (has_extension(de->d_name, ".lock")) + continue; + memcpy(log + baselen, de->d_name, namelen+1); + if (stat(git_path("logs/%s", log), &st) < 0) + continue; + if (S_ISDIR(st.st_mode)) { + retval = do_for_each_reflog(log, fn, cb_data); + } else { + unsigned char sha1[20]; + if (!resolve_ref(log, sha1, 0, NULL)) + retval = error("bad ref for %s", log); + else + retval = fn(log, sha1, 0, cb_data); + } + if (retval) + break; + } + free(log); + closedir(dir); + } + return retval; +} + +int for_each_reflog(each_ref_fn fn, void *cb_data) +{ + return do_for_each_reflog("", fn, cb_data); +} diff --git a/refs.h b/refs.h index 94a58b41fb..acedffc0e4 100644 --- a/refs.h +++ b/refs.h @@ -48,6 +48,12 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *); int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data); +/* + * Calls the specified function for each reflog file until it returns nonzero, + * and returns the value + */ +extern int for_each_reflog(each_ref_fn, void *); + /** Returns 0 if target has the right format for a ref. **/ extern int check_ref_format(const char *target); From 5f856dd47dacb30fb9f605b4b7e1fa577ada7d26 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 19 Jan 2007 11:51:29 -0800 Subject: [PATCH 374/548] fix reflog entries for "git-branch" Even when -l is not given from the command line, the repository may have the configuration variable core.logallrefupdates set, or an old-timer might have done ": >.git/logs/refs/heads/new" before running "git branch new". In these cases, the code gave an uninitialized msg[] from the stack to be written out as the reflog message. This also passes a different message when '-f' option is used. Saying "git branch -f branch some-commit" is a moral equilvalent of doing "git-reset some-commit" while on the branch. Signed-off-by: Junio C Hamano --- builtin-branch.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/builtin-branch.c b/builtin-branch.c index d60690bb08..eaff54ec5f 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -316,6 +316,7 @@ static void create_branch(const char *name, const char *start_name, struct commit *commit; unsigned char sha1[20]; char ref[PATH_MAX], msg[PATH_MAX + 20]; + int forcing = 0; snprintf(ref, sizeof ref, "refs/heads/%s", name); if (check_ref_format(ref)) @@ -326,6 +327,7 @@ static void create_branch(const char *name, const char *start_name, die("A branch named '%s' already exists.", name); else if (!is_bare_repository() && !strcmp(head, name)) die("Cannot force update the current branch."); + forcing = 1; } if (start_sha1) @@ -342,11 +344,15 @@ static void create_branch(const char *name, const char *start_name, if (!lock) die("Failed to lock ref for update: %s.", strerror(errno)); - if (reflog) { + if (reflog) log_all_ref_updates = 1; + + if (forcing) + snprintf(msg, sizeof msg, "branch: Reset from %s", + start_name); + else snprintf(msg, sizeof msg, "branch: Created from %s", start_name); - } if (write_ref_sha1(lock, sha1, msg) < 0) die("Failed to write ref: %s.", strerror(errno)); From 23913dc713a4f5b5228258919769ff1ab0a74a2b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 19 Jan 2007 17:12:11 -0800 Subject: [PATCH 375/548] honor GIT_REFLOG_ACTION in git-commit This allows git-cherry-pick and git-revert to properly identify themselves in the resulting reflog entries. Earlier they were recorded as what git-commit has done. Signed-off-by: Junio C Hamano --- git-commit.sh | 3 ++- git-revert.sh | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/git-commit.sh b/git-commit.sh index dc0fc3b679..ec506d956f 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -528,6 +528,7 @@ else rloga='commit (initial)' current='' fi +set_reflog_action "$rloga" if test -z "$no_edit" then @@ -602,7 +603,7 @@ then fi && commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) && rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) && - git-update-ref -m "$rloga: $rlogm" HEAD $commit "$current" && + git-update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" && rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" && if test -f "$NEXT_INDEX" then diff --git a/git-revert.sh b/git-revert.sh index 866d622d23..49f00321b2 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -54,6 +54,8 @@ do shift done +set_reflog_action "$me" + test "$me,$replay" = "revert,t" && usage case "$no_commit" in From a9d1836b100d988bb292086eeab1ab826b3959f1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 2 Feb 2007 22:40:49 -0800 Subject: [PATCH 376/548] Why is it bad to rewind a branch that has already been pushed out? I was reading the tutorial and noticed that we say this: Also, don't use "git reset" on a publicly-visible branch that other developers pull from, as git will be confused by history that disappears in this way. I do not think this is a good explanation. For example, if we do this: (1) I build a series and push it out. ---o---o---o---j (2) Alice clones from me, and builds two commits on top of it. ---o---o---o---j---a---a (3) I rewind one and build a few, and push them out. ---o---o---o...j \ h---h---h---h (4) Alice pulls from me again: ---o---o---o---j---a---a---* \ / h---h---h---h Contrary to the description, git will happily have Alice merge between the two branches, and never gets confused. Maybe I did not want to have 'j' because it was an incomplete solution to some problem, and Alice may have fixed it up with her changes, while I abandoned that approach I started with 'j', and worked on something completely unrelated in the four 'h' commits. In such a case, the merge Alice would make would be very sensible, and after she makes the merge if I pull from her, the world will be perfect. I started something with 'j' and dropped the ball, Alice picked it up and perfected it while I went on to work on something else with 'h'. This would be a perfect example of distributed parallel collaboration. There is nothing confused about it. The case the rewinding becomes problematic is if the work done in 'h' tries to solve the same problem as 'j' tried to solve in a different way. Then the merge forced on Alice would make her pick between my previous attempt with her fixups (j+a) and my second attempt (h). If 'a' commits were to fix up what 'j' started, presumably Alice already studied and knows enough about the problem so she should be able to make an informed decision to pick between what 'j+a' and 'h' do. A lot worse case is if Alice's work is not at all related to what 'j' wanted to do (she did not mean to pick up from where I left off -- she just wanted to work on something different). Then she would not be familiar enough with what 'j' and 'h' tried to achieve, and I'd be forcing her to pick between the two. Of course if she can make the right decision, then again that is a perfect example of distributed collaboration, but that does not change the fact that I'd be forcing her to clean up my mess. Signed-off-by: Junio C Hamano --- Documentation/tutorial.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt index ea3418909e..5fc5be5a28 100644 --- a/Documentation/tutorial.txt +++ b/Documentation/tutorial.txt @@ -458,9 +458,9 @@ $ git reset --hard HEAD^ # reset your current branch and working Be careful with that last command: in addition to losing any changes in the working directory, it will also remove all later commits from this branch. If this branch is the only branch containing those -commits, they will be lost. (Also, don't use "git reset" on a -publicly-visible branch that other developers pull from, as git will -be confused by history that disappears in this way.) +commits, they will be lost. Also, don't use "git reset" on a +publicly-visible branch that other developers pull from, as it will +force needless merges on other developers to clean up the history. The git grep command can search for strings in any version of your project, so From 3b0f5e88ee039e96822d7d0127a59d78936c222f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 3 Feb 2007 12:37:54 -0800 Subject: [PATCH 377/548] combine-diff: special case --unified=0 Even when --unified=0 is given, the main loop to show the combined textual diff needs to handle a line that is unchanged but has lines that were deleted relative to a parent before it (because that is where the lost lines hang). However, such a line should not be emitted in the final output. Signed-off-by: Junio C Hamano --- combine-diff.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/combine-diff.c b/combine-diff.c index 29d0c9cf95..a5f2c8dd4a 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -482,11 +482,11 @@ static int make_hunks(struct sline *sline, unsigned long cnt, return has_interesting; } -static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long l1, int n) +static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long l1, int n, unsigned long null_context) { l0 = sline[l0].p_lno[n]; l1 = sline[l1].p_lno[n]; - printf(" -%lu,%lu", l0, l1-l0); + printf(" -%lu,%lu", l0, l1-l0-null_context); } static int hunk_comment_line(const char *bol) @@ -519,6 +519,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, unsigned long hunk_end; unsigned long rlines; const char *hunk_comment = NULL; + unsigned long null_context = 0; while (lno <= cnt && !(sline[lno].flag & mark)) { if (hunk_comment_line(sline[lno].bol)) @@ -535,10 +536,28 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, rlines = hunk_end - lno; if (cnt < hunk_end) rlines--; /* pointing at the last delete hunk */ + + if (!context) { + /* + * Even when running with --unified=0, all + * lines in the hunk needs to be processed in + * the loop below in order to show the + * deletion recorded in lost_head. However, + * we do not want to show the resulting line + * with all blank context markers in such a + * case. Compensate. + */ + unsigned long j; + for (j = lno; j < hunk_end; j++) + if (!(sline[j].flag & (mark-1))) + null_context++; + rlines -= null_context; + } + fputs(c_frag, stdout); for (i = 0; i <= num_parent; i++) putchar(combine_marker); for (i = 0; i < num_parent; i++) - show_parent_lno(sline, lno, hunk_end, i); + show_parent_lno(sline, lno, hunk_end, i, null_context); printf(" +%lu,%lu ", lno+1, rlines); for (i = 0; i <= num_parent; i++) putchar(combine_marker); @@ -578,8 +597,15 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, if (cnt < lno) break; p_mask = 1; - if (!(sl->flag & (mark-1))) + if (!(sl->flag & (mark-1))) { + /* + * This sline was here to hang the + * lost lines in front of it. + */ + if (!context) + continue; fputs(c_plain, stdout); + } else fputs(c_new, stdout); for (j = 0; j < num_parent; j++) { From 9673a0b182fdb39e6a00e2cbdcfacee769201053 Mon Sep 17 00:00:00 2001 From: Pavel Roskin Date: Sat, 3 Feb 2007 22:01:04 -0500 Subject: [PATCH 378/548] git-config --rename-section could rename wrong section The "git-config --rename-section" implementation would match sections that are substrings of the section name to be renamed. Signed-off-by: Pavel Roskin Signed-off-by: Junio C Hamano --- config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.c b/config.c index c08c66890f..d82107124a 100644 --- a/config.c +++ b/config.c @@ -896,7 +896,7 @@ int git_config_rename_section(const char *old_name, const char *new_name) if (buf[i] != old_name[j++]) break; } - if (buf[i] == ']') { + if (buf[i] == ']' && old_name[j] == 0) { /* old_name matches */ ret++; store.baselen = strlen(new_name); From 0f3908758947b2279f5d2d4a294653d76a62d468 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 3 Feb 2007 23:02:59 -0500 Subject: [PATCH 379/548] Cleanup subcommand documentation for git-remote. Jakub Narebski pointed out the positional notation in git-remote's documentation was very confusing, especially now that we have 3 supported subcommands. Instead of referring to subcommands by position, refer to them by name. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-remote.txt | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 817651eaa4..a60c31a315 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -19,18 +19,33 @@ DESCRIPTION Manage the set of repositories ("remotes") whose branches you track. -With no arguments, shows a list of existing remotes. -In the second form, adds a remote named for the repository at +COMMANDS +-------- + +With no arguments, shows a list of existing remotes. Several +subcommands are available to perform operations on the remotes. + +'add':: + +Adds a remote named for the repository at . The command `git fetch ` can then be used to create and update remote-tracking branches /. -In the third form, gives some information about the remote . +'show':: -In the fourth form, deletes all stale tracking branches under . +Gives some information about the remote . + +'prune':: + +Deletes all stale tracking branches under . These stale branches have already been removed from the remote repository referenced by , but are still locally available in "remotes/". + +DISCUSSION +---------- + The remote configuration is achieved using the `remote.origin.url` and `remote.origin.fetch` configuration variables. (See gitlink:git-config[1]). From 3dff5379bf1e3fda5e5a84ca5813b0c0cfd51be7 Mon Sep 17 00:00:00 2001 From: Pavel Roskin Date: Sat, 3 Feb 2007 23:49:16 -0500 Subject: [PATCH 380/548] Assorted typo fixes Signed-off-by: Junio C Hamano --- Documentation/config.txt | 2 +- Documentation/diff-format.txt | 2 +- Documentation/git-send-pack.txt | 2 +- Documentation/git-svn.txt | 2 +- Documentation/gitk.txt | 2 +- Documentation/user-manual.txt | 10 +++++----- builtin-blame.c | 6 +++--- builtin-branch.c | 2 +- builtin-for-each-ref.c | 2 +- builtin-fsck.c | 2 +- daemon.c | 2 +- git-merge-resolve.sh | 2 +- git-quiltimport.sh | 2 +- gitweb/gitweb.perl | 2 +- merge-recursive.c | 8 ++++---- perl/Git.pm | 2 +- perl/private-Error.pm | 2 +- ppc/sha1ppc.S | 6 +++--- sha1_file.c | 2 +- t/t1004-read-tree-m-u-wf.sh | 2 +- t/t1020-subdirectory.sh | 6 +++--- t/t3800-mktag.sh | 4 ++-- t/t5000-tar-tree.sh | 2 +- t/t5600-clone-fail-cleanup.sh | 2 +- t/t9101-git-svn-props.sh | 2 +- templates/Makefile | 2 +- templates/hooks--update | 2 +- 27 files changed, 41 insertions(+), 41 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index e5e019fedd..4e650af01a 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -39,7 +39,7 @@ in the section header, like in example below: Subsection names can contain any characters except newline (doublequote '`"`' and backslash have to be escaped as '`\"`' and '`\\`', -respecitvely) and are case sensitive. Section header cannot span multiple +respectively) and are case sensitive. Section header cannot span multiple lines. Variables may belong directly to a section or to a given subsection. You can have `[section]` if you have `[section "subsection"]`, but you don't need to. diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index 883c1bb0a6..378e72f38f 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -159,7 +159,7 @@ or like this (when '--cc' option is used): deleted file mode , + The `mode ,..` line appears only if at least one of -the is diferent from the rest. Extended headers with +the is different from the rest. Extended headers with information about detected contents movement (renames and copying detection) are designed to work with diff of two and are not used by combined diff format. diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index 2f6267ce60..205bfd2d25 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -3,7 +3,7 @@ git-send-pack(1) NAME ---- -git-send-pack - Push objects over git protocol to another reposiotory +git-send-pack - Push objects over git protocol to another repository SYNOPSIS diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index aea4a6bf5f..6ce6a3944d 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -133,7 +133,7 @@ manually joining branches on commit. 'multi-init':: This command supports git-svnimport-like command-line syntax for - importing repositories that are layed out as recommended by the + importing repositories that are laid out as recommended by the SVN folks. This is a bit more tolerant than the git-svnimport command-line syntax and doesn't require the user to figure out where the repository URL ends and where the repository path diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index 5bdaa601f0..48c5894736 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -54,7 +54,7 @@ frequently used options. Limit commits to the ones touching files in the given paths. Note, to avoid ambiguity wrt. revision names use "--" to separate the paths - from any preceeding options. + from any preceding options. Examples -------- diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 6576625fa0..c5e9ea8a42 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -425,8 +425,8 @@ 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. -Undestanding history: History diagrams -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Understanding history: History diagrams +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We will sometimes represent git history using diagrams like the one below. Commits are shown as "o", and the links between them with @@ -806,7 +806,7 @@ display options. Note that git log starts with the most recent commit and works backwards through the parents; however, since git history can contain -multiple independant lines of development, the particular order that +multiple independent lines of development, the particular order that commits are listed in may be somewhat arbitrary. Generating diffs @@ -1075,7 +1075,7 @@ $ git commit ------------------------------------------------- and git will prompt you for a commit message and then create the new -commmit. Check to make sure it looks like what you expected with +commit. Check to make sure it looks like what you expected with ------------------------------------------------- $ git show @@ -2953,7 +2953,7 @@ Include cross-references to the glossary, where appropriate. Document shallow clones? See draft 1.5.0 release notes for some documentation. -Add a sectin on working with other version control systems, including +Add a section on working with other version control systems, including CVS, Subversion, and just imports of series of release tarballs. More details on gitweb? diff --git a/builtin-blame.c b/builtin-blame.c index 3033e9bdad..1c21204a2c 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -550,7 +550,7 @@ static void free_patch(struct patch *p) } /* - * Link in a new blame entry to the scorebord. Entries that cover the + * Link in a new blame entry to the scoreboard. Entries that cover the * same line range have been removed from the scoreboard previously. */ static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e) @@ -1392,7 +1392,7 @@ static void found_guilty_entry(struct blame_entry *ent) /* * The main loop -- while the scoreboard has lines whose true origin - * is still unknown, pick one brame_entry, and allow its current + * is still unknown, pick one blame_entry, and allow its current * suspect to pass blames to its parents. */ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) @@ -2001,7 +2001,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) /* * We have collected options unknown to us in argv[1..unk] * which are to be passed to revision machinery if we are - * going to do the "bottom" procesing. + * going to do the "bottom" processing. * * The remaining are: * diff --git a/builtin-branch.c b/builtin-branch.c index eaff54ec5f..9b68d3b69f 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -364,7 +364,7 @@ static void rename_branch(const char *oldname, const char *newname, int force) unsigned char sha1[20]; if (!oldname) - die("cannot rename the curren branch while not on any."); + die("cannot rename the current branch while not on any."); if (snprintf(oldref, sizeof(oldref), "refs/heads/%s", oldname) > sizeof(oldref)) die("Old branchname too long"); diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 9d5f266dc4..16c785f047 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -135,7 +135,7 @@ static const char *find_next(const char *cp) while (*cp) { if (*cp == '%') { /* %( is the start of an atom; - * %% is a quoteed per-cent. + * %% is a quoted per-cent. */ if (cp[1] == '(') return cp; diff --git a/builtin-fsck.c b/builtin-fsck.c index fec1cbd784..f6f1967449 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -117,7 +117,7 @@ static void check_unreachable_object(struct object *obj) /* * "!used" means that nothing at all points to it, including - * other unreacahble objects. In other words, it's the "tip" + * other unreachable objects. In other words, it's the "tip" * of some set of unreachable objects, usually a commit that * got dropped. * diff --git a/daemon.c b/daemon.c index 9590372214..2a20ca55cb 100644 --- a/daemon.c +++ b/daemon.c @@ -408,7 +408,7 @@ static void make_service_overridable(const char *name, int ena) { /* * Separate the "extra args" information as supplied by the client connection. - * Any resulting data is squirrelled away in the given interpolation table. + * Any resulting data is squirreled away in the given interpolation table. */ static void parse_extra_args(struct interp *table, char *extra_args, int buflen) { diff --git a/git-merge-resolve.sh b/git-merge-resolve.sh index 0a8ef216cb..75e1de49ac 100755 --- a/git-merge-resolve.sh +++ b/git-merge-resolve.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Linus Torvalds # Copyright (c) 2005 Junio C Hamano # -# Resolve two trees, using enhancd multi-base read-tree. +# Resolve two trees, using enhanced multi-base read-tree. # The first parameters up to -- are merge bases; the rest are heads. bases= head= remotes= sep_seen= diff --git a/git-quiltimport.sh b/git-quiltimport.sh index 2ae1f20c2e..671a5ff865 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -59,7 +59,7 @@ if ! [ -d "$QUILT_PATCHES" ] ; then exit 1 fi -# Temporay directories +# Temporary directories tmp_dir=.dotest tmp_msg="$tmp_dir/msg" tmp_patch="$tmp_dir/patch" diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b606c1d3e7..a2076a680c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -834,7 +834,7 @@ sub file_type_long { ## ---------------------------------------------------------------------- ## functions returning short HTML fragments, or transforming HTML fragments -## which don't beling to other sections +## which don't belong to other sections # format line of commit message. sub format_log_line_html { diff --git a/merge-recursive.c b/merge-recursive.c index fa320eb6b1..a5e68457f4 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -386,7 +386,7 @@ struct rename }; /* - * Get information of all renames which occured between 'o_tree' and + * Get information of all renames which occurred between 'o_tree' and * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and * 'b_tree') to be able to associate the correct cache entries with * the rename information. 'tree' is always equal to either a_tree or b_tree. @@ -1175,7 +1175,7 @@ static struct commit_list *reverse_commit_list(struct commit_list *list) /* * Merge the commits h1 and h2, return the resulting virtual - * commit object and a flag indicating the cleaness of the merge. + * commit object and a flag indicating the cleanness of the merge. */ static int merge(struct commit *h1, struct commit *h2, @@ -1222,8 +1222,8 @@ static int merge(struct commit *h1, /* * When the merge fails, the result contains files * with conflict markers. The cleanness flag is - * ignored, it was never acutally used, as result of - * merge_trees has always overwritten it: the commited + * ignored, it was never actually used, as result of + * merge_trees has always overwritten it: the committed * "conflicts" were already resolved. */ discard_cache(); diff --git a/perl/Git.pm b/perl/Git.pm index 5d1ccaa125..f2c156cde9 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -354,7 +354,7 @@ sub command_input_pipe { =item command_close_pipe ( PIPE [, CTX ] ) Close the C as returned from C, checking -whether the command finished successfuly. The optional C argument +whether the command finished successfully. The optional C argument is required if you want to see the command name in the error message, and it is the second value returned by C when called in array context. The call idiom is: diff --git a/perl/private-Error.pm b/perl/private-Error.pm index 8fff86699f..11e9cd9a02 100644 --- a/perl/private-Error.pm +++ b/perl/private-Error.pm @@ -781,7 +781,7 @@ that is a plain string. (Unless C<$Error::ObjectifyCallback> is modified) This variable holds a reference to a subroutine that converts errors that are plain strings to objects. It is used by Error.pm to convert textual -errors to objects, and can be overrided by the user. +errors to objects, and can be overridden by the user. It accepts a single argument which is a hash reference to named parameters. Currently the only named parameter passed is C<'text'> which is the text diff --git a/ppc/sha1ppc.S b/ppc/sha1ppc.S index 140cb53370..f132696ee7 100644 --- a/ppc/sha1ppc.S +++ b/ppc/sha1ppc.S @@ -18,7 +18,7 @@ * %r0 - temp * %r3 - argument (pointer to 5 words of SHA state) * %r4 - argument (pointer to data to hash) - * %r5 - Contant K in SHA round (initially number of blocks to hash) + * %r5 - Constant K in SHA round (initially number of blocks to hash) * %r6-%r10 - Working copies of SHA variables A..E (actually E..A order) * %r11-%r26 - Data being hashed W[]. * %r27-%r31 - Previous copies of A..E, for final add back. @@ -48,7 +48,7 @@ * E += ROTL(A,5) + F(B,C,D) + W[i] + K; B = ROTL(B,30) * Then the variables are renamed: (A,B,C,D,E) = (E,A,B,C,D). * - * Every 20 rounds, the function F() and the contant K changes: + * Every 20 rounds, the function F() and the constant K changes: * - 20 rounds of f0(b,c,d) = "bit wise b ? c : d" = (^b & d) + (b & c) * - 20 rounds of f1(b,c,d) = b^c^d = (b^d)^c * - 20 rounds of f2(b,c,d) = majority(b,c,d) = (b&d) + ((b^d)&c) @@ -57,7 +57,7 @@ * These are all scheduled for near-optimal performance on a G4. * The G4 is a 3-issue out-of-order machine with 3 ALUs, but it can only * *consider* starting the oldest 3 instructions per cycle. So to get - * maximum performace out of it, you have to treat it as an in-order + * maximum performance out of it, you have to treat it as an in-order * machine. Which means interleaving the computation round t with the * computation of W[t+4]. * diff --git a/sha1_file.c b/sha1_file.c index 45e410e883..1526a28095 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1163,7 +1163,7 @@ static unsigned long unpack_object_header(struct packed_git *p, /* use_pack() assures us we have [base, base + 20) available * as a range that we can look at at. (Its actually the hash - * size that is assurred.) With our object header encoding + * size that is assured.) With our object header encoding * the maximum deflated object size is 2^137, which is just * insane, so we know won't exceed what we have been given. */ diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh index 4f664f6adf..c11420a8b6 100755 --- a/t/t1004-read-tree-m-u-wf.sh +++ b/t/t1004-read-tree-m-u-wf.sh @@ -87,7 +87,7 @@ test_expect_success 'three-way not complaining on an untracked path in both' ' git-read-tree -m -u branch-point master side ' -test_expect_success 'three-way not cloberring a working tree file' ' +test_expect_success 'three-way not clobbering a working tree file' ' git reset --hard && rm -f file2 subdir/file2 file3 subdir/file3 && diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh index c090c96185..1e8f9e59df 100755 --- a/t/t1020-subdirectory.sh +++ b/t/t1020-subdirectory.sh @@ -106,21 +106,21 @@ test_expect_success 'read-tree' ' cmp ../one ../original.one ' -test_expect_success 'no file/rev ambuguity check inside .git' ' +test_expect_success 'no file/rev ambiguity check inside .git' ' cd $HERE && git commit -a -m 1 && cd $HERE/.git && git show -s HEAD ' -test_expect_success 'no file/rev ambuguity check inside a bare repo' ' +test_expect_success 'no file/rev ambiguity check inside a bare repo' ' cd $HERE && git clone -s --bare .git foo.git && cd foo.git && GIT_DIR=. git show -s HEAD ' # This still does not work as it should... -: test_expect_success 'no file/rev ambuguity check inside a bare repo' ' +: test_expect_success 'no file/rev ambiguity check inside a bare repo' ' cd $HERE && git clone -s --bare .git foo.git && cd foo.git && git show -s HEAD diff --git a/t/t3800-mktag.sh b/t/t3800-mktag.sh index ede4d42495..7c7e4335d6 100755 --- a/t/t3800-mktag.sh +++ b/t/t3800-mktag.sh @@ -172,7 +172,7 @@ EOF check_verify_failure 'verify tag-name check' ############################################################ -# 11. tagger line lable check #1 +# 11. tagger line label check #1 cat >tag.sig <tag.sig <> kw.c && - git commit -a -m "test keywoards ignoring" && + git commit -a -m "test keywords ignoring" && git-svn set-tree remotes/git-svn..mybranch && git pull . remotes/git-svn' diff --git a/templates/Makefile b/templates/Makefile index 9e1ae1a4e0..0eeee43feb 100644 --- a/templates/Makefile +++ b/templates/Makefile @@ -6,7 +6,7 @@ prefix ?= $(HOME) template_dir ?= $(prefix)/share/git-core/templates/ # DESTDIR= -# Shell quote (do not use $(call) to accomodate ancient setups); +# Shell quote (do not use $(call) to accommodate ancient setups); DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) template_dir_SQ = $(subst ','\'',$(template_dir)) diff --git a/templates/hooks--update b/templates/hooks--update index 4bd9d96ff9..d4253cbcfb 100644 --- a/templates/hooks--update +++ b/templates/hooks--update @@ -61,7 +61,7 @@ newrev_type=$(git-cat-file -t "$newrev") case "$refname","$newrev_type" in refs/tags/*,commit) - # un-annoted tag + # un-annotated tag refname_type="tag" short_refname=${refname##refs/tags/} if [ $allowunannotated != "true" ]; then From 183d79724f6c442f74309dd04c89d3cb48ded628 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 4 Feb 2007 00:45:54 -0500 Subject: [PATCH 381/548] Keep untracked files not involved in a merge. My earlier fix (8371234e) to delete renamed tracked files from the working directory also caused merge-recursive to delete untracked files that were in the working directory. The problem here is merge-recursive is deleting the working directory file without regard for which branch it was associated with. What we really want to do during a merge is to only delete files that were renamed by the branch we are merging into the current branch, and that are still tracked by the current branch. These files definitely don't belong in the working directory anymore. Anything else is either a merge conflict (already handled in other parts of the code) or a file that is untracked by the current branch and thus is not even participating in the merge. Its this latter class that must be left alone. For this fix to work we are now assuming that the first non-base argument passed to git-merge-recursive always corresponds to the working directory. This is already true for all in-tree callers of merge-recursive. This assumption is also supported by the long time usage message of " ... -- ", where "" is implied to be HEAD, which is generally assumed to be the current tree-ish. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- merge-recursive.c | 2 +- t/t6023-merge-rename-nocruft.sh | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/merge-recursive.c b/merge-recursive.c index a5e68457f4..a68fcc6f37 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -891,7 +891,7 @@ static int process_renames(struct path_list *a_renames, struct diff_filespec src_other, dst_other; int try_merge, stage = a_renames == renames1 ? 3: 2; - remove_file(1, ren1_src, index_only); + remove_file(1, ren1_src, index_only || stage == 3); hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); src_other.mode = ren1->src_entry->stages[stage].mode; diff --git a/t/t6023-merge-rename-nocruft.sh b/t/t6023-merge-rename-nocruft.sh index 69c66cf6fa..65be95fbaa 100755 --- a/t/t6023-merge-rename-nocruft.sh +++ b/t/t6023-merge-rename-nocruft.sh @@ -45,6 +45,7 @@ git add A M && git commit -m "initial has A and M" && git branch white && git branch red && +git branch blue && git checkout white && sed -e "/^g /s/.*/g : white changes a line/" B && @@ -58,6 +59,13 @@ echo created by red >R && git update-index --add R && git commit -m "red creates R" && +git checkout blue && +sed -e "/^o /s/.*/g : blue changes a line/" B && +rm -f A && +mv B A && +git update-index A && +git commit -m "blue modify A" && + git checkout master' # This test broke in 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae @@ -94,4 +102,38 @@ test_expect_success 'merge white into red (A->B,M->N)' \ return 0 ' +# This test broke in 8371234ecaaf6e14fe3f2082a855eff1bbd79ae9 +test_expect_success 'merge blue into white (A->B, mod A, A untracked)' \ +' + git checkout -b white-blue white && + echo dirty >A && + git merge blue && + git write-tree >/dev/null || { + echo "BAD: merge did not complete" + return 1 + } + + test -f A || { + echo "BAD: A does not exist in working directory" + return 1 + } + test `cat A` = dirty || { + echo "BAD: A content is wrong" + return 1 + } + test -f B || { + echo "BAD: B does not exist in working directory" + return 1 + } + test -f N || { + echo "BAD: N does not exist in working directory" + return 1 + } + test -f M && { + echo "BAD: M still exists in working directory" + return 1 + } + return 0 +' + test_done From 8d0fc48f27304ac1bc7abf802ec53fe66fedb15a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 4 Feb 2007 00:45:47 -0500 Subject: [PATCH 382/548] Default GIT_MERGE_VERBOSITY to 5 during tests. Its really nice to be able to run a test with -v and automatically see the "debugging" dump from merge-recursive, especially if we are actually trying to debug merge-recursive. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/test-lib.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/t/test-lib.sh b/t/test-lib.sh index 8e3ee6cd7b..37822fc13d 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -30,6 +30,8 @@ unset GIT_INDEX_FILE unset GIT_OBJECT_DIRECTORY unset SHA1_FILE_DIRECTORIES unset SHA1_FILE_DIRECTORY +GIT_MERGE_VERBOSITY=5 +export GIT_MERGE_VERBOSITY export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME export EDITOR VISUAL From f2eba66d4d1a664f40bcfd45e0b8a2670df222b3 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 3 Feb 2007 21:49:16 -0500 Subject: [PATCH 383/548] Enable HEAD@{...} and make it independent from the current branch Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- sha1_name.c | 62 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index de8caf8cf6..d77f77097f 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -235,22 +235,23 @@ static int ambiguous_path(const char *path, int len) return slash; } +static const char *ref_fmt[] = { + "%.*s", + "refs/%.*s", + "refs/tags/%.*s", + "refs/heads/%.*s", + "refs/remotes/%.*s", + "refs/remotes/%.*s/HEAD", + NULL +}; + int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) { - static const char *fmt[] = { - "%.*s", - "refs/%.*s", - "refs/tags/%.*s", - "refs/heads/%.*s", - "refs/remotes/%.*s", - "refs/remotes/%.*s/HEAD", - NULL - }; const char **p, *r; int refs_found = 0; *ref = NULL; - for (p = fmt; *p; p++) { + for (p = ref_fmt; *p; p++) { unsigned char sha1_from_ref[20]; unsigned char *this_result; @@ -266,6 +267,28 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) return refs_found; } +static int dwim_log(const char *str, int len, unsigned char *sha1, char **log) +{ + const char **p; + int logs_found = 0; + + *log = NULL; + for (p = ref_fmt; *p; p++) { + struct stat st; + char *path = mkpath(*p, len, str); + if (!stat(git_path("logs/%s", path), &st) && + S_ISREG(st.st_mode)) { + if (!logs_found++) { + *log = xstrdup(path); + resolve_ref(path, sha1, 0, NULL); + } + if (!warn_ambiguous_refs) + break; + } + } + return logs_found; +} + static int get_sha1_basic(const char *str, int len, unsigned char *sha1) { static const char *warning = "warning: refname '%.*s' is ambiguous.\n"; @@ -295,7 +318,9 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) if (!len && reflog_len) { /* allow "@{...}" to mean the current branch reflog */ refs_found = dwim_ref("HEAD", 4, sha1, &real_ref); - } else + } else if (reflog_len) + refs_found = dwim_log(str, len, sha1, &real_ref); + else refs_found = dwim_ref(str, len, sha1, &real_ref); if (!refs_found) @@ -310,21 +335,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) unsigned long co_time; int co_tz, co_cnt; - /* - * We'll have an independent reflog for "HEAD" eventually - * which won't be a synonym for the current branch reflog. - * In the mean time prevent people from getting used to - * such a synonym until the work is completed. - */ - if (len && !strncmp("HEAD", str, len) && - !strncmp(real_ref, "refs/", 5)) { - error("reflog for HEAD has not been implemented yet\n" - "Maybe you could try %s%s instead, " - "or just %s for current branch..", - strchr(real_ref+5, '/')+1, str+len, str+len); - exit(-1); - } - /* Is it asking for N-th entry, or approxidate? */ for (i = nth = 0; 0 <= nth && i < reflog_len; i++) { char ch = str[at+2+i]; From dc9195ac7830bdf08ee847ef6a385c0b8f673d69 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 3 Feb 2007 21:50:39 -0500 Subject: [PATCH 384/548] Let git-checkout always drop any detached head We used to refuse leaving a detached HEAD when it wasn't matching an existing ref so not to lose any commit that might have been performed while not on any branch (unless -f was provided). But this protection was completely bogus since it was still possible to move to HEAD^ while still remaining detached but losing the last commit anyway if there was one. Now that we have a proper reflog for HEAD it is best to simply remove that bogus (and admitedly annoying) protection and simply display the last HEAD position instead. If one wants to recover a lost detached state then it can be retrieved from the HEAD reflog. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- git-checkout.sh | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/git-checkout.sh b/git-checkout.sh index 2c8cbe43a6..14835a4aa9 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -164,22 +164,9 @@ If you want to create a new branch from this checkout, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b " fi -elif test -z "$oldbranch" && test -n "$branch" +elif test -z "$oldbranch" && test -z "$quiet" then - # Coming back... - if test -z "$force" - then - git show-ref -d -s | grep "$old" >/dev/null || { - echo >&2 \ -"You are not on any branch and switching to branch '$new_name' -may lose your changes. At this point, you can do one of two things: - (1) Decide it is Ok and say 'git checkout -f $new_name'; - (2) Start a new branch from the current commit, by saying - 'git checkout -b '. -Leaving your HEAD detached; not switching to branch '$new_name'." - exit 1; - } - fi + echo >&2 "Previous HEAD position was $old" fi if [ "X$old" = X ] From 632ac9fd12a2d4ff2c1a1fcd63492ce24315221f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 3 Feb 2007 23:31:47 -0800 Subject: [PATCH 385/548] show-branch -g: default to the current branch. Now we have a separate reflog on HEAD, show-branch -g without an explicit parameter defaults to the current branch, or HEAD when it is detached from branches. Signed-off-by: Junio C Hamano --- Documentation/git-show-branch.txt | 8 +++++--- builtin-show-branch.c | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index b38633c397..ba5313d51f 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git-show-branch' [--all] [--remotes] [--topo-order] [--current] [--more= | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [ | ]... -'git-show-branch' (-g|--reflog)[=[,]] [--list] +'git-show-branch' (-g|--reflog)[=[,]] [--list] [] DESCRIPTION ----------- @@ -97,11 +97,13 @@ OPTIONS will show the revisions given by "git rev-list {caret}master topic1 topic2" ---reflog[=[,]] :: +--reflog[=[,]] []:: Shows most recent ref-log entries for the given ref. If is given, entries going back from that entry. can be specified as count or date. - `-g` can be used as a short-hand for this option. + `-g` can be used as a short-hand for this option. When + no explicit parameter is given, it defaults to the + current branch (or `HEAD` if it is detached). Note that --more, --list, --independent and --merge-base options are mutually exclusive. diff --git a/builtin-show-branch.c b/builtin-show-branch.c index fa62e487b1..0d94e40df8 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -690,7 +690,10 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (ac == 0) { static const char *fake_av[2]; - fake_av[0] = "HEAD"; + const char *refname; + + refname = resolve_ref("HEAD", sha1, 1, NULL); + fake_av[0] = xstrdup(refname); fake_av[1] = NULL; av = fake_av; ac = 1; From 9f4cc6f76b8a15a89b85a43e0c0016a64d368709 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 4 Feb 2007 02:38:17 -0500 Subject: [PATCH 386/548] bash: Remove short option completions for branch/checkout/diff. The short options (-l, -f, -d) for git-branch are rather silly to include in the completion generation as these options must be fully typed out by the user and most users already know what the options are anyway, so including them in the suggested completions does not offer huge value. (The same goes for git-checkout and git-diff.) Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 83c69ecf48..971fefb05f 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -359,7 +359,7 @@ _git_apply () _git_branch () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } _git_cat_file () @@ -381,7 +381,7 @@ _git_cat_file () _git_checkout () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "-l -b $(__git_refs)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } _git_cherry_pick () @@ -421,7 +421,7 @@ _git_diff () _git_diff_tree () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "-r -p -M $(__git_refs)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } _git_fetch () From 2e3a430a9a976ec9324b33fef41dfeb55353a1ae Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 4 Feb 2007 02:38:21 -0500 Subject: [PATCH 387/548] bash: Classify cat-file and reflog as plumbing. Now that git-show is capable of displaying any file content from any revision and is the approved Porcelain-ish level method of doing so, cat-file should no longer be classified as a user-level utility by the bash completion package. I'm also classifying the new git-reflog command as plumbing for the time being as there are no subcommands which are really useful to the end-user. git-gc already invokes `git reflog expire --all`, which makes it rather unnecessary for the user to invoke it directly. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 971fefb05f..b87f96d9ea 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1,7 +1,7 @@ # # bash completion support for core Git. # -# Copyright (C) 2006 Shawn Pearce +# Copyright (C) 2006,2007 Shawn Pearce # Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/). # # The contained completion routines provide support for completing: @@ -235,6 +235,7 @@ __git_commands () for i in $(git help -a|egrep '^ ') do case $i in + cat-file) : plumbing;; check-ref-format) : plumbing;; commit-tree) : plumbing;; convert-objects) : plumbing;; @@ -258,6 +259,7 @@ __git_commands () peek-remote) : plumbing;; read-tree) : plumbing;; receive-pack) : plumbing;; + reflog) : plumbing;; rerere) : plumbing;; rev-list) : plumbing;; rev-parse) : plumbing;; @@ -362,22 +364,6 @@ _git_branch () COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } -_git_cat_file () -{ - local cur="${COMP_WORDS[COMP_CWORD]}" - case "${COMP_WORDS[0]},$COMP_CWORD" in - git-cat-file*,1) - COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur")) - ;; - git,2) - COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur")) - ;; - *) - __git_complete_file - ;; - esac -} - _git_checkout () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -802,7 +788,6 @@ _git () am) _git_am ;; apply) _git_apply ;; branch) _git_branch ;; - cat-file) _git_cat_file ;; checkout) _git_checkout ;; cherry-pick) _git_cherry_pick ;; commit) _git_commit ;; @@ -840,7 +825,6 @@ complete -o default -F _gitk gitk complete -o default -F _git_am git-am complete -o default -F _git_apply git-apply complete -o default -F _git_branch git-branch -complete -o default -o nospace -F _git_cat_file git-cat-file complete -o default -F _git_checkout git-checkout complete -o default -F _git_cherry_pick git-cherry-pick complete -o default -F _git_commit git-commit @@ -871,7 +855,6 @@ if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then complete -o default -F _git_apply git-apply.exe complete -o default -o nospace -F _git git.exe complete -o default -F _git_branch git-branch.exe -complete -o default -o nospace -F _git_cat_file git-cat-file.exe complete -o default -o nospace -F _git_diff git-diff.exe complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe complete -o default -o nospace -F _git_format_patch git-format-patch.exe From 8435b548480a6b6d110fd2f59b3139f25ffb392e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 4 Feb 2007 02:38:23 -0500 Subject: [PATCH 388/548] bash: Complete long options to git-add. The new --interactive mode of git-add can be very useful, so users will probably want to have completion for it. Likewise the new git-add--interactive executable is actually a plumbing command. Its invoked by `git add --interactive` and is not intended to be invoked directly by the user. Therefore we should hide it from the list of available Git commands. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index b87f96d9ea..93f2af5e1f 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -235,6 +235,7 @@ __git_commands () for i in $(git help -a|egrep '^ ') do case $i in + add--interactive) : plumbing;; cat-file) : plumbing;; check-ref-format) : plumbing;; commit-tree) : plumbing;; @@ -358,6 +359,19 @@ _git_apply () COMPREPLY=() } +_git_add () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + COMPREPLY=($(compgen -W " + --interactive + " -- "$cur")) + return + esac + COMPREPLY=() +} + _git_branch () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -786,6 +800,7 @@ _git () case "$command" in am) _git_am ;; + add) _git_add ;; apply) _git_apply ;; branch) _git_branch ;; checkout) _git_checkout ;; @@ -852,6 +867,7 @@ complete -o default -o nospace -F _git_log git-whatchanged # included the '.exe' suffix. # if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then +complete -o default -F _git_add git-add.exe complete -o default -F _git_apply git-apply.exe complete -o default -o nospace -F _git git.exe complete -o default -F _git_branch git-branch.exe From 72e5e989b8c22dc6dd2b4f889d5d0dabf698b387 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 4 Feb 2007 02:38:27 -0500 Subject: [PATCH 389/548] bash: Add space after unique command name is completed. Because we use the nospace option for our completion function for the main 'git' wrapper bash won't automatically add a space after a unique completion has been made by the user. This has been pointed out in the past by Linus Torvalds as an undesired behavior. I agree. We have to use the nospace option to ensure path completion for a command such as `git show` works properly, but that breaks the common case of getting the space for a unique completion. So now we set IFS=$'\n' (linefeed) and add a trailing space to every possible completion option. This causes bash to insert the space when the completion is unique. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 93f2af5e1f..1cf576e1e5 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -61,6 +61,20 @@ __git_ps1 () fi } +__gitcomp () +{ + local all c s=$'\n' IFS=' '$'\t'$'\n' + for c in $1; do + case "$c" in + --*=*) all="$all$c$s" ;; + *) all="$all$c $s" ;; + esac + done + IFS=$s + COMPREPLY=($(compgen -W "$all" -- "${COMP_WORDS[COMP_CWORD]}")) + return +} + __git_heads () { local cmd i is_hash=y dir="$(__gitdir "$1")" @@ -787,12 +801,12 @@ _git () done if [ $c -eq $COMP_CWORD -a -z "$command" ]; then - COMPREPLY=($(compgen -W " - --git-dir= --version --exec-path - $(__git_commands) - $(__git_aliases) - " -- "${COMP_WORDS[COMP_CWORD]}")) - return; + case "${COMP_WORDS[COMP_CWORD]}" in + --*=*) COMPREPLY=() ;; + --*) __gitcomp "--git-dir= --bare --version --exec-path" ;; + *) __gitcomp "$(__git_commands) $(__git_aliases)" ;; + esac + return fi local expansion=$(__git_aliased_command "$command") From a925c6f165a3374ff4a69d7c991ead2d9fa90b98 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 4 Feb 2007 02:38:30 -0500 Subject: [PATCH 390/548] bash: Classify more commends out of completion. Most of these commands are not ones you want to invoke from the command line on a frequent basis, or have been renamed in 1.5.0 to more friendly versions, but the old names are being left behind to support existing scripts in the wild. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 1cf576e1e5..382c8177a3 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -250,16 +250,24 @@ __git_commands () do case $i in add--interactive) : plumbing;; + applymbox) : ask gittus;; + applypatch) : ask gittus;; + archimport) : import;; cat-file) : plumbing;; check-ref-format) : plumbing;; commit-tree) : plumbing;; convert-objects) : plumbing;; + cvsexportcommit) : export;; + cvsimport) : import;; cvsserver) : daemon;; daemon) : daemon;; + fsck-objects) : plumbing;; fetch-pack) : plumbing;; + fmt-merge-msg) : plumbing;; hash-object) : plumbing;; http-*) : transport;; index-pack) : plumbing;; + init-db) : deprecated;; local-fetch) : plumbing;; mailinfo) : plumbing;; mailsplit) : plumbing;; @@ -272,9 +280,13 @@ __git_commands () parse-remote) : plumbing;; patch-id) : plumbing;; peek-remote) : plumbing;; + prune) : plumbing;; + prune-packed) : plumbing;; + quiltimport) : import;; read-tree) : plumbing;; receive-pack) : plumbing;; reflog) : plumbing;; + repo-config) : plumbing;; rerere) : plumbing;; rev-list) : plumbing;; rev-parse) : plumbing;; @@ -285,14 +297,19 @@ __git_commands () show-index) : plumbing;; ssh-*) : transport;; stripspace) : plumbing;; + svn) : import export;; + svnimport) : import;; symbolic-ref) : plumbing;; + tar-tree) : deprecated;; unpack-file) : plumbing;; unpack-objects) : plumbing;; + update-index) : plumbing;; update-ref) : plumbing;; update-server-info) : daemon;; upload-archive) : plumbing;; upload-pack) : plumbing;; write-tree) : plumbing;; + verify-tag) : plumbing;; *) echo $i;; esac done @@ -834,7 +851,6 @@ _git () pull) _git_pull ;; push) _git_push ;; rebase) _git_rebase ;; - repo-config) _git_config ;; reset) _git_reset ;; show) _git_show ;; show-branch) _git_log ;; From 78d4d6a2815f20607336fcb238ba23efc00e1b0a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 4 Feb 2007 02:38:37 -0500 Subject: [PATCH 391/548] bash: Support unique completion on git-config. In many cases we know a completion will be unique, but we've disabled bash's automatic space addition (-o nospace) so we need to do it ourselves when necessary. This change adds additional support for new configuration options added in 1.5.0, as well as some extended completion support for the color.* family of options. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 122 +++++++++++++++++-------- 1 file changed, 83 insertions(+), 39 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 382c8177a3..38d61210a2 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -64,14 +64,19 @@ __git_ps1 () __gitcomp () { local all c s=$'\n' IFS=' '$'\t'$'\n' + local cur="${COMP_WORDS[COMP_CWORD]}" + if [ -n "$2" ]; then + cur="$3" + fi for c in $1; do - case "$c" in - --*=*) all="$all$c$s" ;; - *) all="$all$c $s" ;; + case "$c$4" in + --*=*) all="$all$c$4$s" ;; + *.) all="$all$c$4$s" ;; + *) all="$all$c$4 $s" ;; esac done IFS=$s - COMPREPLY=($(compgen -W "$all" -- "${COMP_WORDS[COMP_CWORD]}")) + COMPREPLY=($(compgen -P "$2" -W "$all" -- "$cur")) return } @@ -666,26 +671,40 @@ _git_config () local prv="${COMP_WORDS[COMP_CWORD-1]}" case "$prv" in branch.*.remote) - COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + __gitcomp "$(__git_remotes)" return ;; branch.*.merge) - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + __gitcomp "$(__git_refs)" return ;; remote.*.fetch) local remote="${prv#remote.}" remote="${remote%.fetch}" - COMPREPLY=($(compgen -W "$(__git_refs_remotes "$remote")" \ - -- "$cur")) + __gitcomp "$(__git_refs_remotes "$remote")" return ;; remote.*.push) local remote="${prv#remote.}" remote="${remote%.push}" - COMPREPLY=($(compgen -W "$(git --git-dir="$(__gitdir)" \ + __gitcomp "$(git --git-dir="$(__gitdir)" \ for-each-ref --format='%(refname):%(refname)' \ - refs/heads)" -- "$cur")) + refs/heads)" + return + ;; + pull.twohead|pull.octopus) + __gitcomp "$(__git_merge_strategies)" + return + ;; + color.branch|color.diff|color.status) + __gitcomp "always never auto" + return + ;; + color.*.*) + __gitcomp " + black red green yellow blue magenta cyan white + bold dim ul blink reverse + " return ;; *.*) @@ -695,41 +714,39 @@ _git_config () esac case "$cur" in --*) - COMPREPLY=($(compgen -W " + __gitcomp " --global --list --replace-all --get --get-all --get-regexp --unset --unset-all - " -- "$cur")) + " return ;; branch.*.*) local pfx="${cur%.*}." cur="${cur##*.}" - COMPREPLY=($(compgen -P "$pfx" -W "remote merge" -- "$cur")) + __gitcomp "remote merge" "$pfx" "$cur" return ;; branch.*) local pfx="${cur%.*}." cur="${cur#*.}" - COMPREPLY=($(compgen -P "$pfx" -S . \ - -W "$(__git_heads)" -- "$cur")) + __gitcomp "$(__git_heads)" "$pfx" "$cur" "." return ;; remote.*.*) local pfx="${cur%.*}." cur="${cur##*.}" - COMPREPLY=($(compgen -P "$pfx" -W "url fetch push" -- "$cur")) + __gitcomp "url fetch push" "$pfx" "$cur" return ;; remote.*) local pfx="${cur%.*}." cur="${cur#*.}" - COMPREPLY=($(compgen -P "$pfx" -S . \ - -W "$(__git_remotes)" -- "$cur")) + __gitcomp "$(__git_remotes)" "$pfx" "$cur" "." return ;; esac - COMPREPLY=($(compgen -W " + __gitcomp " apply.whitespace core.fileMode core.gitProxy @@ -741,40 +758,67 @@ _git_config () core.warnAmbiguousRefs core.compression core.legacyHeaders - i18n.commitEncoding - i18n.logOutputEncoding - diff.color + core.packedGitWindowSize + core.packedGitLimit + color.branch + color.branch.current + color.branch.local + color.branch.remote + color.branch.plain color.diff + color.diff.plain + color.diff.meta + color.diff.frag + color.diff.old + color.diff.new + color.diff.commit + color.diff.whitespace + color.pager + color.status + color.status.header + color.status.added + color.status.changed + color.status.untracked diff.renameLimit diff.renames - pager.color - color.pager - status.color - color.status - log.showroot - show.difftree - showbranch.default - whatchanged.difftree + fetch.unpackLimit + format.headers + gitcvs.enabled + gitcvs.logfile + gc.reflogexpire + gc.reflogexpireunreachable + gc.rerereresolved + gc.rerereunresolved http.sslVerify http.sslCert http.sslKey http.sslCAInfo http.sslCAPath http.maxRequests - http.lowSpeedLimit http.lowSpeedTime + http.lowSpeedLimit + http.lowSpeedTime http.noEPSV - pack.window - repack.useDeltaBaseOffset - pull.octopus pull.twohead + i18n.commitEncoding + i18n.logOutputEncoding + log.showroot merge.summary + merge.verbosity + pack.window + pull.octopus + pull.twohead + repack.useDeltaBaseOffset + show.difftree + showbranch.default + tar.umask + transfer.unpackLimit receive.unpackLimit receive.denyNonFastForwards - user.name user.email - tar.umask - gitcvs.enabled - gitcvs.logfile + user.name + user.email + user.signingkey + whatchanged.difftree branch. remote. - " -- "$cur")) + " } _git_reset () From b3391775e87bed073b93a0a534169a794eceebd7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 4 Feb 2007 02:38:43 -0500 Subject: [PATCH 392/548] bash: Support unique completion when possible. Because our use of -o nospace prevents bash from adding a trailing space when a completion is unique and has been fully completed, we need to perform this addition on our own. This (large) change converts all existing uses of compgen to our wrapper __gitcomp which attempts to handle this by tacking a trailing space onto the end of each offered option. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 190 ++++++++++++------------- 1 file changed, 91 insertions(+), 99 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 38d61210a2..3b1f100f1b 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -65,7 +65,7 @@ __gitcomp () { local all c s=$'\n' IFS=' '$'\t'$'\n' local cur="${COMP_WORDS[COMP_CWORD]}" - if [ -n "$2" ]; then + if [ $# -gt 2 ]; then cur="$3" fi for c in $1; do @@ -219,7 +219,7 @@ __git_complete_file () -- "$cur")) ;; *) - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + __gitcomp "$(__git_refs)" ;; esac } @@ -231,15 +231,18 @@ __git_complete_revlist () *...*) pfx="${cur%...*}..." cur="${cur#*...}" - COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) + __gitcomp "$(__git_refs)" "$pfx" "$cur" ;; *..*) pfx="${cur%..*}.." cur="${cur#*..}" - COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) + __gitcomp "$(__git_refs)" "$pfx" "$cur" + ;; + *.) + __gitcomp "$cur." ;; *) - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + __gitcomp "$(__git_refs)" ;; esac } @@ -353,22 +356,19 @@ _git_am () { local cur="${COMP_WORDS[COMP_CWORD]}" if [ -d .dotest ]; then - COMPREPLY=($(compgen -W " - --skip --resolved - " -- "$cur")) + __gitcomp "--skip --resolved" return fi case "$cur" in --whitespace=*) - COMPREPLY=($(compgen -W "$__git_whitespacelist" \ - -- "${cur##--whitespace=}")) + __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" return ;; --*) - COMPREPLY=($(compgen -W " + __gitcomp " --signoff --utf8 --binary --3way --interactive --whitespace= - " -- "$cur")) + " return esac COMPREPLY=() @@ -379,17 +379,16 @@ _git_apply () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --whitespace=*) - COMPREPLY=($(compgen -W "$__git_whitespacelist" \ - -- "${cur##--whitespace=}")) + __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" return ;; --*) - COMPREPLY=($(compgen -W " + __gitcomp " --stat --numstat --summary --check --index --cached --index-info --reverse --reject --unidiff-zero --apply --no-add --exclude= --whitespace= --inaccurate-eof --verbose - " -- "$cur")) + " return esac COMPREPLY=() @@ -400,9 +399,7 @@ _git_add () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --*) - COMPREPLY=($(compgen -W " - --interactive - " -- "$cur")) + __gitcomp "--interactive" return esac COMPREPLY=() @@ -410,14 +407,12 @@ _git_add () _git_branch () { - local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + __gitcomp "$(__git_refs)" } _git_checkout () { - local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + __gitcomp "$(__git_refs)" } _git_cherry_pick () @@ -425,12 +420,10 @@ _git_cherry_pick () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --*) - COMPREPLY=($(compgen -W " - --edit --no-commit - " -- "$cur")) + __gitcomp "--edit --no-commit" ;; *) - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + __gitcomp "$(__git_refs)" ;; esac } @@ -440,10 +433,10 @@ _git_commit () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --*) - COMPREPLY=($(compgen -W " + __gitcomp " --all --author= --signoff --verify --no-verify --edit --amend --include --only - " -- "$cur")) + " return esac COMPREPLY=() @@ -456,8 +449,7 @@ _git_diff () _git_diff_tree () { - local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + __gitcomp "$(__git_refs)" } _git_fetch () @@ -466,16 +458,15 @@ _git_fetch () case "${COMP_WORDS[0]},$COMP_CWORD" in git-fetch*,1) - COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + __gitcomp "$(__git_remotes)" ;; git,2) - COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + __gitcomp "$(__git_remotes)" ;; *) case "$cur" in *:*) - cur="${cur#*:}" - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + __gitcomp "$(__git_refs)" "" "${cur#*:}" ;; *) local remote @@ -483,7 +474,7 @@ _git_fetch () git-fetch) remote="${COMP_WORDS[1]}" ;; git) remote="${COMP_WORDS[2]}" ;; esac - COMPREPLY=($(compgen -W "$(__git_refs2 "$remote")" -- "$cur")) + __gitcomp "$(__git_refs2 "$remote")" ;; esac ;; @@ -495,7 +486,7 @@ _git_format_patch () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --*) - COMPREPLY=($(compgen -W " + __gitcomp " --stdout --attach --thread --output-directory --numbered --start-number @@ -503,7 +494,7 @@ _git_format_patch () --signoff --in-reply-to= --full-index --binary - " -- "$cur")) + " return ;; esac @@ -512,8 +503,7 @@ _git_format_patch () _git_ls_remote () { - local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + __gitcomp "$(__git_remotes)" } _git_ls_tree () @@ -526,13 +516,13 @@ _git_log () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --pretty=*) - COMPREPLY=($(compgen -W " + __gitcomp " oneline short medium full fuller email raw - " -- "${cur##--pretty=}")) + " "" "${cur##--pretty=}" return ;; --*) - COMPREPLY=($(compgen -W " + __gitcomp " --max-count= --max-age= --since= --after= --min-age= --before= --until= --root --not --topo-order --date-order @@ -542,7 +532,7 @@ _git_log () --author= --committer= --grep= --all-match --pretty= --name-status --name-only - " -- "$cur")) + " return ;; esac @@ -554,34 +544,31 @@ _git_merge () local cur="${COMP_WORDS[COMP_CWORD]}" case "${COMP_WORDS[COMP_CWORD-1]}" in -s|--strategy) - COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur")) + __gitcomp "$(__git_merge_strategies)" return esac case "$cur" in --strategy=*) - COMPREPLY=($(compgen -W "$(__git_merge_strategies)" \ - -- "${cur##--strategy=}")) + __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}" return ;; --*) - COMPREPLY=($(compgen -W " + __gitcomp " --no-commit --no-summary --squash --strategy - " -- "$cur")) + " return esac - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + __gitcomp "$(__git_refs)" } _git_merge_base () { - local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + __gitcomp "$(__git_refs)" } _git_name_rev () { - local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "--tags --all --stdin" -- "$cur")) + __gitcomp "--tags --all --stdin" } _git_pull () @@ -590,10 +577,10 @@ _git_pull () case "${COMP_WORDS[0]},$COMP_CWORD" in git-pull*,1) - COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + __gitcomp "$(__git_remotes)" ;; git,2) - COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + __gitcomp "$(__git_remotes)" ;; *) local remote @@ -601,7 +588,7 @@ _git_pull () git-pull) remote="${COMP_WORDS[1]}" ;; git) remote="${COMP_WORDS[2]}" ;; esac - COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur")) + __gitcomp "$(__git_refs "$remote")" ;; esac } @@ -612,10 +599,10 @@ _git_push () case "${COMP_WORDS[0]},$COMP_CWORD" in git-push*,1) - COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + __gitcomp "$(__git_remotes)" ;; git,2) - COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + __gitcomp "$(__git_remotes)" ;; *) case "$cur" in @@ -625,11 +612,10 @@ _git_push () git-push) remote="${COMP_WORDS[1]}" ;; git) remote="${COMP_WORDS[2]}" ;; esac - cur="${cur#*:}" - COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur")) + __gitcomp "$(__git_refs "$remote")" "" "${cur#*:}" ;; *) - COMPREPLY=($(compgen -W "$(__git_refs2)" -- "$cur")) + __gitcomp "$(__git_refs2)" ;; esac ;; @@ -640,29 +626,24 @@ _git_rebase () { local cur="${COMP_WORDS[COMP_CWORD]}" if [ -d .dotest ]; then - COMPREPLY=($(compgen -W " - --continue --skip --abort - " -- "$cur")) + __gitcomp "--continue --skip --abort" return fi case "${COMP_WORDS[COMP_CWORD-1]}" in -s|--strategy) - COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur")) + __gitcomp "$(__git_merge_strategies)" return esac case "$cur" in --strategy=*) - COMPREPLY=($(compgen -W "$(__git_merge_strategies)" \ - -- "${cur##--strategy=}")) + __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}" return ;; --*) - COMPREPLY=($(compgen -W " - --onto --merge --strategy - " -- "$cur")) + __gitcomp "--onto --merge --strategy" return esac - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + __gitcomp "$(__git_refs)" } _git_config () @@ -824,8 +805,13 @@ _git_config () _git_reset () { local cur="${COMP_WORDS[COMP_CWORD]}" - local opt="--mixed --hard --soft" - COMPREPLY=($(compgen -W "$opt $(__git_refs)" -- "$cur")) + case "$cur" in + --*) + __gitcomp "--mixed --hard --soft" + return + ;; + esac + __gitcomp "$(__git_refs)" } _git_show () @@ -833,13 +819,13 @@ _git_show () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --pretty=*) - COMPREPLY=($(compgen -W " + __gitcomp " oneline short medium full fuller email raw - " -- "${cur##--pretty=}")) + " "" "${cur##--pretty=}" return ;; --*) - COMPREPLY=($(compgen -W "--pretty=" -- "$cur")) + __gitcomp "--pretty=" return ;; esac @@ -906,32 +892,38 @@ _git () _gitk () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "--all $(__git_refs)" -- "$cur")) + case "$cur" in + --*) + __gitcomp "--not --all" + return + ;; + esac + __gitcomp "$(__git_refs)" } complete -o default -o nospace -F _git git -complete -o default -F _gitk gitk -complete -o default -F _git_am git-am -complete -o default -F _git_apply git-apply -complete -o default -F _git_branch git-branch -complete -o default -F _git_checkout git-checkout -complete -o default -F _git_cherry_pick git-cherry-pick -complete -o default -F _git_commit git-commit +complete -o default -o nospace -F _gitk gitk +complete -o default -o nospace -F _git_am git-am +complete -o default -o nospace -F _git_apply git-apply +complete -o default -o nospace -F _git_branch git-branch +complete -o default -o nospace -F _git_checkout git-checkout +complete -o default -o nospace -F _git_cherry_pick git-cherry-pick +complete -o default -o nospace -F _git_commit git-commit complete -o default -o nospace -F _git_diff git-diff -complete -o default -F _git_diff_tree git-diff-tree +complete -o default -o nospace -F _git_diff_tree git-diff-tree complete -o default -o nospace -F _git_fetch git-fetch complete -o default -o nospace -F _git_format_patch git-format-patch complete -o default -o nospace -F _git_log git-log -complete -o default -F _git_ls_remote git-ls-remote +complete -o default -o nospace -F _git_ls_remote git-ls-remote complete -o default -o nospace -F _git_ls_tree git-ls-tree -complete -o default -F _git_merge git-merge -complete -o default -F _git_merge_base git-merge-base -complete -o default -F _git_name_rev git-name-rev +complete -o default -o nospace -F _git_merge git-merge +complete -o default -o nospace -F _git_merge_base git-merge-base +complete -o default -o nospace -F _git_name_rev git-name-rev complete -o default -o nospace -F _git_pull git-pull complete -o default -o nospace -F _git_push git-push -complete -o default -F _git_rebase git-rebase -complete -o default -F _git_config git-config -complete -o default -F _git_reset git-reset +complete -o default -o nospace -F _git_rebase git-rebase +complete -o default -o nospace -F _git_config git-config +complete -o default -o nospace -F _git_reset git-reset complete -o default -o nospace -F _git_show git-show complete -o default -o nospace -F _git_log git-show-branch complete -o default -o nospace -F _git_log git-whatchanged @@ -941,19 +933,19 @@ complete -o default -o nospace -F _git_log git-whatchanged # included the '.exe' suffix. # if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then -complete -o default -F _git_add git-add.exe -complete -o default -F _git_apply git-apply.exe +complete -o default -o nospace -F _git_add git-add.exe +complete -o default -o nospace -F _git_apply git-apply.exe complete -o default -o nospace -F _git git.exe -complete -o default -F _git_branch git-branch.exe +complete -o default -o nospace -F _git_branch git-branch.exe complete -o default -o nospace -F _git_diff git-diff.exe complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe complete -o default -o nospace -F _git_format_patch git-format-patch.exe complete -o default -o nospace -F _git_log git-log.exe complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe -complete -o default -F _git_merge_base git-merge-base.exe -complete -o default -F _git_name_rev git-name-rev.exe +complete -o default -o nospace -F _git_merge_base git-merge-base.exe +complete -o default -o nospace -F _git_name_rev git-name-rev.exe complete -o default -o nospace -F _git_push git-push.exe -complete -o default -F _git_config git-config +complete -o default -o nospace -F _git_config git-config complete -o default -o nospace -F _git_show git-show.exe complete -o default -o nospace -F _git_log git-show-branch.exe complete -o default -o nospace -F _git_log git-whatchanged.exe From ec8048913217d8ff6e54950a0cb8ab2e739a1d1f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 4 Feb 2007 02:38:47 -0500 Subject: [PATCH 393/548] bash: Support internal revlist options better. format-patch/log/whatchanged all take --not and --all as options to the internal revlist process. So these should be supported as possible completions. gitk takes anything rev-list/log/whatchanged takes, so we should use complete_revlist to handle its options. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 3b1f100f1b..466cc32f4c 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -494,6 +494,7 @@ _git_format_patch () --signoff --in-reply-to= --full-index --binary + --not --all " return ;; @@ -532,6 +533,7 @@ _git_log () --author= --committer= --grep= --all-match --pretty= --name-status --name-only + --not --all " return ;; @@ -898,7 +900,7 @@ _gitk () return ;; esac - __gitcomp "$(__git_refs)" + __git_complete_revlist } complete -o default -o nospace -F _git git From 1f7d1a53fed40608e76200f941e6689b53752747 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 4 Feb 2007 03:25:12 -0800 Subject: [PATCH 394/548] git-clone --reference: saner handling of borrowed symrefs. When using --reference to borrow objects from a neighbouring repository while cloning, we copy the entire set of refs under temporary "refs/reference-tmp/refs" space and set up the object alternates. However, a textual symref copied this way would not point at the right place, and causes later steps to emit error messages (which is harmless but still alarming). This is most visible when using a clone created with the separate-remote layout as a reference, because such a repository would have refs/remotes/origin/HEAD with 'ref: refs/remotes/origin/master' as its contents. Although we do not create symbolic-link based refs anymore, they have the same problem because they are always supposed to be relative to refs/ hierarchy (we dereference by hand, so it only is good for HEAD and nothing else). In either case, the solution is simply to remove them after copying under refs/reference-tmp; if a symref points at a true ref, that true ref itself is enough to ensure that objects reachable from it do not needlessly get fetched. Signed-off-by: Junio C Hamano --- git-clone.sh | 29 ++++++++++++++++++++++++++- t/t5700-clone-reference.sh | 40 +++++++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/git-clone.sh b/git-clone.sh index 4ddfa774ec..171099674d 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -190,7 +190,34 @@ then (cd "$GIT_DIR/refs" && mkdir reference-tmp && cd reference-tmp && - tar xf -) + tar xf - && + find refs ! -type d -print | + while read ref + do + if test -h "$ref" + then + # Old-style symbolic link ref. Not likely + # to appear under refs/ but we might as well + # deal with them. + : + elif test -f "$ref" + then + point=$(cat "$ref") && + case "$point" in + 'ref: '*) ;; + *) continue ;; + esac + fi + # The above makes true ref to 'continue' and + # we will come here when we are looking at + # symbolic link ref or a textual symref (or + # garbage, like fifo). + # The true ref pointed at by it is enough to + # ensure that we do not fetch objects reachable + # from it. + rm -f "$ref" + done + ) else die "reference repository '$reference' is not a local directory." fi diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh index dd9caad1c2..6d43252593 100755 --- a/t/t5700-clone-reference.sh +++ b/t/t5700-clone-reference.sh @@ -26,7 +26,7 @@ git prune' cd "$base_dir" -test_expect_success 'cloning with reference' \ +test_expect_success 'cloning with reference (-l -s)' \ 'git clone -l -s --reference B A C' cd "$base_dir" @@ -50,6 +50,28 @@ diff expected current' cd "$base_dir" +test_expect_success 'cloning with reference (no -l -s)' \ +'git clone --reference B A D' + +cd "$base_dir" + +test_expect_success 'existence of info/alternates' \ +'test `wc -l expected && +git count-objects > current && +diff expected current' + +cd "$base_dir" + test_expect_success 'updating origin' \ 'cd A && echo third > file3 && @@ -75,4 +97,20 @@ diff expected current' cd "$base_dir" +test_expect_success 'pulling changes from origin' \ +'cd D && +git pull origin' + +cd "$base_dir" + +# the 5 local objects are expected; file3 blob, commit in A to add it +# and its tree, and 2 are our tree and the merge commit. +test_expect_success 'check objects expected to exist locally' \ +'cd D && +echo "5 objects" > expected && +git count-objects | cut -d, -f1 > current && +diff expected current' + +cd "$base_dir" + test_done From 6e2e1cfb81a6a6de9fc074bd26bed8a45f73251f Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Sun, 4 Feb 2007 17:16:39 +0100 Subject: [PATCH 395/548] Why is it bad to rewind a branch that has already been pushed out? Mention git-revert as an alternative to git-reset to revert changes. Signed-off-by: Robin Rosenberg Signed-off-by: Junio C Hamano --- Documentation/tutorial.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt index 5fc5be5a28..129c5c5f5b 100644 --- a/Documentation/tutorial.txt +++ b/Documentation/tutorial.txt @@ -461,6 +461,8 @@ this branch. If this branch is the only branch containing those commits, they will be lost. Also, don't use "git reset" on a publicly-visible branch that other developers pull from, as it will force needless merges on other developers to clean up the history. +If you need to undo changes that you have pushed, use gitlink:git-revert[1] +instead. The git grep command can search for strings in any version of your project, so From 756373da254dbe8bd03bd83ec658d3f015fee958 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 3 Feb 2007 16:23:38 -0800 Subject: [PATCH 396/548] Revert "Allow branch.*.merge to talk about remote tracking branches." This reverts commit 80c797764a6b6a373f0f1f47d7f56b0d950418a9. Back when I committed this, it seemed to be a good idea. People who always use remote tracking branches can optionally use the local name they happen to use to specify what to merge, which meant that I did not have to teach them why we use the name at the remote side every time they are confused. But allowing it seems to break other people's scripts. The real solution is not to allow more ways to express the same thing, but to educate people to use the right syntax. Signed-off-by: Junio C Hamano --- git-parse-remote.sh | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 3e783b7b05..5208ee6ce0 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -174,12 +174,8 @@ canon_refs_list_for_fetch () { else for merge_branch in $merge_branches do - if test "$remote" = "$merge_branch" || - test "$local" = "$merge_branch" - then - dot_prefix= - break - fi + [ "$remote" = "$merge_branch" ] && + dot_prefix= && break done fi case "$remote" in From 11dbe9e88016f3894eda79c7437f6c3bf79e155e Mon Sep 17 00:00:00 2001 From: Gerrit Pape Date: Sat, 3 Feb 2007 22:38:59 +0000 Subject: [PATCH 397/548] git-archimport: initial import needs empty directory git-archimport should better refuse to start an initial import if the current directory is not empty. (http://bugs.debian.org/400508) Signed-off-by: Gerrit Pape Signed-off-by: Junio C Hamano --- git-archimport.perl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/git-archimport.perl b/git-archimport.perl index 2e15781246..66aaeae102 100755 --- a/git-archimport.perl +++ b/git-archimport.perl @@ -95,6 +95,15 @@ $ENV{'TMPDIR'} = $opt_t if $opt_t; # $ENV{TMPDIR} will affect tempdir() calls: my $tmp = tempdir('git-archimport-XXXXXX', TMPDIR => 1, CLEANUP => 1); $opt_v && print "+ Using $tmp as temporary directory\n"; +unless (-d $git_dir) { # initial import needs empty directory + opendir DIR, '.' or die "Unable to open current directory: $!\n"; + while (my $entry = readdir DIR) { + $entry =~ /^\.\.?$/ or + die "Initial import needs an empty current working directory.\n" + } + closedir DIR +} + my %reachable = (); # Arch repositories we can access my %unreachable = (); # Arch repositories we can't access :< my @psets = (); # the collection From 798123af21e1660cb606ab730ce721f354957719 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 4 Feb 2007 17:50:14 -0800 Subject: [PATCH 398/548] Rename get_ident() to fmt_ident() and make it available to outside This makes the functionality of ident.c::get_ident() available to other callers. Signed-off-by: Junio C Hamano --- cache.h | 1 + ident.c | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cache.h b/cache.h index 201704bacf..38a9bc02f6 100644 --- a/cache.h +++ b/cache.h @@ -321,6 +321,7 @@ unsigned long approxidate(const char *); extern const char *git_author_info(int); extern const char *git_committer_info(int); +extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int); struct checkout { const char *base_dir; diff --git a/ident.c b/ident.c index a6fc7b5e11..bb03bddd34 100644 --- a/ident.c +++ b/ident.c @@ -185,8 +185,8 @@ static const char *env_hint = "Add --global to set your account\'s default\n" "\n"; -static const char *get_ident(const char *name, const char *email, - const char *date_str, int error_on_no_name) +const char *fmt_ident(const char *name, const char *email, + const char *date_str, int error_on_no_name) { static char buffer[1000]; char date[50]; @@ -233,7 +233,7 @@ static const char *get_ident(const char *name, const char *email, const char *git_author_info(int error_on_no_name) { - return get_ident(getenv("GIT_AUTHOR_NAME"), + return fmt_ident(getenv("GIT_AUTHOR_NAME"), getenv("GIT_AUTHOR_EMAIL"), getenv("GIT_AUTHOR_DATE"), error_on_no_name); @@ -241,7 +241,7 @@ const char *git_author_info(int error_on_no_name) const char *git_committer_info(int error_on_no_name) { - return get_ident(getenv("GIT_COMMITTER_NAME"), + return fmt_ident(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"), getenv("GIT_COMMITTER_DATE"), error_on_no_name); From 8c1f22da9f8124dfabb5da8476845250b5c35ae8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 16:05:11 -0500 Subject: [PATCH 399/548] Include checkpoint command in the BNF. This command isn't encouraged (as its slow) but it does exist and is accepted, so it still should be covered in the BNF. Signed-off-by: Shawn O. Pearce --- fast-import.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fast-import.c b/fast-import.c index f62a5a9f6c..1559f9c0ff 100644 --- a/fast-import.c +++ b/fast-import.c @@ -7,6 +7,7 @@ Format of STDIN stream: | new_commit | new_tag | reset_branch + | checkpoint ; new_blob ::= 'blob' lf From 10831c551323121bdab06c3eaf2f52c6658fd6b8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 16:34:56 -0500 Subject: [PATCH 400/548] Reduce memory usage of fast-import. Some structs are allocated rather frequently, but were using integer types which were far larger than required to actually store their full value range. As packfiles are limited to 4 GiB we don't need more than 32 bits to store the offset of an object within that packfile, an `unsigned long` on a 64 bit system is likely a 64 bit unsigned value. Saving 4 bytes per object on a 64 bit system can add up fast on any sizable import. As atom strings are strictly single components in a path name these are probably limited to just 255 bytes by the underlying OS. Going to that short of a string is probably too restrictive, but certainly `unsigned int` is far too large for their lengths. `unsigned short` is a reasonable limit. Modes within a tree really only need two bytes to store their whole value; using `unsigned int` here is vast overkill. Saving 4 bytes per file entry in an active branch can add up quickly on a project with a large number of files. Signed-off-by: Shawn O. Pearce --- fast-import.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/fast-import.c b/fast-import.c index 1559f9c0ff..9658c28413 100644 --- a/fast-import.c +++ b/fast-import.c @@ -130,7 +130,7 @@ Format of STDIN stream: struct object_entry { struct object_entry *next; - unsigned long offset; + uint32_t offset; unsigned type : TYPE_BITS; unsigned pack_id : PACK_ID_BITS; unsigned char sha1[20]; @@ -157,7 +157,7 @@ struct last_object { void *data; unsigned long len; - unsigned long offset; + uint32_t offset; unsigned int depth; unsigned no_free:1; }; @@ -173,7 +173,7 @@ struct mem_pool struct atom_str { struct atom_str *next_atom; - unsigned int str_len; + unsigned short str_len; char str_dat[FLEX_ARRAY]; /* more */ }; @@ -184,7 +184,7 @@ struct tree_entry struct atom_str* name; struct tree_entry_ms { - unsigned int mode; + uint16_t mode; unsigned char sha1[20]; } versions[2]; }; @@ -464,7 +464,7 @@ static struct object_entry* find_mark(uintmax_t idnum) return oe; } -static struct atom_str* to_atom(const char *s, size_t len) +static struct atom_str* to_atom(const char *s, unsigned short len) { unsigned int hc = hc_str(s, len) % atom_table_sz; struct atom_str *c; @@ -993,10 +993,10 @@ static void *gfi_unpack_entry( return unpack_entry(p, oe->offset, type, sizep); } -static const char *get_mode(const char *str, unsigned int *modep) +static const char *get_mode(const char *str, uint16_t *modep) { unsigned char c; - unsigned int mode = 0; + uint16_t mode = 0; while ((c = *str++) != ' ') { if (c < '0' || c > '7') @@ -1046,7 +1046,7 @@ static void load_tree(struct tree_entry *root) if (!c) die("Corrupt mode in %s", sha1_to_hex(sha1)); e->versions[0].mode = e->versions[1].mode; - e->name = to_atom(c, strlen(c)); + e->name = to_atom(c, (unsigned short)strlen(c)); c += e->name->str_len + 1; hashcpy(e->versions[0].sha1, (unsigned char*)c); hashcpy(e->versions[1].sha1, (unsigned char*)c); @@ -1098,7 +1098,7 @@ static void mktree(struct tree_content *t, struct tree_entry *e = t->entries[i]; if (!e->versions[v].mode) continue; - c += sprintf(c, "%o", e->versions[v].mode); + c += sprintf(c, "%o", (unsigned int)e->versions[v].mode); *c++ = ' '; strcpy(c, e->name->str_dat); c += e->name->str_len + 1; @@ -1161,7 +1161,7 @@ static int tree_content_set( struct tree_entry *root, const char *p, const unsigned char *sha1, - const unsigned int mode) + const uint16_t mode) { struct tree_content *t = root->tree; const char *slash1; @@ -1207,7 +1207,7 @@ static int tree_content_set( if (t->entry_count == t->entry_capacity) root->tree = t = grow_tree_content(t, 8); e = new_tree_entry(); - e->name = to_atom(p, n); + e->name = to_atom(p, (unsigned short)n); e->versions[0].mode = 0; hashclr(e->versions[0].sha1); t->entries[t->entry_count++] = e; @@ -1458,7 +1458,7 @@ static void file_change_m(struct branch *b) const char *endp; struct object_entry *oe; unsigned char sha1[20]; - unsigned int mode, inline_data = 0; + uint16_t mode, inline_data = 0; char type[20]; p = get_mode(p, &mode); From 133f081057a99957d6d34aa1c4fec61e5c5d22d8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 4 Feb 2007 17:30:58 -0800 Subject: [PATCH 401/548] t9200: Work around HFS+ issues. We at least know that the test as written has a problem in an environment where "touch '$p'; ls | fgrep '$p'" fails, and have a clear understand why it fails. This tests if the filesystem has that particular issue we know "git add" has a problem with, and skips the test in such an environment. This way, we might catch issues "git add" might have in other environments. Signed-off-by: Junio C Hamano --- t/t9200-git-cvsexportcommit.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index c443f32fce..4efa0c926c 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -169,6 +169,16 @@ test_expect_success \ test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.2/-kb with spaces.txt/1.2/" )' +# Some filesystems mangle pathnames with UTF-8 characters -- +# check and skip +if p="Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö" && + mkdir -p "tst/$p" && + date >"tst/$p/day" && + found=$(find tst -type f -print) && + test "z$found" = "ztst/$p/day" && + rm -fr tst +then + # This test contains UTF-8 characters test_expect_success \ 'File with non-ascii file name' \ @@ -184,6 +194,10 @@ test_expect_success \ test "$(echo $(sort Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/CVS/Entries|cut -d/ -f2,3,5))" = "gårdetsågårdet.png/1.1/-kb gårdetsågårdet.txt/1.1/" )' +fi + +rm -fr tst + test_expect_success \ 'Mismatching patch should fail' \ 'date >>"E/newfile5.txt" && From 69057cf39f87ecfe7446b14aa7df87ccf19b1151 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Mon, 5 Feb 2007 19:58:47 +0000 Subject: [PATCH 402/548] git-for-each-ref doesn't return "the bit after $GIT_DIR/refs" The documentation for git-for-each-ref said that the refname variable would return "the part after $GIT_DIR/refs/", which isn't true. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index da52eba7b3..f49b0d944c 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -66,7 +66,7 @@ keys. For all objects, the following names can be used: refname:: - The name of the ref (the part after $GIT_DIR/refs/). + The name of the ref (the part after $GIT_DIR/). objecttype:: The type of the object (`blob`, `tree`, `commit`, `tag`). From ea81fcc5762c56b13d1ad04d62b161d782c591f1 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:21:06 -0500 Subject: [PATCH 403/548] Show an example of deleting commits with git-rebase. This particular use of git-rebase to remove a single commit or a range of commits from the history of a branch recently came up on the mailing list. Documenting the example should help other users arrive at the same solution on their own. It also was not obvious to the newcomer that git-rebase is able to accept any commit for --onto and . We should at least minimally document this, as much of the language in git-rebase's manpage refers to 'branch' rather than 'committish'. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-rebase.txt | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 0cb9e1f10a..977f661b9d 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -114,6 +114,27 @@ would result in: This is useful when topicB does not depend on topicA. +A range of commits could also be removed with rebase. If we have +the following situation: + +------------ + E---F---G---H---I---J topicA +------------ + +then the command + + git-rebase --onto topicA~5 topicA~2 topicA + +would result in the removal of commits F and G: + +------------ + E---H'---I'---J' topicA +------------ + +This is useful if F and G were flawed in some way, or should not be +part of topicA. Note that the argument to --onto and the +parameter can be any valid commit-ish. + In case of conflict, git-rebase will stop at the first problematic commit and leave conflict markers in the tree. You can use git diff to locate the markers (<<<<<<) and make edits to resolve the conflict. For each @@ -141,10 +162,12 @@ OPTIONS :: Starting point at which to create the new commits. If the --onto option is not specified, the starting point is - . + . May be any valid commit, and not just an + existing branch name. :: - Upstream branch to compare against. + Upstream branch to compare against. May be any valid commit, + not just an existing branch name. :: Working branch; defaults to HEAD. From d8a9fea5ea44a4068cc95de9368798ae3cee74ee Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:44:22 -0500 Subject: [PATCH 404/548] bash: Support completion on git-cherry. I just realized I did not support ref name completion for git-cherry. This tool is just too useful to contributors who submit patches upstream by email; completion support for it is very handy. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 466cc32f4c..b434332bf7 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -415,6 +415,11 @@ _git_checkout () __gitcomp "$(__git_refs)" } +_git_cherry () +{ + __gitcomp "$(__git_refs)" +} + _git_cherry_pick () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -867,6 +872,7 @@ _git () apply) _git_apply ;; branch) _git_branch ;; checkout) _git_checkout ;; + cherry) _git_cherry ;; cherry-pick) _git_cherry_pick ;; commit) _git_commit ;; config) _git_config ;; @@ -909,6 +915,7 @@ complete -o default -o nospace -F _git_am git-am complete -o default -o nospace -F _git_apply git-apply complete -o default -o nospace -F _git_branch git-branch complete -o default -o nospace -F _git_checkout git-checkout +complete -o default -o nospace -F _git_cherry git-cherry complete -o default -o nospace -F _git_cherry_pick git-cherry-pick complete -o default -o nospace -F _git_commit git-commit complete -o default -o nospace -F _git_diff git-diff @@ -939,6 +946,7 @@ complete -o default -o nospace -F _git_add git-add.exe complete -o default -o nospace -F _git_apply git-apply.exe complete -o default -o nospace -F _git git.exe complete -o default -o nospace -F _git_branch git-branch.exe +complete -o default -o nospace -F _git_cherry git-cherry.exe complete -o default -o nospace -F _git_diff git-diff.exe complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe complete -o default -o nospace -F _git_format_patch git-format-patch.exe From 983591c31e2529fbba8dd2d69dce5ab446584921 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:44:24 -0500 Subject: [PATCH 405/548] bash: Hide diff-stages from completion. Apparently nobody really makes use of git-diff-stages, as nobody has complained that it is not supported by the git-diff frontend. Since its likely this will go away in the future, we should not offer it as a possible subcommand completion. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index b434332bf7..c0dae5484d 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -269,6 +269,7 @@ __git_commands () cvsimport) : import;; cvsserver) : daemon;; daemon) : daemon;; + diff-stages) : nobody uses it;; fsck-objects) : plumbing;; fetch-pack) : plumbing;; fmt-merge-msg) : plumbing;; From b26c87488fa89e444972c486326eecca5b2132c7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:44:28 -0500 Subject: [PATCH 406/548] bash: Offer --prune completion for git-gc. I'm lazy. I don't want to type out --prune if bash can do it for me with --. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index c0dae5484d..324bfbd8bb 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -508,6 +508,18 @@ _git_format_patch () __git_complete_revlist } +_git_gc () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + __gitcomp "--prune" + return + ;; + esac + COMPREPLY=() +} + _git_ls_remote () { __gitcomp "$(__git_remotes)" @@ -881,6 +893,7 @@ _git () diff-tree) _git_diff_tree ;; fetch) _git_fetch ;; format-patch) _git_format_patch ;; + gc) _git_gc ;; log) _git_log ;; ls-remote) _git_ls_remote ;; ls-tree) _git_ls_tree ;; @@ -923,6 +936,7 @@ complete -o default -o nospace -F _git_diff git-diff complete -o default -o nospace -F _git_diff_tree git-diff-tree complete -o default -o nospace -F _git_fetch git-fetch complete -o default -o nospace -F _git_format_patch git-format-patch +complete -o default -o nospace -F _git_gc git-gc complete -o default -o nospace -F _git_log git-log complete -o default -o nospace -F _git_ls_remote git-ls-remote complete -o default -o nospace -F _git_ls_tree git-ls-tree From e459415c9c3c8e855998dd0247f23d32a0dca4ff Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:44:30 -0500 Subject: [PATCH 407/548] bash: Hide git-resolve, its deprecated. Don't offer resolve as a possible subcommand completion. If you read the top of the script, there is a big warning about how it will go away soon in the near future. People should not be using it. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 324bfbd8bb..089a7b0571 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -297,6 +297,7 @@ __git_commands () reflog) : plumbing;; repo-config) : plumbing;; rerere) : plumbing;; + resolve) : dead dont use;; rev-list) : plumbing;; rev-parse) : plumbing;; runstatus) : plumbing;; From 1b71eb35ddb20057a266b05258de24d350ed5ba5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:44:32 -0500 Subject: [PATCH 408/548] bash: Support --add completion to git-config. We've recently added --add as an argument to git-config, but I missed putting it into the earlier round of git-config updates within the bash completion. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 089a7b0571..f9b827a617 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -719,7 +719,7 @@ _git_config () __gitcomp " --global --list --replace-all --get --get-all --get-regexp - --unset --unset-all + --add --unset --unset-all " return ;; From b2e69f6299b5a46840600176679b94843cf63a8d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:44:37 -0500 Subject: [PATCH 409/548] bash: Support git-bisect and its subcommands. We now offer completion support for git-bisect's subcommands, as well as ref name completion on the good/bad/reset subcommands. This should make interacting with git-bisect slightly easier on the fingers. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index f9b827a617..430e6367a7 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -407,6 +407,35 @@ _git_add () COMPREPLY=() } +_git_bisect () +{ + local i c=1 command + while [ $c -lt $COMP_CWORD ]; do + i="${COMP_WORDS[c]}" + case "$i" in + start|bad|good|reset|visualize|replay|log) + command="$i" + break + ;; + esac + c=$((++c)) + done + + if [ $c -eq $COMP_CWORD -a -z "$command" ]; then + __gitcomp "start bad good reset visualize replay log" + return + fi + + case "$command" in + bad|good|reset) + __gitcomp "$(__git_refs)" + ;; + *) + COMPREPLY=() + ;; + esac +} + _git_branch () { __gitcomp "$(__git_refs)" @@ -884,6 +913,7 @@ _git () am) _git_am ;; add) _git_add ;; apply) _git_apply ;; + bisect) _git_bisect ;; branch) _git_branch ;; checkout) _git_checkout ;; cherry) _git_cherry ;; @@ -928,6 +958,7 @@ complete -o default -o nospace -F _git git complete -o default -o nospace -F _gitk gitk complete -o default -o nospace -F _git_am git-am complete -o default -o nospace -F _git_apply git-apply +complete -o default -o nospace -F _git_bisect git-bisect complete -o default -o nospace -F _git_branch git-branch complete -o default -o nospace -F _git_checkout git-checkout complete -o default -o nospace -F _git_cherry git-cherry From 041794188ff4f7a0b3ada8058d174a0c114da78b Mon Sep 17 00:00:00 2001 From: Yasushi SHOJI Date: Tue, 30 Jan 2007 19:23:38 +0900 Subject: [PATCH 410/548] gitweb: Convert project name to UTF-8 If the repository directory name is in non-ascii, $project needs to be converted from perl internal to utf-8 because it will be used as title, page path, and snapshot filename. use to_utf8() to do the conversion. Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a2076a680c..d374110070 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1690,7 +1690,7 @@ sub git_header_html { my $title = "$site_name"; if (defined $project) { - $title .= " - $project"; + $title .= " - " . to_utf8($project); if (defined $action) { $title .= "/$action"; if (defined $file_name) { @@ -1963,7 +1963,7 @@ sub git_print_page_path { print "
"; print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), - -title => 'tree root'}, "[$project]"); + -title => 'tree root'}, to_utf8("[$project]"); print " / "; if (defined $name) { my @dirname = split '/', $name; @@ -3610,7 +3610,7 @@ sub git_snapshot { $hash = git_get_head_hash($project); } - my $filename = basename($project) . "-$hash.tar.$suffix"; + my $filename = to_utf8(basename($project)) . "-$hash.tar.$suffix"; print $cgi->header( -type => "application/$ctype", From 3fb624521e059c6c3caef470a9ff03f72b86036c Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Tue, 30 Jan 2007 13:26:50 +0530 Subject: [PATCH 411/548] blameview: Support browsable functionality to blameview. Double clicking on the row execs a new blameview with commit hash as argument. Signed-off-by: Aneesh Kumar K.V Signed-off-by: Junio C Hamano --- contrib/blameview/blameview.perl | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/contrib/blameview/blameview.perl b/contrib/blameview/blameview.perl index 5e9a67c123..807d01fe3d 100755 --- a/contrib/blameview/blameview.perl +++ b/contrib/blameview/blameview.perl @@ -3,7 +3,17 @@ use Gtk2 -init; use Gtk2::SimpleList; -my $fn = shift or die "require filename to blame"; +my $hash; +my $fn; +if ( @ARGV == 1 ) { + $hash = "HEAD"; + $fn = shift; +} elsif ( @ARGV == 2 ) { + $hash = shift; + $fn = shift; +} else { + die "Usage blameview [] "; +} Gtk2::Rc->parse_string(<<'EOS'); style "treeview_style" @@ -27,17 +37,24 @@ $scrolled_window->add($fileview); $fileview->get_column(0)->set_spacing(0); $fileview->set_size_request(1024, 768); $fileview->set_rules_hint(1); +$fileview->signal_connect (row_activated => sub { + my ($sl, $path, $column) = @_; + my $row_ref = $sl->get_row_data_from_path ($path); + system("blameview @$row_ref[0] $fn"); + # $row_ref is now an array ref to the double-clicked row's data. + }); my $fh; -open($fh, '-|', "git cat-file blob HEAD:$fn") +open($fh, '-|', "git cat-file blob $hash:$fn") or die "unable to open $fn: $!"; + while(<$fh>) { chomp; $fileview->{data}->[$.] = ['HEAD', '?', "$fn:$.", $_]; } my $blame; -open($blame, '-|', qw(git blame --incremental --), $fn) +open($blame, '-|', qw(git blame --incremental --), $fn, $hash) or die "cannot start git-blame $fn"; Glib::IO->add_watch(fileno($blame), 'in', \&read_blame_line); From 98d47d4ccf76725e7833c1bbda1da82f7648925f Mon Sep 17 00:00:00 2001 From: Stelian Pop Date: Fri, 12 Jan 2007 22:57:03 +0100 Subject: [PATCH 412/548] Add hg-to-git conversion utility. hg-to-git.py is able to convert a Mercurial repository into a git one, and preserves the branches in the process (unlike tailor) hg-to-git.py can probably be greatly improved (it's a rather crude combination of shell and python) but it does already work quite well for me. Features: - supports incremental conversion (for keeping a git repo in sync with a hg one) - supports hg branches - converts hg tags Signed-off-by: Stelian Pop Signed-off-by: Junio C Hamano --- Documentation/git-tools.txt | 8 ++ contrib/hg-to-git/hg-to-git.py | 233 ++++++++++++++++++++++++++++++++ contrib/hg-to-git/hg-to-git.txt | 21 +++ 3 files changed, 262 insertions(+) create mode 100755 contrib/hg-to-git/hg-to-git.py create mode 100644 contrib/hg-to-git/hg-to-git.txt diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt index 6b407f9adf..10653ff898 100644 --- a/Documentation/git-tools.txt +++ b/Documentation/git-tools.txt @@ -86,6 +86,14 @@ Foreign SCM interface series in git back and forth. + - *hg-to-git* (contrib/) + + hg-to-git converts a Mercurial repository into a git one, and + preserves the full branch history in the process. hg-to-git can + also be used in an incremental way to keep the git repository + in sync with the master Mercurial repository. + + Others ------ diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py new file mode 100755 index 0000000000..37337ff01f --- /dev/null +++ b/contrib/hg-to-git/hg-to-git.py @@ -0,0 +1,233 @@ +#! /usr/bin/python + +""" hg-to-svn.py - A Mercurial to GIT converter + + Copyright (C)2007 Stelian Pop + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os, os.path, sys +import tempfile, popen2, pickle, getopt +import re + +# Maps hg version -> git version +hgvers = {} +# List of children for each hg revision +hgchildren = {} +# Current branch for each hg revision +hgbranch = {} + +#------------------------------------------------------------------------------ + +def usage(): + + print """\ +%s: [OPTIONS] + +options: + -s, --gitstate=FILE: name of the state to be saved/read + for incrementals + +required: + hgprj: name of the HG project to import (directory) +""" % sys.argv[0] + +#------------------------------------------------------------------------------ + +def getgitenv(user, date): + env = '' + elems = re.compile('(.*?)\s+<(.*)>').match(user) + if elems: + env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1) + env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1) + env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2) + env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2) + else: + env += 'export GIT_AUTHOR_NAME="%s" ;' % user + env += 'export GIT_COMMITER_NAME="%s" ;' % user + env += 'export GIT_AUTHOR_EMAIL= ;' + env += 'export GIT_COMMITER_EMAIL= ;' + + env += 'export GIT_AUTHOR_DATE="%s" ;' % date + env += 'export GIT_COMMITTER_DATE="%s" ;' % date + return env + +#------------------------------------------------------------------------------ + +state = '' + +try: + opts, args = getopt.getopt(sys.argv[1:], 's:t:', ['gitstate=', 'tempdir=']) + for o, a in opts: + if o in ('-s', '--gitstate'): + state = a + state = os.path.abspath(state) + + if len(args) != 1: + raise('params') +except: + usage() + sys.exit(1) + +hgprj = args[0] +os.chdir(hgprj) + +if state: + if os.path.exists(state): + print 'State does exist, reading' + f = open(state, 'r') + hgvers = pickle.load(f) + else: + print 'State does not exist, first run' + +tip = os.popen('hg tip | head -1 | cut -f 2 -d :').read().strip() +print 'tip is', tip + +# Calculate the branches +print 'analysing the branches...' +hgchildren["0"] = () +hgbranch["0"] = "master" +for cset in range(1, int(tip) + 1): + hgchildren[str(cset)] = () + prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines() + if len(prnts) > 0: + parent = prnts[0].strip() + else: + parent = str(cset - 1) + hgchildren[parent] += ( str(cset), ) + if len(prnts) > 1: + mparent = prnts[1].strip() + hgchildren[mparent] += ( str(cset), ) + else: + mparent = None + + if mparent: + # For merge changesets, take either one, preferably the 'master' branch + if hgbranch[mparent] == 'master': + hgbranch[str(cset)] = 'master' + else: + hgbranch[str(cset)] = hgbranch[parent] + else: + # Normal changesets + # For first children, take the parent branch, for the others create a new branch + if hgchildren[parent][0] == str(cset): + hgbranch[str(cset)] = hgbranch[parent] + else: + hgbranch[str(cset)] = "branch-" + str(cset) + +if not hgvers.has_key("0"): + print 'creating repository' + os.system('git-init-db') + +# loop through every hg changeset +for cset in range(int(tip) + 1): + + # incremental, already seen + if hgvers.has_key(str(cset)): + continue + + # get info + prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines() + if len(prnts) > 0: + parent = prnts[0].strip() + else: + parent = str(cset - 1) + if len(prnts) > 1: + mparent = prnts[1].strip() + else: + mparent = None + + (fdcomment, filecomment) = tempfile.mkstemp() + csetcomment = os.popen('hg log -r %d -v | grep -v ^changeset: | grep -v ^parent: | grep -v ^user: | grep -v ^date | grep -v ^files: | grep -v ^description: | grep -v ^tag:' % cset).read().strip() + os.write(fdcomment, csetcomment) + os.close(fdcomment) + + date = os.popen('hg log -r %d | grep ^date: | cut -f 2- -d :' % cset).read().strip() + + tag = os.popen('hg log -r %d | grep ^tag: | cut -f 2- -d :' % cset).read().strip() + + user = os.popen('hg log -r %d | grep ^user: | cut -f 2- -d :' % cset).read().strip() + + print '-----------------------------------------' + print 'cset:', cset + print 'branch:', hgbranch[str(cset)] + print 'user:', user + print 'date:', date + print 'comment:', csetcomment + print 'parent:', parent + if mparent: + print 'mparent:', mparent + if tag: + print 'tag:', tag + print '-----------------------------------------' + + # checkout the parent if necessary + if cset != 0: + if hgbranch[str(cset)] == "branch-" + str(cset): + print 'creating new branch', hgbranch[str(cset)] + os.system('git-checkout -b %s %s' % (hgbranch[str(cset)], hgvers[parent])) + else: + print 'checking out branch', hgbranch[str(cset)] + os.system('git-checkout %s' % hgbranch[str(cset)]) + + # merge + if mparent: + if hgbranch[parent] == hgbranch[str(cset)]: + otherbranch = hgbranch[mparent] + else: + otherbranch = hgbranch[parent] + print 'merging', otherbranch, 'into', hgbranch[str(cset)] + os.system(getgitenv(user, date) + 'git-merge --no-commit -s ours "" %s %s' % (hgbranch[str(cset)], otherbranch)) + + # remove everything except .git and .hg directories + os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf') + + # repopulate with checkouted files + os.system('hg update -C %d' % cset) + + # add new files + os.system('git-ls-files -x .hg --others | git-update-index --add --stdin') + # delete removed files + os.system('git-ls-files -x .hg --deleted | git-update-index --remove --stdin') + + # commit + os.system(getgitenv(user, date) + 'git-commit -a -F %s' % filecomment) + os.unlink(filecomment) + + # tag + if tag and tag != 'tip': + os.system(getgitenv(user, date) + 'git-tag %s' % tag) + + # delete branch if not used anymore... + if mparent and len(hgchildren[str(cset)]): + print "Deleting unused branch:", otherbranch + os.system('git-branch -d %s' % otherbranch) + + # retrieve and record the version + vvv = os.popen('git-show | head -1').read() + vvv = vvv[vvv.index(' ') + 1 : ].strip() + print 'record', cset, '->', vvv + hgvers[str(cset)] = vvv + +os.system('git-repack -a -d') + +# write the state for incrementals +if state: + print 'Writing state' + f = open(state, 'w') + pickle.dump(hgvers, f) + +# vim: et ts=8 sw=4 sts=4 diff --git a/contrib/hg-to-git/hg-to-git.txt b/contrib/hg-to-git/hg-to-git.txt new file mode 100644 index 0000000000..91f8fe6410 --- /dev/null +++ b/contrib/hg-to-git/hg-to-git.txt @@ -0,0 +1,21 @@ +hg-to-git.py is able to convert a Mercurial repository into a git one, +and preserves the branches in the process (unlike tailor) + +hg-to-git.py can probably be greatly improved (it's a rather crude +combination of shell and python) but it does already work quite well for +me. Features: + - supports incremental conversion + (for keeping a git repo in sync with a hg one) + - supports hg branches + - converts hg tags + +Note that the git repository will be created 'in place' (at the same +location as the source hg repo). You will have to manually remove the +'.hg' directory after the conversion. + +Also note that the incremental conversion uses 'simple' hg changesets +identifiers (ordinals, as opposed to SHA-1 ids), and since these ids +are not stable across different repositories the hg-to-git.py state file +is forever tied to one hg repository. + +Stelian Pop From 6d9ba67b0fc2f18c1d7a65514edbdcdf86429c1c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 23 Jan 2007 13:30:20 +0100 Subject: [PATCH 413/548] Commands requiring a work tree must not run in GIT_DIR This patch helps when you accidentally run something like git-clean in the git directory instead of the work tree. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-ls-files.c | 10 +++++++++- builtin-rev-parse.c | 5 +++++ git-sh-setup.sh | 3 ++- git.c | 5 +++-- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 21c2a6e2d9..ac89eb2f77 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -323,7 +323,7 @@ static const char ls_files_usage[] = int cmd_ls_files(int argc, const char **argv, const char *prefix) { int i; - int exc_given = 0; + int exc_given = 0, require_work_tree = 0; struct dir_struct dir; memset(&dir, 0, sizeof(dir)); @@ -363,14 +363,17 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) { show_modified = 1; + require_work_tree = 1; continue; } if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) { show_others = 1; + require_work_tree = 1; continue; } if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) { dir.show_ignored = 1; + require_work_tree = 1; continue; } if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) { @@ -379,6 +382,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) { show_killed = 1; + require_work_tree = 1; continue; } if (!strcmp(arg, "--directory")) { @@ -447,6 +451,10 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) break; } + if (require_work_tree && + (is_bare_repository() || is_inside_git_dir())) + die("This operation must be run in a work tree"); + pathspec = get_pathspec(prefix, argv + i); /* Verify that the pathspec matches the prefix */ diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 3b716fba13..d53deaa369 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -347,6 +347,11 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) printf("%s/.git\n", cwd); continue; } + if (!strcmp(arg, "--is-inside-git-dir")) { + printf("%s\n", is_inside_git_dir() ? "true" + : "false"); + continue; + } if (!strncmp(arg, "--since=", 8)) { show_datestring("--max-age=", arg+8); continue; diff --git a/git-sh-setup.sh b/git-sh-setup.sh index b4aa4b2f4e..f24c7f2d23 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -48,7 +48,8 @@ cd_to_toplevel () { } require_work_tree () { - test $(is_bare_repository) = false || + test $(is_bare_repository) = false && + test $(git-rev-parse --is-inside-git-dir) = false || die "fatal: $0 cannot be used without a working tree." } diff --git a/git.c b/git.c index fb03a547de..82a8357272 100644 --- a/git.c +++ b/git.c @@ -299,8 +299,9 @@ static void handle_internal_command(int argc, const char **argv, char **envp) prefix = setup_git_directory(); if (p->option & USE_PAGER) setup_pager(); - if ((p->option & NOT_BARE) && is_bare_repository()) - die("%s cannot be used in a bare git directory", cmd); + if ((p->option & NOT_BARE) && + (is_bare_repository() || is_inside_git_dir())) + die("%s must be run in a work tree", cmd); trace_argv_printf(argv, argc, "trace: built-in: git"); exit(p->fn(argc, argv, prefix)); From 4c55068683ac2e9765ba6e112d2227e50fefae87 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 5 Feb 2007 14:03:27 -0800 Subject: [PATCH 414/548] bisect: it needs to be done in a working tree. Signed-off-by: Junio C Hamano --- git-bisect.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/git-bisect.sh b/git-bisect.sh index e8d3418988..24160ec51c 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -11,6 +11,7 @@ git bisect replay replay bisection log git bisect log show bisect log.' . git-sh-setup +require_work_tree sq() { @@PERL@@ -e ' From ca28370a3567c14841c594b89fd62449abc166e6 Mon Sep 17 00:00:00 2001 From: Simon 'corecode' Schubert Date: Thu, 1 Feb 2007 11:43:39 +0100 Subject: [PATCH 415/548] Allow forcing of a parent commit, even if the parent is not a direct one. This can be used to compress multiple changesets into one, for example like git cvsexportcommit -P cvshead mybranch without having to do so in git first. Signed-off-by: Simon 'corecode' Schubert Signed-off-by: Junio C Hamano --- Documentation/git-cvsexportcommit.txt | 5 ++++- git-cvsexportcommit.perl | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index 347cbcec35..27d531b888 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -8,7 +8,7 @@ git-cvsexportcommit - Export a single commit to a CVS checkout SYNOPSIS -------- -'git-cvsexportcommit' [-h] [-v] [-c] [-p] [-a] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID +'git-cvsexportcommit' [-h] [-v] [-c] [-P] [-p] [-a] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID DESCRIPTION @@ -46,6 +46,9 @@ OPTIONS -f:: Force the merge even if the files are not up to date. +-P:: + Force the parent commit, even if it is not a direct parent. + -m:: Prepend the commit message with the provided prefix. Useful for patch series and the like. diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 4863c91fe3..870554eade 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -15,9 +15,9 @@ unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){ die "GIT_DIR is not defined or is unreadable"; } -our ($opt_h, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m ); +our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m ); -getopts('hpvcfam:'); +getopts('hPpvcfam:'); $opt_h && usage(); @@ -89,7 +89,7 @@ if ($parent) { last; }; # found it } - die "Did not find $parent in the parents for this commit!" if !$found; + die "Did not find $parent in the parents for this commit!" if !$found and !$opt_P; } else { # we don't have a parent from the cmdline... if (@parents == 1) { # it's safe to get it from the commit $parent = $parents[0]; From 28389d45fb2161d4dcdc1bbfabbcc2fb135914c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20K=C3=A5gedal?= Date: Mon, 5 Feb 2007 14:22:28 -0800 Subject: [PATCH 416/548] git-blame: an Emacs minor mode to view file with git-blame output. Here's another version of git-blame.el that automatically tries to create a sensible list of colors to use for both light and dark backgrounds. Plus a few minor fixes. To use: 1) Load into emacs: M-x load-file RET git-blame.el RET 2) Open a git-controlled file 3) Blame: M-x git-blame-mode Signed-off-by: Junio C Hamano --- contrib/emacs/git-blame.el | 180 +++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 contrib/emacs/git-blame.el diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el new file mode 100644 index 0000000000..62cf24c996 --- /dev/null +++ b/contrib/emacs/git-blame.el @@ -0,0 +1,180 @@ +;;; git-blame.el +;; David KÃ¥gedal +;; Message-ID: <87iren2vqx.fsf@morpheus.local> + +(require 'cl) +(defun color-scale (l) + (let* ((colors ()) + r g b) + (setq r l) + (while r + (setq g l) + (while g + (setq b l) + (while b + (push (concat "#" (car r) (car g) (car b)) colors) + (pop b)) + (pop g)) + (pop r)) + colors)) + +(defvar git-blame-dark-colors + (color-scale '("00" "04" "08" "0c" + "10" "14" "18" "1c" + "20" "24" "28" "2c" + "30" "34" "38" "3c"))) + +(defvar git-blame-light-colors + (color-scale '("c0" "c4" "c8" "cc" + "d0" "d4" "d8" "dc" + "e0" "e4" "e8" "ec" + "f0" "f4" "f8" "fc"))) + +(defvar git-blame-ancient-color "dark green") + +(defvar git-blame-overlays nil) +(defvar git-blame-cache nil) + +(defvar git-blame-mode nil) +(make-variable-buffer-local 'git-blame-mode) +(push (list 'git-blame-mode " blame") minor-mode-alist) + +(defun git-blame-mode (&optional arg) + (interactive "P") + (if arg + (setq git-blame-mode (eq arg 1)) + (setq git-blame-mode (not git-blame-mode))) + (make-local-variable 'git-blame-overlays) + (make-local-variable 'git-blame-colors) + (make-local-variable 'git-blame-cache) + (let ((bgmode (cdr (assoc 'background-mode (frame-parameters))))) + (if (eq bgmode 'dark) + (setq git-blame-colors git-blame-dark-colors) + (setq git-blame-colors git-blame-light-colors))) + (if git-blame-mode + (git-blame-run) + (git-blame-cleanup))) + +(defun git-blame-run () + (let* ((display-buf (current-buffer)) + (blame-buf (get-buffer-create + (concat " git blame for " (buffer-name)))) + (proc (start-process "git-blame" blame-buf + "git" "blame" "--incremental" + (file-name-nondirectory buffer-file-name)))) + (mapcar 'delete-overlay git-blame-overlays) + (setq git-blame-overlays nil) + (setq git-blame-cache (make-hash-table :test 'equal)) + (with-current-buffer blame-buf + (erase-buffer) + (make-local-variable 'git-blame-file) + (make-local-variable 'git-blame-current) + (setq git-blame-file display-buf) + (setq git-blame-current nil)) + (set-process-filter proc 'git-blame-filter) + (set-process-sentinel proc 'git-blame-sentinel))) + +(defun git-blame-cleanup () + "Remove all blame properties" + (mapcar 'delete-overlay git-blame-overlays) + (setq git-blame-overlays nil) + (let ((modified (buffer-modified-p))) + (remove-text-properties (point-min) (point-max) '(point-entered nil)) + (set-buffer-modified-p modified))) + +(defun git-blame-sentinel (proc status) + ;;(kill-buffer (process-buffer proc)) + (message "git blame finished")) + +(defvar in-blame-filter nil) + +(defun git-blame-filter (proc str) + (save-excursion + (set-buffer (process-buffer proc)) + (goto-char (process-mark proc)) + (insert-before-markers str) + (goto-char 0) + (unless in-blame-filter + (let ((more t) + (in-blame-filter t)) + (while more + (setq more (git-blame-parse))))))) + +(defun git-blame-parse () + (cond ((looking-at "\\([0-9a-f]\\{40\\}\\) \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\)\n") + (let ((hash (match-string 1)) + (src-line (string-to-number (match-string 2))) + (res-line (string-to-number (match-string 3))) + (num-lines (string-to-number (match-string 4)))) + (setq git-blame-current + (git-blame-new-commit + hash src-line res-line num-lines))) + (delete-region (point) (match-end 0)) + t) + ((looking-at "filename \\(.+\\)\n") + (let ((filename (match-string 1))) + (git-blame-add-info "filename" filename)) + (delete-region (point) (match-end 0)) + t) + ((looking-at "\\([a-z-]+\\) \\(.+\\)\n") + (let ((key (match-string 1)) + (value (match-string 2))) + (git-blame-add-info key value)) + (delete-region (point) (match-end 0)) + t) + ((looking-at "boundary\n") + (setq git-blame-current nil) + (delete-region (point) (match-end 0)) + t) + (t + nil))) + + +(defun git-blame-new-commit (hash src-line res-line num-lines) + (save-excursion + (set-buffer git-blame-file) + (let ((info (gethash hash git-blame-cache)) + (inhibit-point-motion-hooks t)) + (when (not info) + (let ((color (pop git-blame-colors))) + (unless color + (setq color git-blame-ancient-color)) + (setq info (list hash src-line res-line num-lines + (cons 'color color)))) + (puthash hash info git-blame-cache)) + (goto-line res-line) + (while (> num-lines 0) + (if (get-text-property (point) 'git-blame) + (forward-line) + (let* ((start (point)) + (end (progn (forward-line 1) (point))) + (ovl (make-overlay start end))) + (push ovl git-blame-overlays) + (overlay-put ovl 'git-blame info) + (overlay-put ovl 'help-echo hash) + (overlay-put ovl 'face (list :background + (cdr (assq 'color (cddddr info))))) + ;;(overlay-put ovl 'point-entered + ;; `(lambda (x y) (git-blame-identify ,hash))) + (let ((modified (buffer-modified-p))) + (put-text-property (if (= start 1) start (1- start)) (1- end) + 'point-entered + `(lambda (x y) (git-blame-identify ,hash))) + (set-buffer-modified-p modified)))) + (setq num-lines (1- num-lines)))))) + +(defun git-blame-add-info (key value) + (if git-blame-current + (nconc git-blame-current (list (cons (intern key) value))))) + +(defun git-blame-current-commit () + (let ((info (get-char-property (point) 'git-blame))) + (if info + (car info) + (error "No commit info")))) + +(defun git-blame-identify (&optional hash) + (interactive) + (shell-command + (format "git log -1 --pretty=oneline %s" (or hash + (git-blame-current-commit))))) From 1cfe77333f274c9ba9879c2eb61057a790eb050f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 30 Jan 2007 01:11:08 -0800 Subject: [PATCH 417/548] git-blame: no rev means start from the working tree file. Warning: this changes the semantics. This makes "git blame" without any positive rev to start digging from the working tree copy, which is made into a fake commit whose sole parent is the HEAD. It also adds --contents option to pretend as if the working tree copy has the contents of the named file. You can use '-' to make the command read from the standard input. If you want the command to start annotating from the HEAD commit, you need to explicitly give HEAD parameter. Signed-off-by: Junio C Hamano --- builtin-blame.c | 209 ++++++++++++++++++++++++++++++++++++++++++------ diff-lib.c | 44 +++++++++- diff.h | 1 + 3 files changed, 228 insertions(+), 26 deletions(-) diff --git a/builtin-blame.c b/builtin-blame.c index 1c21204a2c..897323a4b2 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -15,9 +15,10 @@ #include "revision.h" #include "quote.h" #include "xdiff-interface.h" +#include "cache-tree.h" static char blame_usage[] = -"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [commit] [--] file\n" +"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [--contents ] [commit] [--] file\n" " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" " -b Show blank SHA-1 for boundary commits (Default: off)\n" " -l, --long Show long commit SHA1 (Default: off)\n" @@ -29,6 +30,7 @@ static char blame_usage[] = " -L n,m Process only line range n,m, counting from 1\n" " -M, -C Find line movements within and across files\n" " --incremental Show blame entries as we find them, incrementally\n" +" --contents file Use 's contents as the final image\n" " -S revs-file Use revisions from revs-file instead of calling git-rev-list\n"; static int longest_file; @@ -333,9 +335,13 @@ static struct origin *find_origin(struct scoreboard *sb, diff_tree_setup_paths(paths, &diff_opts); if (diff_setup_done(&diff_opts) < 0) die("diff-setup"); - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); + + if (is_null_sha1(origin->commit->object.sha1)) + do_diff_cache(parent->tree->object.sha1, &diff_opts); + else + diff_tree_sha1(parent->tree->object.sha1, + origin->commit->tree->object.sha1, + "", &diff_opts); diffcore_std(&diff_opts); /* It is either one entry that says "modified", or "created", @@ -402,9 +408,13 @@ static struct origin *find_rename(struct scoreboard *sb, diff_tree_setup_paths(paths, &diff_opts); if (diff_setup_done(&diff_opts) < 0) die("diff-setup"); - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); + + if (is_null_sha1(origin->commit->object.sha1)) + do_diff_cache(parent->tree->object.sha1, &diff_opts); + else + diff_tree_sha1(parent->tree->object.sha1, + origin->commit->tree->object.sha1, + "", &diff_opts); diffcore_std(&diff_opts); for (i = 0; i < diff_queued_diff.nr; i++) { @@ -1047,9 +1057,12 @@ static int find_copy_in_parent(struct scoreboard *sb, (!porigin || strcmp(target->path, porigin->path))) diff_opts.find_copies_harder = 1; - diff_tree_sha1(parent->tree->object.sha1, - target->commit->tree->object.sha1, - "", &diff_opts); + if (is_null_sha1(target->commit->object.sha1)) + do_diff_cache(parent->tree->object.sha1, &diff_opts); + else + diff_tree_sha1(parent->tree->object.sha1, + target->commit->tree->object.sha1, + "", &diff_opts); if (!diff_opts.find_copies_harder) diffcore_std(&diff_opts); @@ -1336,9 +1349,9 @@ static void get_commit_info(struct commit *commit, tmp += 2; endp = strchr(tmp, '\n'); if (!endp) - goto error_out; + endp = tmp + strlen(tmp); len = endp - tmp; - if (len >= sizeof(summary_buf)) + if (len >= sizeof(summary_buf) || len == 0) goto error_out; memcpy(summary_buf, tmp, len); summary_buf[len] = 0; @@ -1910,6 +1923,137 @@ static int git_blame_config(const char *var, const char *value) return git_default_config(var, value); } +static struct commit *fake_working_tree_commit(const char *path, const char *contents_from) +{ + struct commit *commit; + struct origin *origin; + unsigned char head_sha1[20]; + char *buf; + const char *ident; + int fd; + time_t now; + unsigned long fin_size; + int size, len; + struct cache_entry *ce; + unsigned mode; + + if (get_sha1("HEAD", head_sha1)) + die("No such ref: HEAD"); + + time(&now); + commit = xcalloc(1, sizeof(*commit)); + commit->parents = xcalloc(1, sizeof(*commit->parents)); + commit->parents->item = lookup_commit_reference(head_sha1); + commit->object.parsed = 1; + commit->date = now; + commit->object.type = OBJ_COMMIT; + + origin = make_origin(commit, path); + + if (!contents_from || strcmp("-", contents_from)) { + struct stat st; + const char *read_from; + + if (contents_from) { + if (stat(contents_from, &st) < 0) + die("Cannot stat %s", contents_from); + read_from = contents_from; + } + else { + if (lstat(path, &st) < 0) + die("Cannot lstat %s", path); + read_from = path; + } + fin_size = st.st_size; + buf = xmalloc(fin_size+1); + mode = canon_mode(st.st_mode); + switch (st.st_mode & S_IFMT) { + case S_IFREG: + fd = open(read_from, O_RDONLY); + if (fd < 0) + die("cannot open %s", read_from); + if (read_in_full(fd, buf, fin_size) != fin_size) + die("cannot read %s", read_from); + break; + case S_IFLNK: + if (readlink(read_from, buf, fin_size+1) != fin_size) + die("cannot readlink %s", read_from); + break; + default: + die("unsupported file type %s", read_from); + } + } + else { + /* Reading from stdin */ + contents_from = "standard input"; + buf = NULL; + fin_size = 0; + mode = 0; + while (1) { + ssize_t cnt = 8192; + buf = xrealloc(buf, fin_size + cnt); + cnt = xread(0, buf + fin_size, cnt); + if (cnt < 0) + die("read error %s from stdin", + strerror(errno)); + if (!cnt) + break; + fin_size += cnt; + } + buf = xrealloc(buf, fin_size + 1); + } + buf[fin_size] = 0; + origin->file.ptr = buf; + origin->file.size = fin_size; + write_sha1_file(buf, fin_size, blob_type, origin->blob_sha1); + commit->util = origin; + + /* + * Read the current index, replace the path entry with + * origin->blob_sha1 without mucking with its mode or type + * bits; we are not going to write this index out -- we just + * want to run "diff-index --cached". + */ + discard_cache(); + read_cache(); + + len = strlen(path); + if (!mode) { + int pos = cache_name_pos(path, len); + if (0 <= pos) + mode = ntohl(active_cache[pos]->ce_mode); + else + /* Let's not bother reading from HEAD tree */ + mode = S_IFREG | 0644; + } + size = cache_entry_size(len); + ce = xcalloc(1, size); + hashcpy(ce->sha1, origin->blob_sha1); + memcpy(ce->name, path, len); + ce->ce_flags = create_ce_flags(len, 0); + ce->ce_mode = create_ce_mode(mode); + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + + /* + * We are not going to write this out, so this does not matter + * right now, but someday we might optimize diff-index --cached + * with cache-tree information. + */ + cache_tree_invalidate_path(active_cache_tree, path); + + commit->buffer = xmalloc(400); + ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0); + sprintf(commit->buffer, + "tree 0000000000000000000000000000000000000000\n" + "parent %s\n" + "author %s\n" + "committer %s\n\n" + "Version of %s from %s\n", + sha1_to_hex(head_sha1), + ident, ident, path, contents_from ? contents_from : path); + return commit; +} + int cmd_blame(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1924,6 +2068,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) const char *final_commit_name = NULL; char type[10]; const char *bottomtop = NULL; + const char *contents_from = NULL; git_config(git_blame_config); save_commit_buffer = 0; @@ -1968,6 +2113,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix) die("More than one '-L n,m' option given"); bottomtop = arg; } + else if (!strcmp("--contents", arg)) { + if (++i >= argc) + usage(blame_usage); + contents_from = argv[i]; + } else if (!strcmp("--incremental", arg)) incremental = 1; else if (!strcmp("--score-debug", arg)) @@ -2087,7 +2237,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) argv[unk] = NULL; init_revisions(&revs, NULL); - setup_revisions(unk, argv, &revs, "HEAD"); + setup_revisions(unk, argv, &revs, NULL); memset(&sb, 0, sizeof(sb)); /* @@ -2114,16 +2264,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix) if (!sb.final) { /* * "--not A B -- path" without anything positive; - * default to HEAD. + * do not default to HEAD, but use the working tree + * or "--contents". */ - unsigned char head_sha1[20]; - - final_commit_name = "HEAD"; - if (get_sha1(final_commit_name, head_sha1)) - die("No such ref: HEAD"); - sb.final = lookup_commit_reference(head_sha1); - add_pending_object(&revs, &(sb.final->object), "HEAD"); + sb.final = fake_working_tree_commit(path, contents_from); + add_pending_object(&revs, &(sb.final->object), ":"); } + else if (contents_from) + die("Cannot use --contents with final commit object name"); /* * If we have bottom, this will mark the ancestors of the @@ -2132,11 +2280,22 @@ int cmd_blame(int argc, const char **argv, const char *prefix) */ prepare_revision_walk(&revs); - o = get_origin(&sb, sb.final, path); - if (fill_blob_sha1(o)) - die("no such path %s in %s", path, final_commit_name); + if (is_null_sha1(sb.final->object.sha1)) { + char *buf; + o = sb.final->util; + buf = xmalloc(o->file.size + 1); + memcpy(buf, o->file.ptr, o->file.size + 1); + sb.final_buf = buf; + sb.final_buf_size = o->file.size; + } + else { + o = get_origin(&sb, sb.final, path); + if (fill_blob_sha1(o)) + die("no such path %s in %s", path, final_commit_name); - sb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size); + sb.final_buf = read_sha1_file(o->blob_sha1, type, + &sb.final_buf_size); + } num_read_blob++; lno = prepare_lines(&sb); diff --git a/diff-lib.c b/diff-lib.c index 2c9be60ed9..91cd87742f 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -7,6 +7,7 @@ #include "diff.h" #include "diffcore.h" #include "revision.h" +#include "cache-tree.h" /* * diff-files @@ -271,7 +272,7 @@ static int diff_cache(struct rev_info *revs, break; } /* Show difference between old and new */ - show_modified(revs,ac[1], ce, 1, + show_modified(revs, ac[1], ce, 1, cached, match_missing); break; case 1: @@ -372,3 +373,44 @@ int run_diff_index(struct rev_info *revs, int cached) diff_flush(&revs->diffopt); return ret; } + +int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) +{ + struct tree *tree; + struct rev_info revs; + int i; + struct cache_entry **dst; + struct cache_entry *last = NULL; + + /* + * This is used by git-blame to run diff-cache internally; + * it potentially needs to repeatedly run this, so we will + * start by removing the higher order entries the last round + * left behind. + */ + dst = active_cache; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce)) { + if (last && !strcmp(ce->name, last->name)) + continue; + cache_tree_invalidate_path(active_cache_tree, + ce->name); + last = ce; + ce->ce_mode = 0; + ce->ce_flags &= ~htons(CE_STAGEMASK); + } + *dst++ = ce; + } + active_nr = dst - active_cache; + + init_revisions(&revs, NULL); + revs.prune_data = opt->paths; + tree = parse_tree_indirect(tree_sha1); + if (!tree) + die("bad tree object %s", sha1_to_hex(tree_sha1)); + if (read_tree(tree, 1, opt->paths)) + return error("unable to read tree %s", sha1_to_hex(tree_sha1)); + return diff_cache(&revs, active_cache, active_nr, revs.prune_data, + 1, 0); +} diff --git a/diff.h b/diff.h index 7a347cf77d..eece65ddcc 100644 --- a/diff.h +++ b/diff.h @@ -222,6 +222,7 @@ extern int run_diff_files(struct rev_info *revs, int silent_on_removed); extern int run_diff_index(struct rev_info *revs, int cached); +extern int do_diff_cache(const unsigned char *, struct diff_options *); extern int diff_flush_patch_id(struct diff_options *, unsigned char *); #endif /* DIFF_H */ From d66b37bb19ff7a347ba4ccbcdc11b00772518b57 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 4 Feb 2007 21:42:38 -0800 Subject: [PATCH 418/548] Add pretend_sha1_file() interface. The new interface allows an application to temporarily hash a small number of objects and pretend that they are available in the object store without actually writing them. Signed-off-by: Junio C Hamano --- cache.h | 1 + sha1_file.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/cache.h b/cache.h index 38a9bc02f6..87142571b3 100644 --- a/cache.h +++ b/cache.h @@ -257,6 +257,7 @@ extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, uns extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size); extern int hash_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1); extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1); +extern int pretend_sha1_file(void *, unsigned long, const char *, unsigned char *); extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type); diff --git a/sha1_file.c b/sha1_file.c index 1526a28095..0d4bf80e74 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1505,10 +1505,67 @@ static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned lo return unpack_entry(e.p, e.offset, type, size); } +/* + * This is meant to hold a *small* number of objects that you would + * want read_sha1_file() to be able to return, but yet you do not want + * to write them into the object store (e.g. a browse-only + * application). + */ +static struct cached_object { + unsigned char sha1[20]; + const char *type; + void *buf; + unsigned long size; +} *cached_objects; +static int cached_object_nr, cached_object_alloc; + +static struct cached_object *find_cached_object(const unsigned char *sha1) +{ + int i; + struct cached_object *co = cached_objects; + + for (i = 0; i < cached_object_nr; i++, co++) { + if (!hashcmp(co->sha1, sha1)) + return co; + } + return NULL; +} + +int pretend_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1) +{ + struct cached_object *co; + + hash_sha1_file(buf, len, type, sha1); + if (has_sha1_file(sha1) || find_cached_object(sha1)) + return 0; + if (cached_object_alloc <= cached_object_nr) { + cached_object_alloc = alloc_nr(cached_object_alloc); + cached_objects = xrealloc(cached_objects, + sizeof(*cached_objects) * + cached_object_alloc); + } + co = &cached_objects[cached_object_nr++]; + co->size = len; + co->type = strdup(type); + hashcpy(co->sha1, sha1); + return 0; +} + void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size) { unsigned long mapsize; void *map, *buf; + struct cached_object *co; + + co = find_cached_object(sha1); + if (co) { + buf = xmalloc(co->size + 1); + memcpy(buf, co->buf, co->size); + ((char*)buf)[co->size] = 0; + strcpy(type, co->type); + *size = co->size; + return buf; + } buf = read_packed_sha1(sha1, type, size); if (buf) From 005f85d9ae95c44d8c6ecf61364642fbcaf49dd2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 4 Feb 2007 21:49:05 -0800 Subject: [PATCH 419/548] Use pretend_sha1_file() in git-blame and git-merge-recursive. git-merge-recursive wants an null tree as the fake merge base while producing the merge result tree. The null tree does not have to be written out in the object store as it won't be part of the result, and it is a prime example for using the new pretend_sha1_file() function. git-blame needs to register an arbitrary data to in-core index while annotating a working tree file (or standard input), but git-blame is a read-only application and the user of it could even lack the privilege to write into the object store; it is another good example for pretend_sha1_file(). Signed-off-by: Junio C Hamano --- builtin-blame.c | 2 +- merge-recursive.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin-blame.c b/builtin-blame.c index 897323a4b2..fb30c49280 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -2005,7 +2005,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con buf[fin_size] = 0; origin->file.ptr = buf; origin->file.size = fin_size; - write_sha1_file(buf, fin_size, blob_type, origin->blob_sha1); + pretend_sha1_file(buf, fin_size, blob_type, origin->blob_sha1); commit->util = origin; /* diff --git a/merge-recursive.c b/merge-recursive.c index a68fcc6f37..58989424d7 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1213,7 +1213,7 @@ static int merge(struct commit *h1, tree->object.parsed = 1; tree->object.type = OBJ_TREE; - write_sha1_file(NULL, 0, tree_type, tree->object.sha1); + pretend_sha1_file(NULL, 0, tree_type, tree->object.sha1); merged_common_ancestors = make_virtual_commit(tree, "ancestor"); } From 06e75a72372d0466e110002a7504fefebe0e9c52 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 5 Feb 2007 15:04:01 -0800 Subject: [PATCH 420/548] blame: document --contents option Signed-off-by: Junio C Hamano --- Documentation/git-blame.txt | 9 ++++++++- builtin-blame.c | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index 0ee887d73c..5c9888d014 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [--incremental] [-L n,m] [-S ] - [-M] [-C] [-C] [--since=] [] [--] + [-M] [-C] [-C] [--since=] [ | --contents ] [--] DESCRIPTION ----------- @@ -67,6 +67,13 @@ OPTIONS Show the result incrementally in a format designed for machine consumption. +--contents :: + When is not specified, the command annotates the + changes starting backwards from the working tree copy. + This flag makes the command pretend as if the working + tree copy has the contents of he named file (specify + `-` to make the command read from the standard input). + -M:: Detect moving lines in the file as well. When a commit moves a block of lines in a file (e.g. the original file diff --git a/builtin-blame.c b/builtin-blame.c index fb30c49280..a18ef81a14 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -18,7 +18,7 @@ #include "cache-tree.h" static char blame_usage[] = -"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [--contents ] [commit] [--] file\n" +"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [--contents ] [--incremental] [commit] [--] file\n" " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" " -b Show blank SHA-1 for boundary commits (Default: off)\n" " -l, --long Show long commit SHA1 (Default: off)\n" From b6f5da1e0f4eeb59798b320f97d27f83d19f89df Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 1 Feb 2007 23:30:03 -0800 Subject: [PATCH 421/548] Teach git-remote add to fetch and track This adds three options to 'git-remote add'. * -f (or --fetch) option tells it to also run the initial "git fetch" using the newly created remote shorthand. * -t (or --track) option tells it not to use the default wildcard to track all branches. * -m (or --master) option tells it to make the remote/$name/HEAD point at a remote tracking branch other than master. For example, with this I can say: $ git remote add -f -t master -t quick-start -m master \ jbf-um git://linux-nfs.org/~bfields/git.git/ to (1) create remote.jbf-um.url; (2) track master and quick-start branches (and no other); the two -t options create these two lines: fetch = +refs/heads/master:refs/remotes/jbf-um/master fetch = +refs/heads/quick-start:refs/remotes/jbf-um/quick-start (3) set up remotes/jbf-um/HEAD to point at jbf-um/master so that later I can say "git log jbf-um" Or I could do $ git remote add -t 'ap/*' andy /home/andy/git.git to make Andy's topic branches kept track of under refs/remotes/andy/ap/. Other possible improvements I considered but haven't implemented (hint, hint) are: * reject wildcard letters other than a trailing '*' to the -t parameter; * make -m optional and when the first -t parameter does not have the trailing '*' default to that value (so the above example does not need to say "-m master"); * if -m is not given, and -t parameter ends with '*' (i.e. the above defaulting did not tell us where to point HEAD at), and if we did the fetch with -f, check if 'master' was fetched and make HEAD point at it. Signed-off-by: Junio C Hamano --- git-remote.perl | 56 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/git-remote.perl b/git-remote.perl index f16ff21b8b..c56c5a84a4 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -253,14 +253,30 @@ sub show_remote { } sub add_remote { - my ($name, $url) = @_; + my ($name, $url, $opts) = @_; if (exists $remote->{$name}) { print STDERR "remote $name already exists.\n"; exit(1); } $git->command('config', "remote.$name.url", $url); - $git->command('config', "remote.$name.fetch", - "+refs/heads/*:refs/remotes/$name/*"); + my $track = $opts->{'track'} || ["*"]; + + for (@$track) { + $git->command('config', '--add', "remote.$name.fetch", + "+refs/heads/$_:refs/remotes/$name/$_"); + } + if ($opts->{'fetch'}) { + $git->command('fetch', $name); + } + if (exists $opts->{'master'}) { + $git->command('symbolic-ref', "refs/remotes/$name/HEAD", + "refs/remotes/$name/$opts->{'master'}"); + } +} + +sub add_usage { + print STDERR "Usage: git remote add [-f] [-t track]* [-m master] \n"; + exit(1); } if (!@ARGV) { @@ -307,11 +323,37 @@ elsif ($ARGV[0] eq 'prune') { } } elsif ($ARGV[0] eq 'add') { - if (@ARGV != 3) { - print STDERR "Usage: git remote add \n"; - exit(1); + my %opts = (); + while (1 < @ARGV && $ARGV[1] =~ /^-/) { + my $opt = $ARGV[1]; + shift @ARGV; + if ($opt eq '-f' || $opt eq '--fetch') { + $opts{'fetch'} = 1; + next; + } + if ($opt eq '-t' || $opt eq '--track') { + if (@ARGV < 1) { + add_usage(); + } + $opts{'track'} ||= []; + push @{$opts{'track'}}, $ARGV[1]; + shift @ARGV; + next; + } + if ($opt eq '-m' || $opt eq '--master') { + if ((@ARGV < 1) || exists $opts{'master'}) { + add_usage(); + } + $opts{'master'} = $ARGV[1]; + shift @ARGV; + next; + } + add_usage(); } - add_remote($ARGV[1], $ARGV[2]); + if (@ARGV != 3) { + add_usage(); + } + add_remote($ARGV[1], $ARGV[2], \%opts); } else { print STDERR "Usage: git remote\n"; From 35ce862279f68a4798889adcdd90a1698a2c102f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 24 Jan 2007 11:21:10 -0800 Subject: [PATCH 422/548] pager: Work around window resizing bug in 'less' If you resize the terminal while less is waiting for input, less will exit entirely without even showing the output. This is very noticeable if you do something like "git diff" on a big and cold-cache tree and git takes a few seconds to think, and then you resize the window while it's preparing. Boom. No output AT ALL. The way to reproduce the problem is to do some pager operation that takes a while in git, and resizing the window while git is thinking about the output. Try git diff --stat v2.6.12.. in the kernel tree to do something where it takes a while for git to start outputting information. Signed-off-by: Junio C Hamano --- pager.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pager.c b/pager.c index 4587fbbdb5..5f280ab527 100644 --- a/pager.c +++ b/pager.c @@ -1,5 +1,7 @@ #include "cache.h" +#include + /* * This is split up from the rest of git so that we might do * something different on Windows, for example. @@ -7,6 +9,16 @@ static void run_pager(const char *pager) { + /* + * Work around bug in "less" by not starting it until we + * have real input + */ + fd_set in; + + FD_ZERO(&in); + FD_SET(0, &in); + select(1, &in, NULL, &in, NULL); + execlp(pager, pager, NULL); execl("/bin/sh", "sh", "-c", pager, NULL); } From 8188e73b17c570517ac3bac742810f1b2e5c420d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 5 Feb 2007 16:53:12 -0800 Subject: [PATCH 423/548] Fix longstanding mismerge of ALL_CFLAGS vs BASIC_CFLAGS The earlier commit d7b6c3c0 (Aug 15, 2006) introduced this mismerge when most of the CFLAGS were renamed to BASIC_CFLAGS. Not that it matters right now, since we do not compile XS Perl extensions which wanted non GNU subset of ALL_CFLAGS for compilation, but we should make things consistent. Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 91bd665725..fc98b27c59 100644 --- a/Makefile +++ b/Makefile @@ -504,7 +504,7 @@ ifdef NO_D_INO_IN_DIRENT BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT endif ifdef NO_C99_FORMAT - ALL_CFLAGS += -DNO_C99_FORMAT + BASIC_CFLAGS += -DNO_C99_FORMAT endif ifdef NO_SYMLINK_HEAD BASIC_CFLAGS += -DNO_SYMLINK_HEAD From 32364b3a191041f330bbe334de934458bbae9f10 Mon Sep 17 00:00:00 2001 From: Mark Levedahl Date: Thu, 1 Feb 2007 08:44:46 -0500 Subject: [PATCH 424/548] gitk - remove trailing whitespace from a few lines. Signed-off-by: Mark Levedahl --- gitk | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gitk b/gitk index 31d0aad450..650435e7d2 100755 --- a/gitk +++ b/gitk @@ -427,7 +427,7 @@ proc makewindow {} { .bar.view add separator .bar.view add radiobutton -label "All files" -command {showview 0} \ -variable selectedview -value 0 - + menu .bar.help .bar add cascade -label "Help" -menu .bar.help .bar.help add command -label "About gitk" -command about @@ -1402,7 +1402,7 @@ proc newview {ishighlight} { set newviewname($nextviewnum) "View $nextviewnum" set newviewperm($nextviewnum) 0 set newviewargs($nextviewnum) [shellarglist $revtreeargs] - vieweditor $top $nextviewnum "Gitk view definition" + vieweditor $top $nextviewnum "Gitk view definition" } proc editview {} { @@ -3897,7 +3897,7 @@ proc selectline {l isnew} { } $ctext insert end "\n" } - + set headers {} set olds [lindex $parentlist $l] if {[llength $olds] > 1} { @@ -4006,7 +4006,7 @@ proc selnextpage {dir} { set l [expr $numcommits - 1] } unmarkmatches - selectline $l 1 + selectline $l 1 } proc unselectline {} { From 3468e71f452701b3eff6a2aeb826bbe0cdad8270 Mon Sep 17 00:00:00 2001 From: Mark Levedahl Date: Thu, 1 Feb 2007 08:46:38 -0500 Subject: [PATCH 425/548] Make gitk work reasonably well on Cygwin. The gitk gui layout was completely broken on Cygwin. If gitk was started without previous geometry in ~/.gitk, the user could drag the window sashes to get a useable layout. However, if ~/.gitk existed, this was not possible at all. The fix was to rewrite makewindow, changing the toplevel containers and the particular geometry information saved between sessions. Numerous bugs in both the Cygwin and the Linux Tk versions make this a delicate balancing act: the version here works in both but many subtle variants are competely broken in one or the other environment. Three user visible changes result: 1 - The viewer is fully functional under Cygwin. 2 - The search bar moves from the bottom to the top of the lower left pane. This was necessary to get around a layout problem on Cygwin. 3 - The window size and position is saved and restored between sessions. Again, this is necessary to get around a layout problem on Cygwin. Signed-off-by: Mark Levedahl --- gitk | 272 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 148 insertions(+), 124 deletions(-) diff --git a/gitk b/gitk index 650435e7d2..8132812b50 100755 --- a/gitk +++ b/gitk @@ -435,56 +435,59 @@ proc makewindow {} { .bar.help configure -font $uifont . configure -menu .bar - if {![info exists geometry(canv1)]} { - set geometry(canv1) [expr {45 * $charspc}] - set geometry(canv2) [expr {30 * $charspc}] - set geometry(canv3) [expr {15 * $charspc}] - set geometry(canvh) [expr {25 * $linespc + 4}] - set geometry(ctextw) 80 - set geometry(ctexth) 30 - set geometry(cflistw) 30 - } + # the gui has upper and lower half, parts of a paned window. panedwindow .ctop -orient vertical - if {[info exists geometry(width)]} { - .ctop conf -width $geometry(width) -height $geometry(height) - set texth [expr {$geometry(height) - $geometry(canvh) - 56}] - set geometry(ctexth) [expr {($texth - 8) / - [font metrics $textfont -linespace]}] + + # possibly use assumed geometry + if {![info exists geometry(topheight)]} { + set geometry(topheight) [expr {15 * $linespc}] + set geometry(topwidth) [expr {80 * $charspc}] + set geometry(botheight) [expr {15 * $linespc}] + set geometry(botwidth) [expr {50 * $charspc}] + set geometry(canv) [expr {40 * $charspc}] + set geometry(canv2) [expr {20 * $charspc}] + set geometry(canv3) [expr {20 * $charspc}] } - frame .ctop.top - frame .ctop.top.bar - frame .ctop.top.lbar - pack .ctop.top.lbar -side bottom -fill x - pack .ctop.top.bar -side bottom -fill x - set cscroll .ctop.top.csb - scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0 - pack $cscroll -side right -fill y - panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4 - pack .ctop.top.clist -side top -fill both -expand 1 - .ctop add .ctop.top - set canv .ctop.top.clist.canv - canvas $canv -height $geometry(canvh) -width $geometry(canv1) \ + + # the upper half will have a paned window, a scroll bar to the right, and some stuff below + frame .tf -height $geometry(topheight) -width $geometry(topwidth) + frame .tf.histframe + panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4 + + # create three canvases + set cscroll .tf.histframe.csb + set canv .tf.histframe.pwclist.canv + canvas $canv -width $geometry(canv) \ -background $bgcolor -bd 0 \ -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll" - .ctop.top.clist add $canv - set canv2 .ctop.top.clist.canv2 - canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \ + .tf.histframe.pwclist add $canv + set canv2 .tf.histframe.pwclist.canv2 + canvas $canv2 -width $geometry(canv2) \ -background $bgcolor -bd 0 -yscrollincr $linespc - .ctop.top.clist add $canv2 - set canv3 .ctop.top.clist.canv3 - canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \ + .tf.histframe.pwclist add $canv2 + set canv3 .tf.histframe.pwclist.canv3 + canvas $canv3 -width $geometry(canv3) \ -background $bgcolor -bd 0 -yscrollincr $linespc - .ctop.top.clist add $canv3 - bind .ctop.top.clist {resizeclistpanes %W %w} - lappend bglist $canv $canv2 $canv3 + .tf.histframe.pwclist add $canv3 - set sha1entry .ctop.top.bar.sha1 + # a scroll bar to rule them + scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0 + pack $cscroll -side right -fill y + bind .tf.histframe.pwclist {resizeclistpanes %W %w} + lappend bglist $canv $canv2 $canv3 + pack .tf.histframe.pwclist -fill both -expand 1 -side left + + # we have two button bars at bottom of top frame. Bar 1 + frame .tf.bar + frame .tf.lbar -height 15 + + set sha1entry .tf.bar.sha1 set entries $sha1entry - set sha1but .ctop.top.bar.sha1label + set sha1but .tf.bar.sha1label button $sha1but -text "SHA1 ID: " -state disabled -relief flat \ -command gotocommit -width 8 -font $uifont $sha1but conf -disabledforeground [$sha1but cget -foreground] - pack .ctop.top.bar.sha1label -side left + pack .tf.bar.sha1label -side left entry $sha1entry -width 40 -font $textfont -textvariable sha1string trace add variable sha1string write sha1change pack $sha1entry -side left -pady 2 @@ -505,91 +508,105 @@ proc makewindow {} { 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c, 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01}; } - button .ctop.top.bar.leftbut -image bm-left -command goback \ + button .tf.bar.leftbut -image bm-left -command goback \ -state disabled -width 26 - pack .ctop.top.bar.leftbut -side left -fill y - button .ctop.top.bar.rightbut -image bm-right -command goforw \ + pack .tf.bar.leftbut -side left -fill y + button .tf.bar.rightbut -image bm-right -command goforw \ -state disabled -width 26 - pack .ctop.top.bar.rightbut -side left -fill y + pack .tf.bar.rightbut -side left -fill y - button .ctop.top.bar.findbut -text "Find" -command dofind -font $uifont - pack .ctop.top.bar.findbut -side left + button .tf.bar.findbut -text "Find" -command dofind -font $uifont + pack .tf.bar.findbut -side left set findstring {} - set fstring .ctop.top.bar.findstring + set fstring .tf.bar.findstring lappend entries $fstring entry $fstring -width 30 -font $textfont -textvariable findstring trace add variable findstring write find_change - pack $fstring -side left -expand 1 -fill x + pack $fstring -side left -expand 1 -fill x -in .tf.bar set findtype Exact - set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \ - findtype Exact IgnCase Regexp] + set findtypemenu [tk_optionMenu .tf.bar.findtype \ + findtype Exact IgnCase Regexp] trace add variable findtype write find_change - .ctop.top.bar.findtype configure -font $uifont - .ctop.top.bar.findtype.menu configure -font $uifont + .tf.bar.findtype configure -font $uifont + .tf.bar.findtype.menu configure -font $uifont set findloc "All fields" - tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \ + tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \ Comments Author Committer trace add variable findloc write find_change - .ctop.top.bar.findloc configure -font $uifont - .ctop.top.bar.findloc.menu configure -font $uifont - pack .ctop.top.bar.findloc -side right - pack .ctop.top.bar.findtype -side right + .tf.bar.findloc configure -font $uifont + .tf.bar.findloc.menu configure -font $uifont + pack .tf.bar.findloc -side right + pack .tf.bar.findtype -side right - label .ctop.top.lbar.flabel -text "Highlight: Commits " \ - -font $uifont - pack .ctop.top.lbar.flabel -side left -fill y + # build up the bottom bar of upper window + label .tf.lbar.flabel -text "Highlight: Commits " \ + -font $uifont + pack .tf.lbar.flabel -side left -fill y set gdttype "touching paths:" - set gm [tk_optionMenu .ctop.top.lbar.gdttype gdttype "touching paths:" \ - "adding/removing string:"] + set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \ + "adding/removing string:"] trace add variable gdttype write hfiles_change $gm conf -font $uifont - .ctop.top.lbar.gdttype conf -font $uifont - pack .ctop.top.lbar.gdttype -side left -fill y - entry .ctop.top.lbar.fent -width 25 -font $textfont \ + .tf.lbar.gdttype conf -font $uifont + pack .tf.lbar.gdttype -side left -fill y + entry .tf.lbar.fent -width 25 -font $textfont \ -textvariable highlight_files trace add variable highlight_files write hfiles_change - lappend entries .ctop.top.lbar.fent - pack .ctop.top.lbar.fent -side left -fill x -expand 1 - label .ctop.top.lbar.vlabel -text " OR in view" -font $uifont - pack .ctop.top.lbar.vlabel -side left -fill y + lappend entries .tf.lbar.fent + pack .tf.lbar.fent -side left -fill x -expand 1 + label .tf.lbar.vlabel -text " OR in view" -font $uifont + pack .tf.lbar.vlabel -side left -fill y global viewhlmenu selectedhlview - set viewhlmenu [tk_optionMenu .ctop.top.lbar.vhl selectedhlview None] + set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None] $viewhlmenu entryconf None -command delvhighlight $viewhlmenu conf -font $uifont - .ctop.top.lbar.vhl conf -font $uifont - pack .ctop.top.lbar.vhl -side left -fill y - label .ctop.top.lbar.rlabel -text " OR " -font $uifont - pack .ctop.top.lbar.rlabel -side left -fill y + .tf.lbar.vhl conf -font $uifont + pack .tf.lbar.vhl -side left -fill y + label .tf.lbar.rlabel -text " OR " -font $uifont + pack .tf.lbar.rlabel -side left -fill y global highlight_related - set m [tk_optionMenu .ctop.top.lbar.relm highlight_related None \ - "Descendent" "Not descendent" "Ancestor" "Not ancestor"] + set m [tk_optionMenu .tf.lbar.relm highlight_related None \ + "Descendent" "Not descendent" "Ancestor" "Not ancestor"] $m conf -font $uifont - .ctop.top.lbar.relm conf -font $uifont + .tf.lbar.relm conf -font $uifont trace add variable highlight_related write vrel_change - pack .ctop.top.lbar.relm -side left -fill y + pack .tf.lbar.relm -side left -fill y - panedwindow .ctop.cdet -orient horizontal - .ctop add .ctop.cdet - frame .ctop.cdet.left - frame .ctop.cdet.left.bot - pack .ctop.cdet.left.bot -side bottom -fill x - button .ctop.cdet.left.bot.search -text "Search" -command dosearch \ + # Finish putting the upper half of the viewer together + pack .tf.lbar -in .tf -side bottom -fill x + pack .tf.bar -in .tf -side bottom -fill x + pack .tf.histframe -fill both -side top -expand 1 + .ctop add .tf + + # now build up the bottom + panedwindow .pwbottom -orient horizontal + + # lower left, a text box over search bar, scroll bar to the right + # if we know window height, then that will set the lower text height, otherwise + # we set lower text height which will drive window height + if {[info exists geometry(main)]} { + frame .bleft -width $geometry(botwidth) + } else { + frame .bleft -width $geometry(botwidth) -height $geometry(botheight) + } + frame .bleft.top + + button .bleft.top.search -text "Search" -command dosearch \ -font $uifont - pack .ctop.cdet.left.bot.search -side left -padx 5 - set sstring .ctop.cdet.left.bot.sstring + pack .bleft.top.search -side left -padx 5 + set sstring .bleft.top.sstring entry $sstring -width 20 -font $textfont -textvariable searchstring lappend entries $sstring trace add variable searchstring write incrsearch pack $sstring -side left -expand 1 -fill x - set ctext .ctop.cdet.left.ctext + set ctext .bleft.ctext text $ctext -background $bgcolor -foreground $fgcolor \ -state disabled -font $textfont \ - -width $geometry(ctextw) -height $geometry(ctexth) \ -yscrollcommand scrolltext -wrap none - scrollbar .ctop.cdet.left.sb -command "$ctext yview" - pack .ctop.cdet.left.sb -side right -fill y + scrollbar .bleft.sb -command "$ctext yview" + pack .bleft.top -side top -fill x + pack .bleft.sb -side right -fill y pack $ctext -side left -fill both -expand 1 - .ctop.cdet add .ctop.cdet.left lappend bglist $ctext lappend fglist $ctext @@ -620,36 +637,45 @@ proc makewindow {} { $ctext tag conf msep -font [concat $textfont bold] $ctext tag conf found -back yellow - frame .ctop.cdet.right - frame .ctop.cdet.right.mode - radiobutton .ctop.cdet.right.mode.patch -text "Patch" \ + .pwbottom add .bleft + + # lower right + frame .bright + frame .bright.mode + radiobutton .bright.mode.patch -text "Patch" \ -command reselectline -variable cmitmode -value "patch" - radiobutton .ctop.cdet.right.mode.tree -text "Tree" \ + radiobutton .bright.mode.tree -text "Tree" \ -command reselectline -variable cmitmode -value "tree" - grid .ctop.cdet.right.mode.patch .ctop.cdet.right.mode.tree -sticky ew - pack .ctop.cdet.right.mode -side top -fill x - set cflist .ctop.cdet.right.cfiles + grid .bright.mode.patch .bright.mode.tree -sticky ew + pack .bright.mode -side top -fill x + set cflist .bright.cfiles set indent [font measure $mainfont "nn"] - text $cflist -width $geometry(cflistw) \ + text $cflist \ -background $bgcolor -foreground $fgcolor \ -font $mainfont \ -tabs [list $indent [expr {2 * $indent}]] \ - -yscrollcommand ".ctop.cdet.right.sb set" \ + -yscrollcommand ".bright.sb set" \ -cursor [. cget -cursor] \ -spacing1 1 -spacing3 1 lappend bglist $cflist lappend fglist $cflist - scrollbar .ctop.cdet.right.sb -command "$cflist yview" - pack .ctop.cdet.right.sb -side right -fill y + scrollbar .bright.sb -command "$cflist yview" + pack .bright.sb -side right -fill y pack $cflist -side left -fill both -expand 1 $cflist tag configure highlight \ -background [$cflist cget -selectbackground] $cflist tag configure bold -font [concat $mainfont bold] - .ctop.cdet add .ctop.cdet.right - bind .ctop.cdet {resizecdetpanes %W %w} - pack .ctop -side top -fill both -expand 1 + .pwbottom add .bright + .ctop add .pwbottom + # restore window position if known + if {[info exists geometry(main)]} { + wm geometry . "$geometry(main)" + } + + bind .pwbottom {resizecdetpanes %W %w} + pack .ctop -fill both -expand 1 bindall <1> {selcanvline %W %x %y} #bindall {selcanvline %W %x %y} bindall "allcanvs yview scroll -5 units" @@ -802,18 +828,16 @@ proc savestuff {w} { puts $f [list set fgcolor $fgcolor] puts $f [list set colors $colors] puts $f [list set diffcolors $diffcolors] - puts $f "set geometry(width) [winfo width .ctop]" - puts $f "set geometry(height) [winfo height .ctop]" - puts $f "set geometry(canv1) [expr {[winfo width $canv]-2}]" - puts $f "set geometry(canv2) [expr {[winfo width $canv2]-2}]" - puts $f "set geometry(canv3) [expr {[winfo width $canv3]-2}]" - puts $f "set geometry(canvh) [expr {[winfo height $canv]-2}]" - set wid [expr {([winfo width $ctext] - 8) \ - / [font measure $textfont "0"]}] - puts $f "set geometry(ctextw) $wid" - set wid [expr {([winfo width $cflist] - 11) \ - / [font measure [$cflist cget -font] "0"]}] - puts $f "set geometry(cflistw) $wid" + + puts $f "set geometry(main) [winfo geometry .]" + puts $f "set geometry(topwidth) [winfo width .tf]" + puts $f "set geometry(topheight) [winfo height .tf]" + puts $f "set geometry(canv) [expr {[winfo width $canv]-0}]" + puts $f "set geometry(canv2) [expr {[winfo width $canv2]-0}]" + puts $f "set geometry(canv3) [expr {[winfo width $canv3]-0}]" + puts $f "set geometry(botwidth) [winfo width .bleft]" + puts $f "set geometry(botheight) [winfo height .bleft]" + puts -nonewline $f "set permviews {" for {set v 0} {$v < $nextviewnum} {incr v} { if {$viewperm($v)} { @@ -4043,11 +4067,11 @@ proc addtohistory {cmd} { } incr historyindex if {$historyindex > 1} { - .ctop.top.bar.leftbut conf -state normal + .tf.bar.leftbut conf -state normal } else { - .ctop.top.bar.leftbut conf -state disabled + .tf.bar.leftbut conf -state disabled } - .ctop.top.bar.rightbut conf -state disabled + .tf.bar.rightbut conf -state disabled } proc godo {elt} { @@ -4067,10 +4091,10 @@ proc goback {} { if {$historyindex > 1} { incr historyindex -1 godo [lindex $history [expr {$historyindex - 1}]] - .ctop.top.bar.rightbut conf -state normal + .tf.bar.rightbut conf -state normal } if {$historyindex <= 1} { - .ctop.top.bar.leftbut conf -state disabled + .tf.bar.leftbut conf -state disabled } } @@ -4081,10 +4105,10 @@ proc goforw {} { set cmd [lindex $history $historyindex] incr historyindex godo $cmd - .ctop.top.bar.leftbut conf -state normal + .tf.bar.leftbut conf -state normal } if {$historyindex >= [llength $history]} { - .ctop.top.bar.rightbut conf -state disabled + .tf.bar.rightbut conf -state disabled } } @@ -4591,7 +4615,7 @@ proc searchmarkvisible {doall} { proc scrolltext {f0 f1} { global searchstring - .ctop.cdet.left.sb set $f0 $f1 + .bleft.sb set $f0 $f1 if {$searchstring ne {}} { searchmarkvisible 0 } From 0f57a31b4c7f8784b70535681a669b7746f38f1c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 29 Jan 2007 21:53:28 -0800 Subject: [PATCH 426/548] gitk: Use show-ref instead of ls-remote It used to be ls-remote on self was the only easy way to grab the ref information. Now we have show-ref which does not involve fork and IPC, so use it. Signed-off-by: Junio C Hamano --- gitk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gitk b/gitk index 8132812b50..1c36235bff 100755 --- a/gitk +++ b/gitk @@ -309,9 +309,9 @@ proc readrefs {} { foreach v {tagids idtags headids idheads otherrefids idotherrefs} { catch {unset $v} } - set refd [open [list | git ls-remote [gitdir]] r] + set refd [open [list | git show-ref] r] while {0 <= [set n [gets $refd line]]} { - if {![regexp {^([0-9a-f]{40}) refs/([^^]*)$} $line \ + if {![regexp {^([0-9a-f]{40}) refs/([^^]*)$} $line \ match id path]} { continue } From 6c3aac1c69ea0bcb2896bec96a01fdf8aa6176fa Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 20:30:37 -0500 Subject: [PATCH 427/548] Don't support shell-quoted refnames in fast-import. The current implementation of shell-style quoted refnames and SHA-1 expressions within fast-import contains a bad memory leak. We leak the unquoted strings used by the `from` and `merge` commands, maybe others. Its also just muddling up the docs. Since Git refnames cannot contain LF, and that is our delimiter for the end of the refname, and we accept any other character as-is, there is no reason for these strings to support quoting, except to be nice to frontends. But frontends shouldn't be expecting to use funny refs in Git, and its just as simple to never quote them as it is to always pass them through the same quoting filter as pathnames. So frontends should never quote refs, or ref expressions. Signed-off-by: Shawn O. Pearce --- fast-import.c | 67 ++++----------------------------------------------- 1 file changed, 5 insertions(+), 62 deletions(-) diff --git a/fast-import.c b/fast-import.c index 9658c28413..e6342386fc 100644 --- a/fast-import.c +++ b/fast-import.c @@ -75,9 +75,9 @@ Format of STDIN stream: # stream formatting is: \, " and LF. Otherwise these values # are UTF8. # - ref_str ::= ref | '"' quoted(ref) '"' ; - sha1exp_str ::= sha1exp | '"' quoted(sha1exp) '"' ; - tag_str ::= tag | '"' quoted(tag) '"' ; + ref_str ::= ref; + sha1exp_str ::= sha1exp; + tag_str ::= tag; path_str ::= path | '"' quoted(path) '"' ; mode ::= '100644' | '644' | '100755' | '755' @@ -1546,8 +1546,7 @@ static void file_change_d(struct branch *b) static void cmd_from(struct branch *b) { - const char *from, *endp; - char *str_uq; + const char *from; struct branch *s; if (strncmp("from ", command_buf.buf, 5)) @@ -1557,13 +1556,6 @@ static void cmd_from(struct branch *b) die("Can't reinitailize branch %s", b->name); from = strchr(command_buf.buf, ' ') + 1; - str_uq = unquote_c_style(from, &endp); - if (str_uq) { - if (*endp) - die("Garbage after string in: %s", command_buf.buf); - from = str_uq; - } - s = lookup_branch(from); if (b == s) die("Can't create a branch from itself: %s", b->name); @@ -1617,20 +1609,12 @@ static void cmd_from(struct branch *b) static struct hash_list* cmd_merge(unsigned int *count) { struct hash_list *list = NULL, *n, *e; - const char *from, *endp; - char *str_uq; + const char *from; struct branch *s; *count = 0; while (!strncmp("merge ", command_buf.buf, 6)) { from = strchr(command_buf.buf, ' ') + 1; - str_uq = unquote_c_style(from, &endp); - if (str_uq) { - if (*endp) - die("Garbage after string in: %s", command_buf.buf); - from = str_uq; - } - n = xmalloc(sizeof(*n)); s = lookup_branch(from); if (s) @@ -1661,8 +1645,6 @@ static void cmd_new_commit(void) struct branch *b; void *msg; size_t msglen; - char *str_uq; - const char *endp; char *sp; char *author = NULL; char *committer = NULL; @@ -1671,17 +1653,9 @@ static void cmd_new_commit(void) /* Obtain the branch name from the rest of our command */ sp = strchr(command_buf.buf, ' ') + 1; - str_uq = unquote_c_style(sp, &endp); - if (str_uq) { - if (*endp) - die("Garbage after ref in: %s", command_buf.buf); - sp = str_uq; - } b = lookup_branch(sp); if (!b) b = new_branch(sp); - if (str_uq) - free(str_uq); read_next_command(); cmd_mark(); @@ -1772,8 +1746,6 @@ static void cmd_new_commit(void) static void cmd_new_tag(void) { - char *str_uq; - const char *endp; char *sp; const char *from; char *tagger; @@ -1786,12 +1758,6 @@ static void cmd_new_tag(void) /* Obtain the new tag name from the rest of our command */ sp = strchr(command_buf.buf, ' ') + 1; - str_uq = unquote_c_style(sp, &endp); - if (str_uq) { - if (*endp) - die("Garbage after tag name in: %s", command_buf.buf); - sp = str_uq; - } t = pool_alloc(sizeof(struct tag)); t->next_tag = NULL; t->name = pool_strdup(sp); @@ -1800,22 +1766,12 @@ static void cmd_new_tag(void) else first_tag = t; last_tag = t; - if (str_uq) - free(str_uq); read_next_command(); /* from ... */ if (strncmp("from ", command_buf.buf, 5)) die("Expected from command, got %s", command_buf.buf); - from = strchr(command_buf.buf, ' ') + 1; - str_uq = unquote_c_style(from, &endp); - if (str_uq) { - if (*endp) - die("Garbage after string in: %s", command_buf.buf); - from = str_uq; - } - s = lookup_branch(from); if (s) { hashcpy(sha1, s->sha1); @@ -1836,9 +1792,6 @@ static void cmd_new_tag(void) free(buf); } else die("Invalid ref name or SHA1 expression: %s", from); - - if (str_uq) - free(str_uq); read_next_command(); /* tagger ... */ @@ -1885,18 +1838,10 @@ static void cmd_new_tag(void) static void cmd_reset_branch(void) { struct branch *b; - char *str_uq; - const char *endp; char *sp; /* Obtain the branch name from the rest of our command */ sp = strchr(command_buf.buf, ' ') + 1; - str_uq = unquote_c_style(sp, &endp); - if (str_uq) { - if (*endp) - die("Garbage after ref in: %s", command_buf.buf); - sp = str_uq; - } b = lookup_branch(sp); if (b) { b->last_commit = 0; @@ -1907,8 +1852,6 @@ static void cmd_reset_branch(void) } else b = new_branch(sp); - if (str_uq) - free(str_uq); read_next_command(); cmd_from(b); } From 6e411d2044072072692f2d9cf9d633421ef6017a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 21:09:25 -0500 Subject: [PATCH 428/548] Initial draft of fast-import documentation. This is a first pass at the manpage for git-fast-import. I have tried to cover the input format in extreme detail, creating a reference which is more detailed than the BNF grammar appearing in the header of fast-import.c. I have also covered some details about gfi's performance and memory utilization, as well as the average learning curve required to create a gfi frontend application (as it is far lower than it might appear on first glance). The documentation still lacks real example input streams, which may turn out to be difficult to format in asciidoc due to the blank lines which carry meaning within the format. Signed-off-by: Shawn O. Pearce --- Documentation/git-fast-import.txt | 655 ++++++++++++++++++++++++++++++ 1 file changed, 655 insertions(+) create mode 100644 Documentation/git-fast-import.txt diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt new file mode 100644 index 0000000000..16308731fb --- /dev/null +++ b/Documentation/git-fast-import.txt @@ -0,0 +1,655 @@ +git-fast-import(1) +================== + +NAME +---- +git-fast-import - Backend for fast Git data importers. + + +SYNOPSIS +-------- +frontend | 'git-fast-import' [options] + +DESCRIPTION +----------- +This program is usually not what the end user wants to run directly. +Most end users want to use one of the existing frontend programs, +which parses a specific type of foreign source and feeds the contents +stored there to git-fast-import (gfi). + +gfi reads a mixed command/data stream from standard input and +writes one or more packfiles directly into the current repository. +When EOF is received on standard input, fast import writes out +updated branch and tag refs, fully updating the current repository +with the newly imported data. + +The gfi backend itself can import into an empty repository (one that +has already been initialized by gitlink:git-init[1]) or incrementally +update an existing populated repository. Whether or not incremental +imports are supported from a particular foreign source depends on +the frontend program in use. + + +OPTIONS +------- +--max-pack-size=:: + Maximum size of each output packfile, expressed in MiB. + The default is 4096 (4 GiB) as that is the maximum allowed + packfile size (due to file format limitations). Some + importers may wish to lower this, such as to ensure the + resulting packfiles fit on CDs. + +--depth=:: + Maximum delta depth, for blob and tree deltification. + Default is 10. + +--active-branches=:: + Maximum number of branches to maintain active at once. + See ``Memory Utilization'' below for details. Default is 5. + +--export-marks=:: + Dumps the internal marks table to when complete. + Marks are written one per line as `:markid SHA-1`. + Frontends can use this file to validate imports after they + have been completed. + +--branch-log=:: + Records every tag and commit made to a log file. (This file + can be quite verbose on large imports.) This particular + option has been primarily intended to facilitate debugging + gfi and has limited usefulness in other contexts. It may + be removed in future versions. + + +Performance +----------- +The design of gfi allows it to import large projects in a minimum +amount of memory usage and processing time. Assuming the frontend +is able to keep up with gfi and feed it a constant stream of data, +import times for projects holding 10+ years of history and containing +100,000+ individual commits are generally completed in just 1-2 +hours on quite modest (~$2,000 USD) hardware. + +Most bottlenecks appear to be in foreign source data access (the +source just cannot extract revisions fast enough) or disk IO (gfi +writes as fast as the disk will take the data). Imports will run +faster if the source data is stored on a different drive than the +destination Git repository (due to less IO contention). + + +Development Cost +---------------- +A typical frontend for gfi tends to weigh in at approximately 200 +lines of Perl/Python/Ruby code. Most developers have been able to +create working importers in just a couple of hours, even though it +is their first exposure to gfi, and sometimes even to Git. This is +an ideal situation, given that most conversion tools are throw-away +(use once, and never look back). + + +Parallel Operation +------------------ +Like `git-push` or `git-fetch`, imports handled by gfi are safe to +run alongside parallel `git repack -a -d` or `git gc` invocations, +or any other Git operation (including `git prune`, as loose objects +are never used by gfi). + +However, gfi does not lock the branch or tag refs it is actively +importing. After EOF, during its ref update phase, gfi blindly +overwrites each imported branch or tag ref. Consequently it is not +safe to modify refs that are currently being used by a running gfi +instance, as work could be lost when gfi overwrites the refs. + + +Technical Discussion +-------------------- +gfi tracks a set of branches in memory. Any branch can be created +or modified at any point during the import process by sending a +`commit` command on the input stream. This design allows a frontend +program to process an unlimited number of branches simultaneously, +generating commits in the order they are available from the source +data. It also simplifies the frontend programs considerably. + +gfi does not use or alter the current working directory, or any +file within it. (It does however update the current Git repository, +as referenced by `GIT_DIR`.) Therefore an import frontend may use +the working directory for its own purposes, such as extracting file +revisions from the foreign source. This ignorance of the working +directory also allows gfi to run very quickly, as it does not +need to perform any costly file update operations when switching +between branches. + +Input Format +------------ +With the exception of raw file data (which Git does not interpret) +the gfi input format is text (ASCII) based. This text based +format simplifies development and debugging of frontend programs, +especially when a higher level language such as Perl, Python or +Ruby is being used. + +gfi is very strict about its input. Where we say SP below we mean +*exactly* one space. Likewise LF means one (and only one) linefeed. +Supplying additional whitespace characters will cause unexpected +results, such as branch names or file names with leading or trailing +spaces in their name, or early termination of gfi when it encounters +unexpected input. + +Commands +~~~~~~~~ +gfi accepts several commands to update the current repository +and control the current import process. More detailed discussion +(with examples) of each command follows later. + +`commit`:: + Creates a new branch or updates an existing branch by + creating a new commit and updating the branch to point at + the newly created commit. + +`tag`:: + Creates an annotated tag object from an existing commit or + branch. Lightweight tags are not supported by this command, + as they are not recommended for recording meaningful points + in time. + +`reset`:: + Reset an existing branch (or a new branch) to a specific + revision. This command must be used to change a branch to + a specific revision without making a commit on it. + +`blob`:: + Convert raw file data into a blob, for future use in a + `commit` command. This command is optional and is not + needed to perform an import. + +`checkpoint`:: + Forces gfi to close the current packfile, generate its + unique SHA-1 checksum and index, and start a new packfile. + This command is optional and is not needed to perform + an import. + +`commit` +~~~~~~~~ +Create or update a branch with a new commit, recording one logical +change to the project. + +.... + 'commit' SP LF + mark? + ('author' SP SP LT GT SP