From 354af6bd69b442d0ce9449961f37d95ef12ba988 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Sun, 23 Nov 2008 13:14:23 +1100 Subject: [PATCH 001/884] gitk: Restore scrolling position of diff pane on back/forward in history This arranges to save the scrolling position of the diff display pane when we move from displaying one thing to another, and then scroll the pane to the same position when we go back to the previous thing using the back or forward buttons. This works if we have clicked on a commit and are in patch display mode, or if we have clicked on a line or a tag, or have done a diff between two commits with the context menu. It doesn't currently restore the pane to where it was if is was displaying a commit in tree display mode. For future extensibility, addtohistory now takes an extra optional argument which is a script to invoke when moving from this thing to another. The script needs to return a list of pairs of variable name and value. If we go back to this thing, the godo procedure will set the named variables to the values given. At present that is just used to store the $ctext scrolling position, but in future we will use it to store the state of which directories are open in the file list pane. Signed-off-by: Paul Mackerras --- gitk | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 9 deletions(-) diff --git a/gitk b/gitk index 9bdaafe408..08a45e1b94 100755 --- a/gitk +++ b/gitk @@ -6618,7 +6618,7 @@ proc selectline {l isnew {desired_loc {}}} { make_secsel $id if {$isnew} { - addtohistory [list selbyid $id] + addtohistory [list selbyid $id 0] savecmitpos } $sha1entry delete 0 end @@ -6770,10 +6770,12 @@ proc reselectline {} { } } -proc addtohistory {cmd} { +proc addtohistory {cmd {saveproc {}}} { global history historyindex curview - set elt [list $curview $cmd] + unset_posvars + save_position + set elt [list $curview $cmd $saveproc {}] if {$historyindex > 0 && [lindex $history [expr {$historyindex - 1}]] == $elt} { return @@ -6793,14 +6795,45 @@ proc addtohistory {cmd} { .tf.bar.rightbut conf -state disabled } +# save the scrolling position of the diff display pane +proc save_position {} { + global historyindex history + + if {$historyindex < 1} return + set hi [expr {$historyindex - 1}] + set fn [lindex $history $hi 2] + if {$fn ne {}} { + lset history $hi 3 [eval $fn] + } +} + +proc unset_posvars {} { + global last_posvars + + if {[info exists last_posvars]} { + foreach {var val} $last_posvars { + global $var + catch {unset $var} + } + unset last_posvars + } +} + proc godo {elt} { - global curview + global curview last_posvars set view [lindex $elt 0] set cmd [lindex $elt 1] + set pv [lindex $elt 3] if {$curview != $view} { showview $view } + unset_posvars + foreach {var val} $pv { + global $var + set $var $val + } + set last_posvars $pv eval $cmd } @@ -6809,6 +6842,7 @@ proc goback {} { focus . if {$historyindex > 1} { + save_position incr historyindex -1 godo [lindex $history [expr {$historyindex - 1}]] .tf.bar.rightbut conf -state normal @@ -6823,6 +6857,7 @@ proc goforw {} { focus . if {$historyindex < [llength $history]} { + save_position set cmd [lindex $history $historyindex] incr historyindex godo $cmd @@ -7189,6 +7224,34 @@ proc getblobdiffs {ids} { filerun $bdf [list getblobdiffline $bdf $diffids] } +proc savecmitpos {} { + global ctext cmitmode + + if {$cmitmode eq "tree"} { + return {} + } + return [list target_scrollpos [$ctext index @0,0]] +} + +proc savectextpos {} { + global ctext + + return [list target_scrollpos [$ctext index @0,0]] +} + +proc maybe_scroll_ctext {ateof} { + global ctext target_scrollpos + + if {![info exists target_scrollpos]} return + if {!$ateof} { + set nlines [expr {[winfo height $ctext] + / [font metrics textfont -linespace]}] + if {[$ctext compare "$target_scrollpos + $nlines lines" <= end]} return + } + $ctext yview $target_scrollpos + unset target_scrollpos +} + proc setinlist {var i val} { global $var @@ -7383,6 +7446,7 @@ proc getblobdiffline {bdf ids} { if {[info exists seehere]} { mark_ctext_line [lindex [split $seehere .] 0] } + maybe_scroll_ctext [eof $bdf] $ctext conf -state disabled if {[eof $bdf]} { close $bdf @@ -7877,7 +7941,7 @@ proc lineclick {x y id isnew} { } if {$isnew} { - addtohistory [list lineclick $x $y $id 0] + addtohistory [list lineclick $x $y $id 0] savectextpos } # fill the details pane with info about this line $ctext conf -state normal @@ -7908,6 +7972,7 @@ proc lineclick {x y id isnew} { $ctext insert end "\n\t[mc "Date"]:\t$date\n" } } + maybe_scroll_ctext 1 $ctext conf -state disabled init_flist {} } @@ -7921,10 +7986,10 @@ proc normalline {} { } } -proc selbyid {id} { +proc selbyid {id {isnew 1}} { global curview if {[commitinview $id $curview]} { - selectline [rowofcommit $id] 1 + selectline [rowofcommit $id] $isnew } } @@ -7974,7 +8039,7 @@ proc diffvssel {dirn} { set oldid $rowmenuid set newid [commitonrow $selectedline] } - addtohistory [list doseldiff $oldid $newid] + addtohistory [list doseldiff $oldid $newid] savectextpos doseldiff $oldid $newid } @@ -9886,7 +9951,7 @@ proc showtag {tag isnew} { global ctext tagcontents tagids linknum tagobjid if {$isnew} { - addtohistory [list showtag $tag 0] + addtohistory [list showtag $tag 0] savectextpos } $ctext conf -state normal clear_ctext @@ -9903,6 +9968,7 @@ proc showtag {tag isnew} { set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)" } appendwithlinks $text {} + maybe_scroll_ctext $ctext conf -state disabled init_flist {} } From d93f1713b0ba57b119f2ba2e2beda2ed9cc7349d Mon Sep 17 00:00:00 2001 From: Pat Thoyts Date: Fri, 17 Apr 2009 01:24:35 +0100 Subject: [PATCH 002/884] gitk: Use themed tk widgets With Tk 8.5+, this uses the themed widgets to improve the appearance on Windows and MacOSX. On X11 less difference is apparent, but users can select alternate themes by setting *TkTheme in the resource database (eg: *TkTheme: clam). With Tk 8.6 there is a built-in font selection dialog. This will make use of that when available, as on Windows and MacOSX it calls the native font selection dialog. [paulus@samba.org - folded in subsequent patch to restore saved pane sizes for ttk widgets, and trimmed trailing whitespace.] Signed-off-by: Pat Thoyts Signed-off-by: Paul Mackerras --- gitk | 607 ++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 370 insertions(+), 237 deletions(-) diff --git a/gitk b/gitk index ab6fda184b..4526193b3d 100755 --- a/gitk +++ b/gitk @@ -7,6 +7,8 @@ exec wish "$0" -- "$@" # and distributed under the terms of the GNU General Public Licence, # either version 2, or (at your option) any later version. +package require Tk + proc gitdir {} { global env if {[info exists env(GIT_DIR)]} { @@ -264,7 +266,7 @@ proc parseviewrevs {view revs} { } lappend badrev $line } - } + } error_popup "[mc "Error parsing revisions:"] $err" return {} } @@ -1767,6 +1769,15 @@ proc removehead {id name} { unset headids($name) } +proc ttk_toplevel {w args} { + global use_ttk + eval [linsert $args 0 ::toplevel $w] + if {$use_ttk} { + place [ttk::frame $w._toplevel_background] -x 0 -y 0 -relwidth 1 -relheight 1 + } + return $w +} + proc make_transient {window origin} { global have_tk85 @@ -1786,9 +1797,11 @@ proc make_transient {window origin} { } proc show_error {w top msg} { + global NS + if {[wm state $top] eq "withdrawn"} { wm deiconify $top } message $w.m -text $msg -justify center -aspect 400 pack $w.m -side top -fill x -padx 20 -pady 20 - button $w.ok -text [mc OK] -command "destroy $top" + ${NS}::button $w.ok -default active -text [mc OK] -command "destroy $top" pack $w.ok -side bottom -fill x bind $top "grab $top; focus $top" bind $top "destroy $top" @@ -1798,45 +1811,56 @@ proc show_error {w top msg} { } proc error_popup {msg {owner .}} { - set w .error - toplevel $w - make_transient $w $owner - show_error $w $w $msg + if {[tk windowingsystem] eq "win32"} { + tk_messageBox -icon error -type ok -title [wm title .] \ + -parent $owner -message $msg + } else { + set w .error + ttk_toplevel $w + make_transient $w $owner + show_error $w $w $msg + } } proc confirm_popup {msg {owner .}} { - global confirm_ok + global confirm_ok NS set confirm_ok 0 set w .confirm - toplevel $w + ttk_toplevel $w make_transient $w $owner message $w.m -text $msg -justify center -aspect 400 pack $w.m -side top -fill x -padx 20 -pady 20 - button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w" + ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w" pack $w.ok -side left -fill x - button $w.cancel -text [mc Cancel] -command "destroy $w" + ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w" pack $w.cancel -side right -fill x bind $w "grab $w; focus $w" bind $w "set confirm_ok 1; destroy $w" bind $w "set confirm_ok 1; destroy $w" bind $w "destroy $w" + tk::PlaceWindow $w widget $owner tkwait window $w return $confirm_ok } proc setoptions {} { - option add *Panedwindow.showHandle 1 startupFile - option add *Panedwindow.sashRelief raised startupFile + if {[tk windowingsystem] ne "win32"} { + option add *Panedwindow.showHandle 1 startupFile + option add *Panedwindow.sashRelief raised startupFile + if {[tk windowingsystem] ne "aqua"} { + option add *Menu.font uifont startupFile + } + } else { + option add *Menu.TearOff 0 startupFile + } option add *Button.font uifont startupFile option add *Checkbutton.font uifont startupFile option add *Radiobutton.font uifont startupFile - if {[tk windowingsystem] ne "aqua"} { - option add *Menu.font uifont startupFile - } option add *Menubutton.font uifont startupFile option add *Label.font uifont startupFile option add *Message.font uifont startupFile option add *Entry.font uifont startupFile + option add *Labelframe.font uifont startupFile } # Make a menu and submenus. @@ -1893,6 +1917,17 @@ proc mca {str} { return [string map {&& & & {}} [mc $str]] } +proc makedroplist {w varname args} { + global use_ttk + if {$use_ttk} { + set gm [ttk::combobox $w -width 10 -state readonly\ + -textvariable $varname -values $args] + } else { + set gm [eval [linsert $args 0 tk_optionMenu $w $varname]] + } + return $gm +} + proc makewindow {} { global canv canv2 canv3 linespc charspc ctext cflist cscroll global tabstop @@ -1908,7 +1943,7 @@ proc makewindow {} { global headctxmenu progresscanv progressitem progresscoords statusw global fprogitem fprogcoord lastprogupdate progupdatepending global rprogitem rprogcoord rownumsel numcommits - global have_tk85 + global have_tk85 use_ttk NS # The "mc" arguments here are purely so that xgettext # sees the following string as needing to be translated @@ -1960,8 +1995,13 @@ proc makewindow {} { makemenu .bar $bar . configure -menu .bar + if {$use_ttk} { + # cover the non-themed toplevel with a themed frame. + place [ttk::frame ._main_background] -x 0 -y 0 -relwidth 1 -relheight 1 + } + # the gui has upper and lower half, parts of a paned window. - panedwindow .ctop -orient vertical + ${NS}::panedwindow .ctop -orient vertical # possibly use assumed geometry if {![info exists geometry(pwsash0)]} { @@ -1969,14 +2009,17 @@ proc makewindow {} { set geometry(topwidth) [expr {80 * $charspc}] set geometry(botheight) [expr {15 * $linespc}] set geometry(botwidth) [expr {50 * $charspc}] - set geometry(pwsash0) "[expr {40 * $charspc}] 2" - set geometry(pwsash1) "[expr {60 * $charspc}] 2" + set geometry(pwsash0) [list [expr {40 * $charspc}] 2] + set geometry(pwsash1) [list [expr {60 * $charspc}] 2] } # 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 + ${NS}::frame .tf -height $geometry(topheight) -width $geometry(topwidth) + ${NS}::frame .tf.histframe + ${NS}::panedwindow .tf.histframe.pwclist -orient horizontal + if {!$use_ttk} { + .tf.histframe.pwclist configure -sashpad 0 -handlesize 4 + } # create three canvases set cscroll .tf.histframe.csb @@ -1996,19 +2039,28 @@ proc makewindow {} { -selectbackground $selectbgcolor \ -background $bgcolor -bd 0 -yscrollincr $linespc .tf.histframe.pwclist add $canv3 - eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0) - eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1) + if {$use_ttk} { + bind .tf.histframe.pwclist { + bind %W {} + .tf.histframe.pwclist sashpos 1 [lindex $::geometry(pwsash1) 0] + .tf.histframe.pwclist sashpos 0 [lindex $::geometry(pwsash0) 0] + } + } else { + eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0) + eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1) + } # a scroll bar to rule them - scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0 + ${NS}::scrollbar $cscroll -command {allcanvs yview} + if {!$use_ttk} {$cscroll configure -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 + ${NS}::frame .tf.bar + ${NS}::frame .tf.lbar -height 15 set sha1entry .tf.bar.sha1 set entries $sha1entry @@ -2017,7 +2069,7 @@ proc makewindow {} { -command gotocommit -width 8 $sha1but conf -disabledforeground [$sha1but cget -foreground] pack .tf.bar.sha1label -side left - entry $sha1entry -width 40 -font textfont -textvariable sha1string + ${NS}::entry $sha1entry -width 40 -font textfont -textvariable sha1string trace add variable sha1string write sha1change pack $sha1entry -side left -pady 2 @@ -2037,36 +2089,43 @@ proc makewindow {} { 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c, 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01}; } - button .tf.bar.leftbut -image bm-left -command goback \ + ${NS}::button .tf.bar.leftbut -image bm-left -command goback \ -state disabled -width 26 pack .tf.bar.leftbut -side left -fill y - button .tf.bar.rightbut -image bm-right -command goforw \ + ${NS}::button .tf.bar.rightbut -image bm-right -command goforw \ -state disabled -width 26 pack .tf.bar.rightbut -side left -fill y - label .tf.bar.rowlabel -text [mc "Row"] + ${NS}::label .tf.bar.rowlabel -text [mc "Row"] set rownumsel {} - label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \ + ${NS}::label .tf.bar.rownum -width 7 -textvariable rownumsel \ -relief sunken -anchor e - label .tf.bar.rowlabel2 -text "/" - label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \ + ${NS}::label .tf.bar.rowlabel2 -text "/" + ${NS}::label .tf.bar.numcommits -width 7 -textvariable numcommits \ -relief sunken -anchor e pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \ -side left + if {!$use_ttk} { + foreach w {rownum numcommits} {.tf.bar.$w configure -font textfont} + } global selectedline trace add variable selectedline write selectedline_change # Status label and progress bar set statusw .tf.bar.status - label $statusw -width 15 -relief sunken + ${NS}::label $statusw -width 15 -relief sunken pack $statusw -side left -padx 5 - set h [expr {[font metrics uifont -linespace] + 2}] - set progresscanv .tf.bar.progress - canvas $progresscanv -relief sunken -height $h -borderwidth 2 - set progressitem [$progresscanv create rect -1 0 0 $h -fill green] - set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow] - set rprogitem [$progresscanv create rect -1 0 0 $h -fill red] - pack $progresscanv -side right -expand 1 -fill x + if {$use_ttk} { + set progresscanv [ttk::progressbar .tf.bar.progress] + } else { + set h [expr {[font metrics uifont -linespace] + 2}] + set progresscanv .tf.bar.progress + canvas $progresscanv -relief sunken -height $h -borderwidth 2 + set progressitem [$progresscanv create rect -1 0 0 $h -fill green] + set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow] + set rprogitem [$progresscanv create rect -1 0 0 $h -fill red] + } + pack $progresscanv -side right -expand 1 -fill x -padx {0 2} set progresscoords {0 0} set fprogcoord 0 set rprogcoord 0 @@ -2075,14 +2134,14 @@ proc makewindow {} { set progupdatepending 0 # build up the bottom bar of upper window - label .tf.lbar.flabel -text "[mc "Find"] " - button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1} - button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1} - label .tf.lbar.flab2 -text " [mc "commit"] " + ${NS}::label .tf.lbar.flabel -text "[mc "Find"] " + ${NS}::button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1} + ${NS}::button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1} + ${NS}::label .tf.lbar.flab2 -text " [mc "commit"] " pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \ -side left -fill y set gdttype [mc "containing:"] - set gm [tk_optionMenu .tf.lbar.gdttype gdttype \ + set gm [makedroplist .tf.lbar.gdttype gdtype \ [mc "containing:"] \ [mc "touching paths:"] \ [mc "adding/removing string:"]] @@ -2092,14 +2151,14 @@ proc makewindow {} { set findstring {} set fstring .tf.lbar.findstring lappend entries $fstring - entry $fstring -width 30 -font textfont -textvariable findstring + ${NS}::entry $fstring -width 30 -font textfont -textvariable findstring trace add variable findstring write find_change set findtype [mc "Exact"] - set findtypemenu [tk_optionMenu .tf.lbar.findtype \ - findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]] + set findtypemenu [makedroplist .tf.lbar.findtype \ + findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]] trace add variable findtype write findcom_change set findloc [mc "All fields"] - tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \ + makedroplist .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \ [mc "Comments"] [mc "Author"] [mc "Committer"] trace add variable findloc write find_change pack .tf.lbar.findloc -side right @@ -2111,38 +2170,41 @@ proc makewindow {} { pack .tf.bar -in .tf -side bottom -fill x pack .tf.histframe -fill both -side top -expand 1 .ctop add .tf - .ctop paneconfigure .tf -height $geometry(topheight) - .ctop paneconfigure .tf -width $geometry(topwidth) + if {!$use_ttk} { + .ctop paneconfigure .tf -height $geometry(topheight) + .ctop paneconfigure .tf -width $geometry(topwidth) + } # now build up the bottom - panedwindow .pwbottom -orient horizontal + ${NS}::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) + ${NS}::frame .bleft -width $geometry(botwidth) } else { - frame .bleft -width $geometry(botwidth) -height $geometry(botheight) + ${NS}::frame .bleft -width $geometry(botwidth) -height $geometry(botheight) } - frame .bleft.top - frame .bleft.mid - frame .bleft.bottom + ${NS}::frame .bleft.top + ${NS}::frame .bleft.mid + ${NS}::frame .bleft.bottom - button .bleft.top.search -text [mc "Search"] -command dosearch + ${NS}::button .bleft.top.search -text [mc "Search"] -command dosearch pack .bleft.top.search -side left -padx 5 set sstring .bleft.top.sstring - entry $sstring -width 20 -font textfont -textvariable searchstring + set searchstring "" + ${NS}::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 - radiobutton .bleft.mid.diff -text [mc "Diff"] \ + ${NS}::radiobutton .bleft.mid.diff -text [mc "Diff"] \ -command changediffdisp -variable diffelide -value {0 0} - radiobutton .bleft.mid.old -text [mc "Old version"] \ + ${NS}::radiobutton .bleft.mid.old -text [mc "Old version"] \ -command changediffdisp -variable diffelide -value {0 1} - radiobutton .bleft.mid.new -text [mc "New version"] \ + ${NS}::radiobutton .bleft.mid.new -text [mc "New version"] \ -command changediffdisp -variable diffelide -value {1 0} - label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: " + ${NS}::label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: " pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left spinbox .bleft.mid.diffcontext -width 5 -font textfont \ -from 1 -increment 1 -to 10000000 \ @@ -2152,7 +2214,7 @@ proc makewindow {} { trace add variable diffcontextstring write diffcontextchange lappend entries .bleft.mid.diffcontext pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left - checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \ + ${NS}::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \ -command changeignorespace -variable ignorespace pack .bleft.mid.ignspace -side left -padx 5 set ctext .bleft.bottom.ctext @@ -2163,9 +2225,8 @@ proc makewindow {} { if {$have_tk85} { $ctext conf -tabstyle wordprocessor } - scrollbar .bleft.bottom.sb -command "$ctext yview" - scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \ - -width 10 + ${NS}::scrollbar .bleft.bottom.sb -command "$ctext yview" + ${NS}::scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h pack .bleft.top -side top -fill x pack .bleft.mid -side top -fill x grid $ctext .bleft.bottom.sb -sticky nsew @@ -2205,14 +2266,16 @@ proc makewindow {} { $ctext tag conf found -back yellow .pwbottom add .bleft - .pwbottom paneconfigure .bleft -width $geometry(botwidth) + if {!$use_ttk} { + .pwbottom paneconfigure .bleft -width $geometry(botwidth) + } # lower right - frame .bright - frame .bright.mode - radiobutton .bright.mode.patch -text [mc "Patch"] \ + ${NS}::frame .bright + ${NS}::frame .bright.mode + ${NS}::radiobutton .bright.mode.patch -text [mc "Patch"] \ -command reselectline -variable cmitmode -value "patch" - radiobutton .bright.mode.tree -text [mc "Tree"] \ + ${NS}::radiobutton .bright.mode.tree -text [mc "Tree"] \ -command reselectline -variable cmitmode -value "tree" grid .bright.mode.patch .bright.mode.tree -sticky ew pack .bright.mode -side top -fill x @@ -2228,7 +2291,7 @@ proc makewindow {} { -spacing1 1 -spacing3 1 lappend bglist $cflist lappend fglist $cflist - scrollbar .bright.sb -command "$cflist yview" + ${NS}::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 \ @@ -2263,6 +2326,17 @@ proc makewindow {} { set ::BM "2" } + if {$use_ttk} { + bind .ctop { + bind %W {} + %W sashpos 0 $::geometry(topheight) + } + bind .pwbottom { + bind %W {} + %W sashpos 0 $::geometry(botwidth) + } + } + bind .pwbottom {resizecdetpanes %W %w} pack .ctop -fill both -expand 1 bindall <1> {selcanvline %W %x %y} @@ -2482,7 +2556,12 @@ proc click {w} { proc adjustprogress {} { global progresscanv progressitem progresscoords global fprogitem fprogcoord lastprogupdate progupdatepending - global rprogitem rprogcoord + global rprogitem rprogcoord use_ttk + + if {$use_ttk} { + $progresscanv configure -value [expr {int($fprogcoord * 100)}] + return + } set w [expr {[winfo width $progresscanv] - 4}] set x0 [expr {$w * [lindex $progresscoords 0]}] @@ -2518,7 +2597,7 @@ proc savestuff {w} { global viewname viewfiles viewargs viewargscmd viewperm nextviewnum global cmitmode wrapcomment datetimeformat limitdiffs global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor - global autoselect extdifftool perfile_attrs markbgcolor + global autoselect extdifftool perfile_attrs markbgcolor use_ttk if {$stuffsaved} return if {![winfo viewable .]} return @@ -2555,8 +2634,13 @@ proc savestuff {w} { puts $f "set geometry(state) [wm state .]" puts $f "set geometry(topwidth) [winfo width .tf]" puts $f "set geometry(topheight) [winfo height .tf]" - puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\"" - puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\"" + if {$use_ttk} { + puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sashpos 0] 1\"" + puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sashpos 1] 1\"" + } else { + puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\"" + puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\"" + } puts $f "set geometry(botwidth) [winfo width .bleft]" puts $f "set geometry(botheight) [winfo height .bleft]" @@ -2574,10 +2658,15 @@ proc savestuff {w} { } proc resizeclistpanes {win w} { - global oldwidth + global oldwidth use_ttk if {[info exists oldwidth($win)]} { - set s0 [$win sash coord 0] - set s1 [$win sash coord 1] + if {$use_ttk} { + set s0 [$win sashpos 0] + set s1 [$win sashpos 1] + } else { + set s0 [$win sash coord 0] + set s1 [$win sash coord 1] + } if {$w < 60} { set sash0 [expr {int($w/2 - 2)}] set sash1 [expr {int($w*5/6 - 2)}] @@ -2598,16 +2687,25 @@ proc resizeclistpanes {win w} { } } } - $win sash place 0 $sash0 [lindex $s0 1] - $win sash place 1 $sash1 [lindex $s1 1] + if {$use_ttk} { + $win sashpos 0 $sash0 + $win sashpos 1 $sash1 + } else { + $win sash place 0 $sash0 [lindex $s0 1] + $win sash place 1 $sash1 [lindex $s1 1] + } } set oldwidth($win) $w } proc resizecdetpanes {win w} { - global oldwidth + global oldwidth use_ttk if {[info exists oldwidth($win)]} { - set s0 [$win sash coord 0] + if {$use_ttk} { + set s0 [$win sashpos 0] + } else { + set s0 [$win sash coord 0] + } if {$w < 60} { set sash0 [expr {int($w*3/4 - 2)}] } else { @@ -2620,7 +2718,11 @@ proc resizecdetpanes {win w} { set sash0 [expr {$w - 15}] } } - $win sash place 0 $sash0 [lindex $s0 1] + if {$use_ttk} { + $win sashpos 0 $sash0 + } else { + $win sash place 0 $sash0 [lindex $s0 1] + } } set oldwidth($win) $w } @@ -2640,31 +2742,33 @@ proc bindall {event action} { } proc about {} { - global uifont + global uifont NS set w .about if {[winfo exists $w]} { raise $w return } - toplevel $w + ttk_toplevel $w wm title $w [mc "About gitk"] make_transient $w . message $w.m -text [mc " Gitk - a commit viewer for git -Copyright © 2005-2008 Paul Mackerras +Copyright \u00a9 2005-2009 Paul Mackerras Use and redistribute under the terms of the GNU General Public License"] \ -justify center -aspect 400 -border 2 -bg white -relief groove pack $w.m -side top -fill x -padx 2 -pady 2 - button $w.ok -text [mc "Close"] -command "destroy $w" -default active + ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active pack $w.ok -side bottom bind $w "focus $w.ok" bind $w "destroy $w" bind $w "destroy $w" + tk::PlaceWindow $w widget . } proc keys {} { + global NS set w .keys if {[winfo exists $w]} { raise $w @@ -2675,7 +2779,7 @@ proc keys {} { } else { set M1T Ctrl } - toplevel $w + ttk_toplevel $w wm title $w [mc "Gitk key bindings"] make_transient $w . message $w.m -text " @@ -2719,7 +2823,7 @@ proc keys {} { " \ -justify left -bg white -border 2 -relief groove pack $w.m -side top -fill both -padx 2 -pady 2 - button $w.ok -text [mc "Close"] -command "destroy $w" -default active + ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active bind $w [list destroy $w] pack $w.ok -side bottom bind $w "focus $w.ok" @@ -3694,7 +3798,7 @@ proc encode_view_opts {n} { set pattern [lindex $patterns 0] set val $newviewopts($n,[lindex $opt 0]) - + if {[lindex $opt 1] eq "b"} { if {$val} { lappend rargs $pattern @@ -3782,16 +3886,16 @@ proc editview {} { proc vieweditor {top n title} { global newviewname newviewopts viewfiles bgcolor - global known_view_options + global known_view_options NS - toplevel $top + ttk_toplevel $top wm title $top $title make_transient $top . # View name - frame $top.nfr - label $top.nl -text [mc "Name"] - entry $top.name -width 20 -textvariable newviewname($n) + ${NS}::frame $top.nfr + ${NS}::label $top.nl -text [mc "Name"] + ${NS}::entry $top.name -width 20 -textvariable newviewname($n) pack $top.nfr -in $top -fill x -pady 5 -padx 3 pack $top.nl -in $top.nfr -side left -padx {0 30} pack $top.name -in $top.nfr -side left @@ -3810,7 +3914,7 @@ proc vieweditor {top n title} { if {$flags eq "+" || $flags eq "*"} { set cframe $top.fr$cnt incr cnt - frame $cframe + ${NS}::frame $cframe pack $cframe -in $top -fill x -pady 3 -padx 3 set cexpand [expr {$flags eq "*"}] } else { @@ -3818,18 +3922,18 @@ proc vieweditor {top n title} { } if {$type eq "b"} { - checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id) + ${NS}::checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id) pack $cframe.c_$id -in $cframe -side left \ -padx [list $lxpad 0] -expand $cexpand -anchor w } elseif {[regexp {^t(\d+)$} $type type sz]} { - message $cframe.l_$id -aspect 1500 -text $title - entry $cframe.e_$id -width $sz -background $bgcolor \ + ${NS}::label $cframe.l_$id -text $title + ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \ -textvariable newviewopts($n,$id) pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0] pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x } elseif {[regexp {^t(\d+)=$} $type type sz]} { - message $cframe.l_$id -aspect 1500 -text $title - entry $cframe.e_$id -width $sz -background $bgcolor \ + ${NS}::label $cframe.l_$id -text $title + ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \ -textvariable newviewopts($n,$id) pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w pack $cframe.e_$id -in $cframe -side top -fill x @@ -3837,7 +3941,7 @@ proc vieweditor {top n title} { } # Path list - message $top.l -aspect 1500 \ + ${NS}::label $top.l \ -text [mc "Enter files and directories to include, one per line:"] pack $top.l -in $top -side top -pady [list 7 0] -anchor w -padx 3 text $top.t -width 40 -height 5 -background $bgcolor -font uifont @@ -3850,10 +3954,10 @@ proc vieweditor {top n title} { $top.t mark set insert 0.0 } pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3 - frame $top.buts - button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n] - button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1] - button $top.buts.can -text [mc "Cancel"] -command [list destroy $top] + ${NS}::frame $top.buts + ${NS}::button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n] + ${NS}::button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1] + ${NS}::button $top.buts.can -text [mc "Cancel"] -command [list destroy $top] bind $top [list newviewok $top $n] bind $top [list newviewok $top $n 1] bind $top [list destroy $top] @@ -6723,8 +6827,7 @@ proc selectline {l isnew {desired_loc {}}} { $sha1entry delete 0 end $sha1entry insert 0 $id if {$autoselect} { - $sha1entry selection from 0 - $sha1entry selection to end + $sha1entry selection range 0 end } rhighlight_sel $id @@ -8332,7 +8435,7 @@ proc doseldiff {oldid newid} { } proc mkpatch {} { - global rowmenuid currentid commitinfo patchtop patchnum + global rowmenuid currentid commitinfo patchtop patchnum NS if {![info exists currentid]} return set oldid $currentid @@ -8342,38 +8445,38 @@ proc mkpatch {} { set top .patch set patchtop $top catch {destroy $top} - toplevel $top + ttk_toplevel $top make_transient $top . - label $top.title -text [mc "Generate patch"] + ${NS}::label $top.title -text [mc "Generate patch"] grid $top.title - -pady 10 - label $top.from -text [mc "From:"] - entry $top.fromsha1 -width 40 -relief flat + ${NS}::label $top.from -text [mc "From:"] + ${NS}::entry $top.fromsha1 -width 40 $top.fromsha1 insert 0 $oldid $top.fromsha1 conf -state readonly grid $top.from $top.fromsha1 -sticky w - entry $top.fromhead -width 60 -relief flat + ${NS}::entry $top.fromhead -width 60 $top.fromhead insert 0 $oldhead $top.fromhead conf -state readonly grid x $top.fromhead -sticky w - label $top.to -text [mc "To:"] - entry $top.tosha1 -width 40 -relief flat + ${NS}::label $top.to -text [mc "To:"] + ${NS}::entry $top.tosha1 -width 40 $top.tosha1 insert 0 $newid $top.tosha1 conf -state readonly grid $top.to $top.tosha1 -sticky w - entry $top.tohead -width 60 -relief flat + ${NS}::entry $top.tohead -width 60 $top.tohead insert 0 $newhead $top.tohead conf -state readonly grid x $top.tohead -sticky w - button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5 - grid $top.rev x -pady 10 - label $top.flab -text [mc "Output file:"] - entry $top.fname -width 60 + ${NS}::button $top.rev -text [mc "Reverse"] -command mkpatchrev + grid $top.rev x -pady 10 -padx 5 + ${NS}::label $top.flab -text [mc "Output file:"] + ${NS}::entry $top.fname -width 60 $top.fname insert 0 [file normalize "patch$patchnum.patch"] incr patchnum grid $top.flab $top.fname -sticky w - frame $top.buts - button $top.buts.gen -text [mc "Generate"] -command mkpatchgo - button $top.buts.can -text [mc "Cancel"] -command mkpatchcan + ${NS}::frame $top.buts + ${NS}::button $top.buts.gen -text [mc "Generate"] -command mkpatchgo + ${NS}::button $top.buts.can -text [mc "Cancel"] -command mkpatchcan bind $top mkpatchgo bind $top mkpatchcan grid $top.buts.gen $top.buts.can @@ -8424,30 +8527,30 @@ proc mkpatchcan {} { } proc mktag {} { - global rowmenuid mktagtop commitinfo + global rowmenuid mktagtop commitinfo NS set top .maketag set mktagtop $top catch {destroy $top} - toplevel $top + ttk_toplevel $top make_transient $top . - label $top.title -text [mc "Create tag"] + ${NS}::label $top.title -text [mc "Create tag"] grid $top.title - -pady 10 - label $top.id -text [mc "ID:"] - entry $top.sha1 -width 40 -relief flat + ${NS}::label $top.id -text [mc "ID:"] + ${NS}::entry $top.sha1 -width 40 $top.sha1 insert 0 $rowmenuid $top.sha1 conf -state readonly grid $top.id $top.sha1 -sticky w - entry $top.head -width 60 -relief flat + ${NS}::entry $top.head -width 60 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0] $top.head conf -state readonly grid x $top.head -sticky w - label $top.tlab -text [mc "Tag name:"] - entry $top.tag -width 60 + ${NS}::label $top.tlab -text [mc "Tag name:"] + ${NS}::entry $top.tag -width 60 grid $top.tlab $top.tag -sticky w - frame $top.buts - button $top.buts.gen -text [mc "Create"] -command mktaggo - button $top.buts.can -text [mc "Cancel"] -command mktagcan + ${NS}::frame $top.buts + ${NS}::button $top.buts.gen -text [mc "Create"] -command mktaggo + ${NS}::button $top.buts.can -text [mc "Cancel"] -command mktagcan bind $top mktaggo bind $top mktagcan grid $top.buts.gen $top.buts.can @@ -8530,34 +8633,34 @@ proc mktaggo {} { } proc writecommit {} { - global rowmenuid wrcomtop commitinfo wrcomcmd + global rowmenuid wrcomtop commitinfo wrcomcmd NS set top .writecommit set wrcomtop $top catch {destroy $top} - toplevel $top + ttk_toplevel $top make_transient $top . - label $top.title -text [mc "Write commit to file"] + ${NS}::label $top.title -text [mc "Write commit to file"] grid $top.title - -pady 10 - label $top.id -text [mc "ID:"] - entry $top.sha1 -width 40 -relief flat + ${NS}::label $top.id -text [mc "ID:"] + ${NS}::entry $top.sha1 -width 40 $top.sha1 insert 0 $rowmenuid $top.sha1 conf -state readonly grid $top.id $top.sha1 -sticky w - entry $top.head -width 60 -relief flat + ${NS}::entry $top.head -width 60 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0] $top.head conf -state readonly grid x $top.head -sticky w - label $top.clab -text [mc "Command:"] - entry $top.cmd -width 60 -textvariable wrcomcmd + ${NS}::label $top.clab -text [mc "Command:"] + ${NS}::entry $top.cmd -width 60 -textvariable wrcomcmd grid $top.clab $top.cmd -sticky w -pady 10 - label $top.flab -text [mc "Output file:"] - entry $top.fname -width 60 + ${NS}::label $top.flab -text [mc "Output file:"] + ${NS}::entry $top.fname -width 60 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"] grid $top.flab $top.fname -sticky w - frame $top.buts - button $top.buts.gen -text [mc "Write"] -command wrcomgo - button $top.buts.can -text [mc "Cancel"] -command wrcomcan + ${NS}::frame $top.buts + ${NS}::button $top.buts.gen -text [mc "Write"] -command wrcomgo + ${NS}::button $top.buts.can -text [mc "Cancel"] -command wrcomcan bind $top wrcomgo bind $top wrcomcan grid $top.buts.gen $top.buts.can @@ -8588,25 +8691,25 @@ proc wrcomcan {} { } proc mkbranch {} { - global rowmenuid mkbrtop + global rowmenuid mkbrtop NS set top .makebranch catch {destroy $top} - toplevel $top + ttk_toplevel $top make_transient $top . - label $top.title -text [mc "Create new branch"] + ${NS}::label $top.title -text [mc "Create new branch"] grid $top.title - -pady 10 - label $top.id -text [mc "ID:"] - entry $top.sha1 -width 40 -relief flat + ${NS}::label $top.id -text [mc "ID:"] + ${NS}::entry $top.sha1 -width 40 $top.sha1 insert 0 $rowmenuid $top.sha1 conf -state readonly grid $top.id $top.sha1 -sticky w - label $top.nlab -text [mc "Name:"] - entry $top.name -width 40 + ${NS}::label $top.nlab -text [mc "Name:"] + ${NS}::entry $top.name -width 40 grid $top.nlab $top.name -sticky w - frame $top.buts - button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top] - button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}" + ${NS}::frame $top.buts + ${NS}::button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top] + ${NS}::button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}" bind $top [list mkbrgo $top] bind $top "catch {destroy $top}" grid $top.buts.go $top.buts.can @@ -8751,34 +8854,31 @@ proc cherrypick {} { } proc resethead {} { - global mainhead rowmenuid confirm_ok resettype + global mainhead rowmenuid confirm_ok resettype NS set confirm_ok 0 set w ".confirmreset" - toplevel $w + ttk_toplevel $w make_transient $w . wm title $w [mc "Confirm reset"] - message $w.m -text \ - [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \ - -justify center -aspect 1000 + ${NS}::label $w.m -text \ + [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] pack $w.m -side top -fill x -padx 20 -pady 20 - frame $w.f -relief sunken -border 2 - message $w.f.rt -text [mc "Reset type:"] -aspect 1000 - grid $w.f.rt -sticky w + ${NS}::labelframe $w.f -text [mc "Reset type:"] set resettype mixed - radiobutton $w.f.soft -value soft -variable resettype -justify left \ + ${NS}::radiobutton $w.f.soft -value soft -variable resettype \ -text [mc "Soft: Leave working tree and index untouched"] grid $w.f.soft -sticky w - radiobutton $w.f.mixed -value mixed -variable resettype -justify left \ + ${NS}::radiobutton $w.f.mixed -value mixed -variable resettype \ -text [mc "Mixed: Leave working tree untouched, reset index"] grid $w.f.mixed -sticky w - radiobutton $w.f.hard -value hard -variable resettype -justify left \ + ${NS}::radiobutton $w.f.hard -value hard -variable resettype \ -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"] grid $w.f.hard -sticky w - pack $w.f -side top -fill x - button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w" + pack $w.f -side top -fill x -padx 4 + ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w" pack $w.ok -side left -fill x -padx 20 -pady 20 - button $w.cancel -text [mc Cancel] -command "destroy $w" + ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w" bind $w [list destroy $w] pack $w.cancel -side right -fill x -padx 20 -pady 20 bind $w "grab $w; focus $w" @@ -8926,7 +9026,7 @@ proc rmbranch {} { # Display a list of tags and heads proc showrefs {} { - global showrefstop bgcolor fgcolor selectbgcolor + global showrefstop bgcolor fgcolor selectbgcolor NS global bglist fglist reflistfilter reflist maincursor set top .showrefs @@ -8936,7 +9036,7 @@ proc showrefs {} { refill_reflist return } - toplevel $top + ttk_toplevel $top wm title $top [mc "Tags and heads: %s" [file tail [pwd]]] make_transient $top . text $top.list -background $bgcolor -foreground $fgcolor \ @@ -8947,19 +9047,19 @@ proc showrefs {} { $top.list tag configure highlight -background $selectbgcolor lappend bglist $top.list lappend fglist $top.list - scrollbar $top.ysb -command "$top.list yview" -orient vertical - scrollbar $top.xsb -command "$top.list xview" -orient horizontal + ${NS}::scrollbar $top.ysb -command "$top.list yview" -orient vertical + ${NS}::scrollbar $top.xsb -command "$top.list xview" -orient horizontal grid $top.list $top.ysb -sticky nsew grid $top.xsb x -sticky ew - frame $top.f - label $top.f.l -text "[mc "Filter"]: " - entry $top.f.e -width 20 -textvariable reflistfilter + ${NS}::frame $top.f + ${NS}::label $top.f.l -text "[mc "Filter"]: " + ${NS}::entry $top.f.e -width 20 -textvariable reflistfilter set reflistfilter "*" trace add variable reflistfilter write reflistfilter_change pack $top.f.e -side right -fill x -expand 1 pack $top.f.l -side left grid $top.f - -sticky ew -pady 2 - button $top.close -command [list destroy $top] -text [mc "Close"] + ${NS}::button $top.close -command [list destroy $top] -text [mc "Close"] bind $top [list destroy $top] grid $top.close - grid columnconfigure $top 0 -weight 1 @@ -9157,7 +9257,7 @@ proc getallclines {fd} { global allparents allchildren idtags idheads nextarc global arcnos arcids arctags arcout arcend arcstart archeads growing global seeds allcommits cachedarcs allcupdate - + set nid 0 while {[incr nid] <= 1000 && [gets $fd line] >= 0} { set id [lindex $line 0] @@ -10255,19 +10355,20 @@ proc doquit {} { } proc mkfontdisp {font top which} { - global fontattr fontpref $font + global fontattr fontpref $font NS use_ttk set fontpref($font) [set $font] - button $top.${font}but -text $which -font optionfont \ + ${NS}::button $top.${font}but -text $which \ -command [list choosefont $font $which] - label $top.$font -relief flat -font $font \ + if {!$use_ttk} {$top.${font}but configure -font optionfont} + ${NS}::label $top.$font -relief flat -font $font \ -text $fontattr($font,family) -justify left grid x $top.${font}but $top.$font -sticky w } proc choosefont {font which} { global fontparam fontlist fonttop fontattr - global prefstop + global prefstop NS set fontparam(which) $which set fontparam(font) $font @@ -10280,21 +10381,21 @@ proc choosefont {font which} { if {![winfo exists $top]} { font create sample eval font config sample [font actual $font] - toplevel $top + ttk_toplevel $top make_transient $top $prefstop wm title $top [mc "Gitk font chooser"] - label $top.l -textvariable fontparam(which) + ${NS}::label $top.l -textvariable fontparam(which) pack $top.l -side top set fontlist [lsort [font families]] - frame $top.f + ${NS}::frame $top.f listbox $top.f.fam -listvariable fontlist \ -yscrollcommand [list $top.f.sb set] bind $top.f.fam <> selfontfam - scrollbar $top.f.sb -command [list $top.f.fam yview] + ${NS}::scrollbar $top.f.sb -command [list $top.f.fam yview] pack $top.f.sb -side right -fill y pack $top.f.fam -side left -fill both -expand 1 pack $top.f -side top -fill both -expand 1 - frame $top.g + ${NS}::frame $top.g spinbox $top.g.size -from 4 -to 40 -width 4 \ -textvariable fontparam(size) \ -validatecommand {string is integer -strict %s} @@ -10312,9 +10413,9 @@ proc choosefont {font which} { -fill black -tags text bind $top.c [list centertext $top.c] pack $top.c -side top -fill x - frame $top.buts - button $top.buts.ok -text [mc "OK"] -command fontok -default active - button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal + ${NS}::frame $top.buts + ${NS}::button $top.buts.ok -text [mc "OK"] -command fontok -default active + ${NS}::button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal bind $top fontok bind $top fontcan grid $top.buts.ok $top.buts.can @@ -10350,7 +10451,7 @@ proc fontok {} { } set w $prefstop.$f $w conf -text $fontparam(family) -font $fontpref($f) - + fontcan } @@ -10365,6 +10466,28 @@ proc fontcan {} { } } +if {[package vsatisfies [package provide Tk] 8.6]} { + # In Tk 8.6 we have a native font chooser dialog. Overwrite the above + # function to make use of it. + proc choosefont {font which} { + tk fontchooser configure -title $which -font $font \ + -command [list on_choosefont $font $which] + tk fontchooser show + } + proc on_choosefont {font which newfont} { + global fontparam + puts stderr "$font $newfont" + array set f [font actual $newfont] + set fontparam(which) $which + set fontparam(font) $font + set fontparam(family) $f(-family) + set fontparam(size) $f(-size) + set fontparam(weight) $f(-weight) + set fontparam(slant) $f(-slant) + fontok + } +} + proc selfontfam {} { global fonttop fontparam @@ -10381,7 +10504,7 @@ proc chg_fontparam {v sub op} { } proc doprefs {} { - global maxwidth maxgraphpct + global maxwidth maxgraphpct use_ttk NS global oldprefs prefstop showneartags showlocalchanges global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor global tabstop limitdiffs autoselect extdifftool perfile_attrs @@ -10396,103 +10519,109 @@ proc doprefs {} { limitdiffs tabstop perfile_attrs} { set oldprefs($v) [set $v] } - toplevel $top + ttk_toplevel $top wm title $top [mc "Gitk preferences"] make_transient $top . - label $top.ldisp -text [mc "Commit list display options"] + ${NS}::label $top.ldisp -text [mc "Commit list display options"] grid $top.ldisp - -sticky w -pady 10 - label $top.spacer -text " " - label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \ - -font optionfont + ${NS}::label $top.spacer -text " " + ${NS}::label $top.maxwidthl -text [mc "Maximum graph width (lines)"] spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w - label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \ - -font optionfont + ${NS}::label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct grid x $top.maxpctl $top.maxpct -sticky w - checkbutton $top.showlocal -text [mc "Show local changes"] \ - -font optionfont -variable showlocalchanges + ${NS}::checkbutton $top.showlocal -text [mc "Show local changes"] \ + -variable showlocalchanges grid x $top.showlocal -sticky w - checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \ - -font optionfont -variable autoselect + ${NS}::checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \ + -variable autoselect grid x $top.autoselect -sticky w - label $top.ddisp -text [mc "Diff display options"] + ${NS}::label $top.ddisp -text [mc "Diff display options"] grid $top.ddisp - -sticky w -pady 10 - label $top.tabstopl -text [mc "Tab spacing"] -font optionfont + ${NS}::label $top.tabstopl -text [mc "Tab spacing"] spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop grid x $top.tabstopl $top.tabstop -sticky w - checkbutton $top.ntag -text [mc "Display nearby tags"] \ - -font optionfont -variable showneartags + ${NS}::checkbutton $top.ntag -text [mc "Display nearby tags"] \ + -variable showneartags grid x $top.ntag -sticky w - checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \ - -font optionfont -variable limitdiffs + ${NS}::checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \ + -variable limitdiffs grid x $top.ldiff -sticky w - checkbutton $top.lattr -text [mc "Support per-file encodings"] \ - -font optionfont -variable perfile_attrs + ${NS}::checkbutton $top.lattr -text [mc "Support per-file encodings"] \ + -variable perfile_attrs grid x $top.lattr -sticky w - entry $top.extdifft -textvariable extdifftool - frame $top.extdifff - label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \ - -padx 10 - button $top.extdifff.b -text [mc "Choose..."] -font optionfont \ - -command choose_extdiff + ${NS}::entry $top.extdifft -textvariable extdifftool + ${NS}::frame $top.extdifff + ${NS}::label $top.extdifff.l -text [mc "External diff tool" ] + ${NS}::button $top.extdifff.b -text [mc "Choose..."] -command choose_extdiff pack $top.extdifff.l $top.extdifff.b -side left - grid x $top.extdifff $top.extdifft -sticky w + pack configure $top.extdifff.l -padx 10 + grid x $top.extdifff $top.extdifft -sticky ew - label $top.cdisp -text [mc "Colors: press to choose"] + ${NS}::label $top.cdisp -text [mc "Colors: press to choose"] grid $top.cdisp - -sticky w -pady 10 label $top.bg -padx 40 -relief sunk -background $bgcolor - button $top.bgbut -text [mc "Background"] -font optionfont \ + ${NS}::button $top.bgbut -text [mc "Background"] \ -command [list choosecolor bgcolor {} $top.bg [mc "background"] setbg] grid x $top.bgbut $top.bg -sticky w label $top.fg -padx 40 -relief sunk -background $fgcolor - button $top.fgbut -text [mc "Foreground"] -font optionfont \ + ${NS}::button $top.fgbut -text [mc "Foreground"] \ -command [list choosecolor fgcolor {} $top.fg [mc "foreground"] setfg] grid x $top.fgbut $top.fg -sticky w label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0] - button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \ + ${NS}::button $top.diffoldbut -text [mc "Diff: old lines"] \ -command [list choosecolor diffcolors 0 $top.diffold [mc "diff old lines"] \ [list $ctext tag conf d0 -foreground]] grid x $top.diffoldbut $top.diffold -sticky w label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1] - button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \ + ${NS}::button $top.diffnewbut -text [mc "Diff: new lines"] \ -command [list choosecolor diffcolors 1 $top.diffnew [mc "diff new lines"] \ [list $ctext tag conf dresult -foreground]] grid x $top.diffnewbut $top.diffnew -sticky w label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2] - button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \ + ${NS}::button $top.hunksepbut -text [mc "Diff: hunk header"] \ -command [list choosecolor diffcolors 2 $top.hunksep \ [mc "diff hunk header"] \ [list $ctext tag conf hunksep -foreground]] grid x $top.hunksepbut $top.hunksep -sticky w label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor - button $top.markbgbut -text [mc "Marked line bg"] -font optionfont \ + ${NS}::button $top.markbgbut -text [mc "Marked line bg"] \ -command [list choosecolor markbgcolor {} $top.markbgsep \ [mc "marked line background"] \ [list $ctext tag conf omark -background]] grid x $top.markbgbut $top.markbgsep -sticky w label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor - button $top.selbgbut -text [mc "Select bg"] -font optionfont \ + ${NS}::button $top.selbgbut -text [mc "Select bg"] \ -command [list choosecolor selectbgcolor {} $top.selbgsep [mc "background"] setselbg] grid x $top.selbgbut $top.selbgsep -sticky w - label $top.cfont -text [mc "Fonts: press to choose"] + ${NS}::label $top.cfont -text [mc "Fonts: press to choose"] grid $top.cfont - -sticky w -pady 10 mkfontdisp mainfont $top [mc "Main font"] mkfontdisp textfont $top [mc "Diff display font"] mkfontdisp uifont $top [mc "User interface font"] - frame $top.buts - button $top.buts.ok -text [mc "OK"] -command prefsok -default active - button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal + if {!$use_ttk} { + foreach w {maxpctl maxwidthl showlocal autoselect tabstopl ntag + ldiff lattr extdifff.l extdifff.b bgbut fgbut + diffoldbut diffnewbut hunksepbut markbgbut selbgbut} { + $top.$w configure -font optionfont + } + } + + ${NS}::frame $top.buts + ${NS}::button $top.buts.ok -text [mc "OK"] -command prefsok -default active + ${NS}::button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal bind $top prefsok bind $top prefscan grid $top.buts.ok $top.buts.can grid columnconfigure $top.buts 0 -weight 1 -uniform a grid columnconfigure $top.buts 1 -weight 1 -uniform a grid $top.buts - - -pady 10 -sticky ew + grid columnconfigure $top 2 -weight 1 bind $top "focus $top.buts.ok" } @@ -11156,6 +11285,10 @@ set nullid2 "0000000000000000000000000000000000000001" set nullfile "/dev/null" set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}] +if {![info exists use_ttk]} { + set use_ttk [llength [info commands ::ttk::style]] +} +set NS [expr {$use_ttk ? "ttk" : ""}] set runq {} set history {} From 3cb1f9c9820357812a9f17df92008c8838cbc1fc Mon Sep 17 00:00:00 2001 From: Pat Thoyts Date: Tue, 12 May 2009 15:45:06 +0100 Subject: [PATCH 003/884] gitk: Fix errors in the theme patch This fixes a typo in the commit selection combobox that prevented it from working properly, and sets the width of the widget. This also fixes show_error to handle errors arising before the gui is fully configured (ie: invalid command line parameters) Signed-off-by: Pat Thoyts Signed-off-by: Paul Mackerras --- gitk | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gitk b/gitk index 4526193b3d..082fa77513 100755 --- a/gitk +++ b/gitk @@ -1798,6 +1798,7 @@ proc make_transient {window origin} { proc show_error {w top msg} { global NS + if {![info exists NS]} {set NS ""} if {[wm state $top] eq "withdrawn"} { wm deiconify $top } message $w.m -text $msg -justify center -aspect 400 pack $w.m -side top -fill x -padx 20 -pady 20 @@ -1920,7 +1921,12 @@ proc mca {str} { proc makedroplist {w varname args} { global use_ttk if {$use_ttk} { - set gm [ttk::combobox $w -width 10 -state readonly\ + set width 0 + foreach label $args { + set cx [string length $label] + if {$cx > $width} {set width $cx} + } + set gm [ttk::combobox $w -width $width -state readonly\ -textvariable $varname -values $args] } else { set gm [eval [linsert $args 0 tk_optionMenu $w $varname]] @@ -2141,7 +2147,7 @@ proc makewindow {} { pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \ -side left -fill y set gdttype [mc "containing:"] - set gm [makedroplist .tf.lbar.gdttype gdtype \ + set gm [makedroplist .tf.lbar.gdttype gdttype \ [mc "containing:"] \ [mc "touching paths:"] \ [mc "adding/removing string:"]] From acd2a45b83e50c0f33b01ee74df241f1adfdff39 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 11 Feb 2009 02:28:03 -0800 Subject: [PATCH 004/884] Refuse updating the current branch in a non-bare repository via push This makes git-push refuse pushing into a non-bare repository to update the current branch by default. To help people who are used to be able to do this (and later "reset --hard" it in some other way), an error message is issued when this refusal is triggered, instructing how to resurrect the old behaviour. Hosting sites that do not give the users direct access to customize their repositories (e.g. repo.or.cz, gitorious, github etc.) may further want to explicitly set the configuration variable to "refuse" for their customers' repositories. Signed-off-by: Junio C Hamano --- builtin-receive-pack.c | 40 ++++++++++++++++--------------------- t/t5400-send-pack.sh | 3 ++- t/t5401-update-hooks.sh | 1 + t/t5405-send-pack-rewind.sh | 1 + t/t5516-fetch-push.sh | 1 + t/t5517-push-mirror.sh | 3 ++- t/t5522-pull-symlink.sh | 20 ++++++++++++------- t/t5701-clone-local.sh | 4 +++- 8 files changed, 40 insertions(+), 33 deletions(-) diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index 6ec1d056e6..b8b69dde48 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -218,33 +218,27 @@ static int is_ref_checked_out(const char *ref) return !strcmp(head_name, ref); } -static char *warn_unconfigured_deny_msg[] = { - "Updating the currently checked out branch may cause confusion,", - "as the index and work tree do not reflect changes that are in HEAD.", - "As a result, you may see the changes you just pushed into it", - "reverted when you run 'git diff' over there, and you may want", - "to run 'git reset --hard' before starting to work to recover.", +static char *refuse_unconfigured_deny_msg[] = { + "By default, updating the current branch in a non-bare repository", + "is denied, because it will make the index and work tree inconsistent", + "with what you pushed, and will require 'git reset --hard' to match", + "the work tree to HEAD.", "", "You can set 'receive.denyCurrentBranch' configuration variable to", - "'refuse' in the remote repository to forbid pushing into its", - "current branch." + "'ignore' or 'warn' in the remote repository to allow pushing into", + "its current branch; however, this is not recommended unless you", + "arranged to update its work tree to match what you pushed in some", + "other way.", "", - "To allow pushing into the current branch, you can set it to 'ignore';", - "but this is not recommended unless you arranged to update its work", - "tree to match what you pushed in some other way.", - "", - "To squelch this message, you can set it to 'warn'.", - "", - "Note that the default will change in a future version of git", - "to refuse updating the current branch unless you have the", - "configuration variable set to either 'ignore' or 'warn'." + "To squelch this message and still keep the default behaviour, set", + "'receive.denyCurrentBranch' configuration variable to 'refuse'." }; -static void warn_unconfigured_deny(void) +static void refuse_unconfigured_deny(void) { int i; - for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++) - warning("%s", warn_unconfigured_deny_msg[i]); + for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++) + error("%s", refuse_unconfigured_deny_msg[i]); } static char *warn_unconfigured_deny_delete_current_msg[] = { @@ -290,14 +284,14 @@ static const char *update(struct command *cmd) switch (deny_current_branch) { case DENY_IGNORE: break; - case DENY_UNCONFIGURED: case DENY_WARN: warning("updating the current branch"); - if (deny_current_branch == DENY_UNCONFIGURED) - warn_unconfigured_deny(); break; case DENY_REFUSE: + case DENY_UNCONFIGURED: error("refusing to update checked out branch: %s", name); + if (deny_current_branch == DENY_UNCONFIGURED) + refuse_unconfigured_deny(); return "branch is currently checked out"; } } diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index f2d5581b12..8463332cb8 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -32,7 +32,7 @@ test_expect_success setup ' done && git update-ref HEAD "$commit" && git clone ./. victim && - ( cd victim && git log ) && + ( cd victim && git config receive.denyCurrentBranch warn && git log ) && git update-ref HEAD "$zero" && parent=$zero && i=0 && @@ -129,6 +129,7 @@ rewound_push_setup() { cd parent && git init && echo one >file && git add file && git commit -m one && + git config receive.denyCurrentBranch warn && echo two >file && git commit -a -m two ) && git clone parent child && diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh index 64f66c94f3..325714e529 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -18,6 +18,7 @@ test_expect_success setup ' git update-ref refs/heads/master $commit0 && git update-ref refs/heads/tofail $commit1 && git clone ./. victim && + GIT_DIR=victim/.git git config receive.denyCurrentBranch warn && GIT_DIR=victim/.git git update-ref refs/heads/tofail $commit1 && git update-ref refs/heads/master $commit1 && git update-ref refs/heads/tofail $commit0 diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh index cb9aacc7bc..4bda18a662 100755 --- a/t/t5405-send-pack-rewind.sh +++ b/t/t5405-send-pack-rewind.sh @@ -8,6 +8,7 @@ test_expect_success setup ' >file1 && git add file1 && test_tick && git commit -m Initial && + git config receive.denyCurrentBranch warn && mkdir another && ( cd another && diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 2d2633f3f8..6529d97dc0 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -12,6 +12,7 @@ mk_empty () { ( cd testrepo && git init && + git config receive.denyCurrentBranch warn && mv .git/hooks .git/hooks-disabled ) } diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh index ea49dedbf8..e2ad260508 100755 --- a/t/t5517-push-mirror.sh +++ b/t/t5517-push-mirror.sh @@ -19,7 +19,8 @@ mk_repo_pair () { mkdir mirror && ( cd mirror && - git init + git init && + git config receive.denyCurrentBranch warn ) && mkdir master && ( diff --git a/t/t5522-pull-symlink.sh b/t/t5522-pull-symlink.sh index 86bbd7d024..7206817ca1 100755 --- a/t/t5522-pull-symlink.sh +++ b/t/t5522-pull-symlink.sh @@ -20,13 +20,19 @@ fi # # The working directory is subdir-link. -mkdir subdir -echo file >subdir/file -git add subdir/file -git commit -q -m file -git clone -q . clone-repo -ln -s clone-repo/subdir/ subdir-link - +test_expect_success setup ' + mkdir subdir && + echo file >subdir/file && + git add subdir/file && + git commit -q -m file && + git clone -q . clone-repo && + ln -s clone-repo/subdir/ subdir-link && + ( + cd clone-repo && + git config receive.denyCurrentBranch warn + ) && + git config receive.denyCurrentBranch warn +' # Demonstrate that things work if we just avoid the symlink # diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh index 19b5c0d552..8b4c356cd2 100755 --- a/t/t5701-clone-local.sh +++ b/t/t5701-clone-local.sh @@ -119,7 +119,9 @@ test_expect_success 'bundle clone with nonexistent HEAD' ' test_expect_success 'clone empty repository' ' cd "$D" && mkdir empty && - (cd empty && git init) && + (cd empty && + git init && + git config receive.denyCurrentBranch warn) && git clone empty empty-clone && test_tick && (cd empty-clone From 375881fa6a43e21ab922b20b2061f9868ef18644 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 9 Feb 2009 00:19:46 -0800 Subject: [PATCH 005/884] Refuse deleting the current branch via push This makes git-push refuse deleting the current branch by default. Signed-off-by: Junio C Hamano --- builtin-receive-pack.c | 30 ++++++++++++------------------ t/t5400-send-pack.sh | 9 ++------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index b8b69dde48..db12b813ac 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -241,30 +241,24 @@ static void refuse_unconfigured_deny(void) error("%s", refuse_unconfigured_deny_msg[i]); } -static char *warn_unconfigured_deny_delete_current_msg[] = { - "Deleting the current branch can cause confusion by making the next", - "'git clone' not check out any file.", +static char *refuse_unconfigured_deny_delete_current_msg[] = { + "By default, deleting the current branch is denied, because the next", + "'git clone' won't result in any file checked out, causing confusion.", "", "You can set 'receive.denyDeleteCurrent' configuration variable to", - "'refuse' in the remote repository to disallow deleting the current", - "branch.", + "'warn' or 'ignore' in the remote repository to allow deleting the", + "current branch, with or without a warning message.", "", - "You can set it to 'ignore' to allow such a delete without a warning.", - "", - "To make this warning message less loud, you can set it to 'warn'.", - "", - "Note that the default will change in a future version of git", - "to refuse deleting the current branch unless you have the", - "configuration variable set to either 'ignore' or 'warn'." + "To squelch this message, you can set it to 'refuse'." }; -static void warn_unconfigured_deny_delete_current(void) +static void refuse_unconfigured_deny_delete_current(void) { int i; for (i = 0; - i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg); + i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg); i++) - warning("%s", warn_unconfigured_deny_delete_current_msg[i]); + error("%s", refuse_unconfigured_deny_delete_current_msg[i]); } static const char *update(struct command *cmd) @@ -313,12 +307,12 @@ static const char *update(struct command *cmd) case DENY_IGNORE: break; case DENY_WARN: - case DENY_UNCONFIGURED: - if (deny_delete_current == DENY_UNCONFIGURED) - warn_unconfigured_deny_delete_current(); warning("deleting the current branch"); break; case DENY_REFUSE: + case DENY_UNCONFIGURED: + if (deny_delete_current == DENY_UNCONFIGURED) + refuse_unconfigured_deny_delete_current(); error("refusing to delete the current branch: %s", name); return "deletion of the current branch prohibited"; } diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 8463332cb8..c718253673 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -191,16 +191,11 @@ test_expect_success 'pushing wildcard refspecs respects forcing' ' test "$parent_head" = "$child_head" ' -test_expect_success 'warn pushing to delete current branch' ' +test_expect_success 'deny pushing to delete current branch' ' rewound_push_setup && ( cd child && - git send-pack ../parent :refs/heads/master 2>errs - ) && - grep "warning: to refuse deleting" child/errs && - ( - cd parent && - test_must_fail git rev-parse --verify master + test_must_fail git send-pack ../parent :refs/heads/master 2>errs ) ' From f245194f9a13d5108c3a59fd4ab1770ae9fd5b65 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 22 May 2009 12:45:29 -0700 Subject: [PATCH 006/884] diff: change semantics of "ignore whitespace" options Traditionally, the --ignore-whitespace* options have merely meant to tell the diff output routine that some class of differences are not worth showing in the textual diff output, so that the end user has easier time to review the remaining (presumably more meaningful) changes. These options never affected the outcome of the command, given as the exit status when the --exit-code option was in effect (either directly or indirectly). When you have only whitespace changes, however, you might expect git diff -b --exit-code to report that there is _no_ change with zero exit status. Change the semantics of --ignore-whitespace* options to mean more than "omit showing the difference in text". The exit status, when --exit-code is in effect, is computed by checking if we found any differences at the path level, while diff frontends feed filepairs to the diffcore engine. When "ignore whitespace" options are in effect, we defer this determination until the very end of diffcore transformation. We simply do not know until the textual diff is generated, which comes very late in the pipeline. When --quiet is in effect, various diff frontends optimize by breaking out early from the loop that enumerates the filepairs, when we find the first path level difference; when --ignore-whitespace* is used the above change automatically disables this optimization. Signed-off-by: Junio C Hamano --- diff.c | 34 +++++++++++++++++-- diff.h | 1 + t/t4037-whitespace-status.sh | 63 ++++++++++++++++++++++++++++++++++++ tree-diff.c | 3 +- 4 files changed, 97 insertions(+), 4 deletions(-) create mode 100755 t/t4037-whitespace-status.sh diff --git a/diff.c b/diff.c index cd35e0c2d7..467925d931 100644 --- a/diff.c +++ b/diff.c @@ -2378,6 +2378,20 @@ int diff_setup_done(struct diff_options *options) if (count > 1) die("--name-only, --name-status, --check and -s are mutually exclusive"); + /* + * Most of the time we can say "there are changes" + * only by checking if there are changed paths, but + * --ignore-whitespace* options force us to look + * inside contets. + */ + + if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) || + DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) || + DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL)) + DIFF_OPT_SET(options, DIFF_FROM_CONTENTS); + else + DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS); + if (DIFF_OPT_TST(options, FIND_COPIES_HARDER)) options->detect_rename = DIFF_DETECT_COPY; @@ -3330,6 +3344,18 @@ free_queue: q->nr = q->alloc = 0; if (options->close_file) fclose(options->file); + + /* + * Report the contents level differences with HAS_CHANGES; + * diff_addremove/diff_change does not set the bit when + * DIFF_FROM_CONTENTS is in effect (e.g. with -w). + */ + if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) { + if (options->found_changes) + DIFF_OPT_SET(options, HAS_CHANGES); + else + DIFF_OPT_CLR(options, HAS_CHANGES); + } } static void diffcore_apply_filter(const char *filter) @@ -3466,7 +3492,7 @@ void diffcore_std(struct diff_options *options) diff_resolve_rename_copy(); diffcore_apply_filter(options->filter); - if (diff_queued_diff.nr) + if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) DIFF_OPT_SET(options, HAS_CHANGES); else DIFF_OPT_CLR(options, HAS_CHANGES); @@ -3526,7 +3552,8 @@ void diff_addremove(struct diff_options *options, fill_filespec(two, sha1, mode); diff_queue(&diff_queued_diff, one, two); - DIFF_OPT_SET(options, HAS_CHANGES); + if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) + DIFF_OPT_SET(options, HAS_CHANGES); } void diff_change(struct diff_options *options, @@ -3558,7 +3585,8 @@ void diff_change(struct diff_options *options, fill_filespec(two, new_sha1, new_mode); diff_queue(&diff_queued_diff, one, two); - DIFF_OPT_SET(options, HAS_CHANGES); + if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) + DIFF_OPT_SET(options, HAS_CHANGES); } void diff_unmerge(struct diff_options *options, diff --git a/diff.h b/diff.h index 6616877ee5..538e4f0d8f 100644 --- a/diff.h +++ b/diff.h @@ -66,6 +66,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_DIRSTAT_CUMULATIVE (1 << 19) #define DIFF_OPT_DIRSTAT_BY_FILE (1 << 20) #define DIFF_OPT_ALLOW_TEXTCONV (1 << 21) +#define DIFF_OPT_DIFF_FROM_CONTENTS (1 << 22) #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) #define DIFF_OPT_CLR(opts, flag) ((opts)->flags &= ~DIFF_OPT_##flag) diff --git a/t/t4037-whitespace-status.sh b/t/t4037-whitespace-status.sh new file mode 100755 index 0000000000..a30b03bcf2 --- /dev/null +++ b/t/t4037-whitespace-status.sh @@ -0,0 +1,63 @@ +#!/bin/sh + +test_description='diff --exit-code with whitespace' +. ./test-lib.sh + +test_expect_success setup ' + mkdir a b && + echo >c && + echo >a/d && + echo >b/e && + git add . && + test_tick && + git commit -m initial && + echo " " >a/d && + test_tick && + git commit -a -m second && + echo " " >a/d && + echo " " >b/e && + git add a/d +' + +test_expect_success 'diff-tree --exit-code' ' + test_must_fail git diff --exit-code HEAD^ HEAD && + test_must_fail git diff-tree --exit-code HEAD^ HEAD +' + +test_expect_success 'diff-tree -b --exit-code' ' + git diff -b --exit-code HEAD^ HEAD && + git diff-tree -b -p --exit-code HEAD^ HEAD && + git diff-tree -b --exit-code HEAD^ HEAD +' + +test_expect_success 'diff-index --cached --exit-code' ' + test_must_fail git diff --cached --exit-code HEAD && + test_must_fail git diff-index --cached --exit-code HEAD +' + +test_expect_success 'diff-index -b -p --cached --exit-code' ' + git diff -b --cached --exit-code HEAD && + git diff-index -b -p --cached --exit-code HEAD +' + +test_expect_success 'diff-index --exit-code' ' + test_must_fail git diff --exit-code HEAD && + test_must_fail git diff-index --exit-code HEAD +' + +test_expect_success 'diff-index -b -p --exit-code' ' + git diff -b --exit-code HEAD && + git diff-index -b -p --exit-code HEAD +' + +test_expect_success 'diff-files --exit-code' ' + test_must_fail git diff --exit-code && + test_must_fail git diff-files --exit-code +' + +test_expect_success 'diff-files -b -p --exit-code' ' + git diff -b --exit-code && + git diff-files -b -p --exit-code +' + +test_done diff --git a/tree-diff.c b/tree-diff.c index 0459e54d3d..7c526d33f4 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -286,7 +286,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru int baselen = strlen(base); for (;;) { - if (DIFF_OPT_TST(opt, QUIET) && DIFF_OPT_TST(opt, HAS_CHANGES)) + if (DIFF_OPT_TST(opt, QUIET) && + DIFF_OPT_TST(opt, HAS_CHANGES)) break; if (opt->nr_paths) { skip_uninteresting(t1, base, baselen, opt); From 90b1994170900514a1ce7a3345e25cb7216915cc Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 23 May 2009 01:15:35 -0700 Subject: [PATCH 007/884] diff: Rename QUIET internal option to QUICK The option "QUIET" primarily meant "find if we have _any_ difference as quick as possible and report", which means we often do not even have to look at blobs if we know the trees are different by looking at the higher level (e.g. "diff-tree A B"). As a side effect, because there is no point showing one change that we happened to have found first, it also enables NO_OUTPUT and EXIT_WITH_STATUS options, making the end result look quiet. Rename the internal option to QUICK to reflect this better; it also makes grepping the source tree much easier, as there are other kinds of QUIET option everywhere. Signed-off-by: Junio C Hamano --- builtin-log.c | 2 +- builtin-rev-list.c | 2 +- diff-lib.c | 4 ++-- diff.c | 4 ++-- diff.h | 2 +- revision.c | 2 +- tree-diff.c | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/builtin-log.c b/builtin-log.c index 0c2fa0ae2d..7903e5a78f 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -537,7 +537,7 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev) get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename); - if (!DIFF_OPT_TST(&rev->diffopt, QUIET)) + if (!DIFF_OPT_TST(&rev->diffopt, QUICK)) fprintf(realstdout, "%s\n", filename.buf + outdir_offset); if (freopen(filename.buf, "w", stdout) == NULL) diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 4ba1c12e0b..69753dc206 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -320,7 +320,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) memset(&info, 0, sizeof(info)); info.revs = &revs; - quiet = DIFF_OPT_TST(&revs.diffopt, QUIET); + quiet = DIFF_OPT_TST(&revs.diffopt, QUICK); for (i = 1 ; i < argc; i++) { const char *arg = argv[i]; diff --git a/diff-lib.c b/diff-lib.c index ad2a4cde74..b7813af614 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -73,7 +73,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) struct cache_entry *ce = active_cache[i]; int changed; - if (DIFF_OPT_TST(&revs->diffopt, QUIET) && + if (DIFF_OPT_TST(&revs->diffopt, QUICK) && DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) break; @@ -523,7 +523,7 @@ int index_differs_from(const char *def, int diff_flags) init_revisions(&rev, NULL); setup_revisions(0, NULL, &rev, def); - DIFF_OPT_SET(&rev.diffopt, QUIET); + DIFF_OPT_SET(&rev.diffopt, QUICK); DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); rev.diffopt.flags |= diff_flags; run_diff_index(&rev, 1); diff --git a/diff.c b/diff.c index 467925d931..91d6ea21a9 100644 --- a/diff.c +++ b/diff.c @@ -2452,7 +2452,7 @@ int diff_setup_done(struct diff_options *options) * to have found. It does not make sense not to return with * exit code in such a case either. */ - if (DIFF_OPT_TST(options, QUIET)) { + if (DIFF_OPT_TST(options, QUICK)) { options->output_format = DIFF_FORMAT_NO_OUTPUT; DIFF_OPT_SET(options, EXIT_WITH_STATUS); } @@ -2643,7 +2643,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--exit-code")) DIFF_OPT_SET(options, EXIT_WITH_STATUS); else if (!strcmp(arg, "--quiet")) - DIFF_OPT_SET(options, QUIET); + DIFF_OPT_SET(options, QUICK); else if (!strcmp(arg, "--ext-diff")) DIFF_OPT_SET(options, ALLOW_EXTERNAL); else if (!strcmp(arg, "--no-ext-diff")) diff --git a/diff.h b/diff.h index 538e4f0d8f..a7e7ccbd42 100644 --- a/diff.h +++ b/diff.h @@ -55,7 +55,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_COLOR_DIFF (1 << 8) #define DIFF_OPT_COLOR_DIFF_WORDS (1 << 9) #define DIFF_OPT_HAS_CHANGES (1 << 10) -#define DIFF_OPT_QUIET (1 << 11) +#define DIFF_OPT_QUICK (1 << 11) #define DIFF_OPT_NO_INDEX (1 << 12) #define DIFF_OPT_ALLOW_EXTERNAL (1 << 13) #define DIFF_OPT_EXIT_WITH_STATUS (1 << 14) diff --git a/revision.c b/revision.c index 9f5dac5f1d..b8afc7c2b5 100644 --- a/revision.c +++ b/revision.c @@ -791,7 +791,7 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->ignore_merges = 1; revs->simplify_history = 1; DIFF_OPT_SET(&revs->pruning, RECURSIVE); - DIFF_OPT_SET(&revs->pruning, QUIET); + DIFF_OPT_SET(&revs->pruning, QUICK); revs->pruning.add_remove = file_add_remove; revs->pruning.change = file_change; revs->lifo = 1; diff --git a/tree-diff.c b/tree-diff.c index 7c526d33f4..7d745b4406 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -286,7 +286,7 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru int baselen = strlen(base); for (;;) { - if (DIFF_OPT_TST(opt, QUIET) && + if (DIFF_OPT_TST(opt, QUICK) && DIFF_OPT_TST(opt, HAS_CHANGES)) break; if (opt->nr_paths) { From 17635fc900674037bcaa9ca0fadcc5c23d6162e9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 15 Jul 2009 15:31:12 -0700 Subject: [PATCH 008/884] mailinfo: -b option keeps [bracketed] strings that is not a [PATCH] marker By default, we remove leading [bracketed] [strings] from the Subject: header when coming up with the summary of the patch. This is because there are mailing lists etc that add their own headers to the subject, and they know they can add things in brackets. The most obvious example is the Linux kernel security list. Their emails look like Subject: [Security] [patch] random: make get_random_int() more random and other people mangle Subject: themselves in a similar way, e.g.: Subject: [PATCH -rc] [BUGFIX] x86: fix kernel_trap_sp() Subject: [BUGFIX][PATCH] fix bad page removal from LRU (Was Re: [RFC][PATCH] .. even though "fix" is more than enough cue to mark it as a [BUGFIX]. Some projects however want to keep these bracketed strings. With this option, we remove only [bracketed strings that contain word PATCH], so we will turn things like these [PATCH] [mailinfo] -b ... [PATCH v2] [mailinfo] -b ... [PATCH (v2) 1/4] [mailinfo] -b ... into [mailinfo] -b ... This lacks tests and integration to the "git am" toolchain to be useful, but it is a start. Signed-off-by: Junio C Hamano --- Documentation/git-mailinfo.txt | 7 ++++- builtin-mailinfo.c | 49 ++++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index 8d95aaa304..d800aea0c1 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -8,7 +8,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message SYNOPSIS -------- -'git mailinfo' [-k] [-u | --encoding= | -n] +'git mailinfo' [-k|-b] [-u | --encoding= | -n] DESCRIPTION @@ -32,6 +32,11 @@ OPTIONS munging, and is most useful when used to read back 'git-format-patch -k' output. +-b:: + When -k is not in effect, all leading strings bracketed with '[' + and ']' pairs are stripped. This option limits the stripping to + only the pairs whose bracketed string contains the word "PATCH". + -u:: The commit log message, author name and author email are taken from the e-mail, and after minimally decoding MIME diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 92637ac0ba..a5949e2c9a 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -10,6 +10,7 @@ static FILE *cmitmsg, *patchfile, *fin, *fout; static int keep_subject; +static int keep_non_patch_brackets_in_subject; static const char *metainfo_charset; static struct strbuf line = STRBUF_INIT; static struct strbuf name = STRBUF_INIT; @@ -219,35 +220,41 @@ static int is_multipart_boundary(const struct strbuf *line) static void cleanup_subject(struct strbuf *subject) { - char *pos; - size_t remove; - while (subject->len) { - switch (*subject->buf) { + size_t at = 0; + + while (at < subject->len) { + char *pos; + size_t remove; + + switch (subject->buf[at]) { case 'r': case 'R': - if (subject->len <= 3) + if (subject->len <= at + 3) break; - if (!memcmp(subject->buf + 1, "e:", 2)) { - strbuf_remove(subject, 0, 3); + if (!memcmp(subject->buf + at + 1, "e:", 2)) { + strbuf_remove(subject, at, 3); continue; } + at++; break; case ' ': case '\t': case ':': - strbuf_remove(subject, 0, 1); + strbuf_remove(subject, at, 1); continue; case '[': - if ((pos = strchr(subject->buf, ']'))) { - remove = pos - subject->buf; - if (remove <= (subject->len - remove) * 2) { - strbuf_remove(subject, 0, remove + 1); - continue; - } - } else - strbuf_remove(subject, 0, 1); - break; + pos = strchr(subject->buf + at, ']'); + if (!pos) + break; + remove = pos - subject->buf + at + 1; + if (!keep_non_patch_brackets_in_subject || + (7 <= remove && + memmem(subject->buf + at, remove, "PATCH", 5))) + strbuf_remove(subject, at, remove); + else + at += remove; + continue; } - strbuf_trim(subject); - return; + break; } + strbuf_trim(subject); } static void cleanup_space(struct strbuf *sb) @@ -931,7 +938,7 @@ static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, } static const char mailinfo_usage[] = - "git mailinfo [-k] [-u | --encoding= | -n] msg patch info"; + "git mailinfo [-k|-b] [-u | --encoding= | -n] msg patch info"; int cmd_mailinfo(int argc, const char **argv, const char *prefix) { @@ -948,6 +955,8 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) while (1 < argc && argv[1][0] == '-') { if (!strcmp(argv[1], "-k")) keep_subject = 1; + else if (!strcmp(argv[1], "-b")) + keep_non_patch_brackets_in_subject = 1; else if (!strcmp(argv[1], "-u")) metainfo_charset = def_charset; else if (!strcmp(argv[1], "-n")) From 76e2f7ce323c7ddb3a7c29e56407da6a69a4fa53 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 7 Aug 2009 23:31:57 -0700 Subject: [PATCH 009/884] git stat: the beginning of "status that is not a dry-run of commit" Tentatively add "git stat" as a new command. This is not "preview of commit with the same arguments"; the path parameters are not paths to be added to the pristine index (aka "--only" option), but are taken as pathspecs to limit the output. Later in 1.7.0 release, it will take over "git status". Signed-off-by: Junio C Hamano --- Makefile | 1 + builtin-commit.c | 75 +++++++++++++++++++++++++++++++++++++++++------- builtin.h | 1 + git.c | 1 + wt-status.c | 10 ++++--- wt-status.h | 1 + 6 files changed, 75 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index daf4296706..39dd334385 100644 --- a/Makefile +++ b/Makefile @@ -378,6 +378,7 @@ BUILT_INS += git-init$X BUILT_INS += git-merge-subtree$X BUILT_INS += git-peek-remote$X BUILT_INS += git-repo-config$X +BUILT_INS += git-stat$X BUILT_INS += git-show$X BUILT_INS += git-stage$X BUILT_INS += git-status$X diff --git a/builtin-commit.c b/builtin-commit.c index 200ffdaad4..5e23ef1f0a 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -24,6 +24,7 @@ #include "string-list.h" #include "rerere.h" #include "unpack-trees.h" +#include "quote.h" static const char * const builtin_commit_usage[] = { "git commit [options] [--] ...", @@ -35,6 +36,11 @@ static const char * const builtin_status_usage[] = { NULL }; +static const char * const builtin_stat_usage[] = { + "git stat [options]", + NULL +}; + static unsigned char head_sha1[20], merge_head_sha1[20]; static char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; @@ -346,6 +352,8 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, struct wt_status *s) { + unsigned char sha1[20]; + if (s->relative_paths) s->prefix = prefix; @@ -357,7 +365,9 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->index_file = index_file; s->fp = fp; s->nowarn = nowarn; + s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; + wt_status_collect(s); wt_status_print(s); return s->commitable; @@ -691,6 +701,21 @@ static const char *find_author_by_nickname(const char *name) die("No existing author found with '%s'", name); } + +static void handle_untracked_files_arg(struct wt_status *s) +{ + if (!untracked_files_arg) + ; /* default already initialized */ + else if (!strcmp(untracked_files_arg, "no")) + s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; + else if (!strcmp(untracked_files_arg, "normal")) + s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; + else if (!strcmp(untracked_files_arg, "all")) + s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; + else + die("Invalid untracked files mode '%s'", untracked_files_arg); +} + static int parse_and_validate_options(int argc, const char *argv[], const char * const usage[], const char *prefix, @@ -794,16 +819,7 @@ static int parse_and_validate_options(int argc, const char *argv[], else die("Invalid cleanup mode %s", cleanup_arg); - if (!untracked_files_arg) - ; /* default already initialized */ - else if (!strcmp(untracked_files_arg, "no")) - s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; - else if (!strcmp(untracked_files_arg, "normal")) - s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; - else if (!strcmp(untracked_files_arg, "all")) - s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; - else - die("Invalid untracked files mode '%s'", untracked_files_arg); + handle_untracked_files_arg(s); if (all && argc > 0) die("Paths with -a does not make sense."); @@ -886,6 +902,45 @@ static int git_status_config(const char *k, const char *v, void *cb) return git_diff_ui_config(k, v, NULL); } +int cmd_stat(int argc, const char **argv, const char *prefix) +{ + struct wt_status s; + unsigned char sha1[20]; + static struct option builtin_stat_options[] = { + OPT__VERBOSE(&verbose), + { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, + "mode", + "show untracked files, optional modes: all, normal, no. (Default: all)", + PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + OPT_END(), + }; + + wt_status_prepare(&s); + git_config(git_status_config, &s); + argc = parse_options(argc, argv, prefix, + builtin_stat_options, + builtin_stat_usage, 0); + handle_untracked_files_arg(&s); + + if (*argv) + s.pathspec = get_pathspec(prefix, argv); + + read_cache(); + refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED); + s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; + wt_status_collect(&s); + + s.verbose = verbose; + if (s.relative_paths) + s.prefix = prefix; + if (s.use_color == -1) + s.use_color = git_use_color_default; + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; + wt_status_print(&s); + return 0; +} + int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; diff --git a/builtin.h b/builtin.h index 20427d2963..eeaf0b6cf5 100644 --- a/builtin.h +++ b/builtin.h @@ -95,6 +95,7 @@ extern int cmd_send_pack(int argc, const char **argv, const char *prefix); extern int cmd_shortlog(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); +extern int cmd_stat(int argc, const char **argv, const char *prefix); extern int cmd_status(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index 807d875ae0..de7fcf6dfb 100644 --- a/git.c +++ b/git.c @@ -350,6 +350,7 @@ static void handle_internal_command(int argc, const char **argv) { "shortlog", cmd_shortlog, USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, + { "stat", cmd_stat, RUN_SETUP | NEED_WORK_TREE }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, diff --git a/wt-status.c b/wt-status.c index 63598ce40c..249227c382 100644 --- a/wt-status.c +++ b/wt-status.c @@ -269,6 +269,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = wt_status_collect_changed_cb; rev.diffopt.format_callback_data = s; + rev.prune_data = s->pathspec; run_diff_files(&rev, 0); } @@ -285,6 +286,7 @@ static void wt_status_collect_changes_index(struct wt_status *s) rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 200; rev.diffopt.break_opt = 0; + rev.prune_data = s->pathspec; run_diff_index(&rev, 1); } @@ -297,6 +299,8 @@ static void wt_status_collect_changes_initial(struct wt_status *s) struct wt_status_change_data *d; struct cache_entry *ce = active_cache[i]; + if (!ce_path_match(ce, s->pathspec)) + continue; it = string_list_insert(ce->name, &s->change); d = it->util; if (!d) { @@ -330,6 +334,8 @@ static void wt_status_collect_untracked(struct wt_status *s) struct dir_entry *ent = dir.entries[i]; if (!cache_name_is_other(ent->name, ent->len)) continue; + if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL)) + continue; s->workdir_untracked = 1; string_list_insert(ent->name, &s->untracked); } @@ -533,10 +539,8 @@ static void wt_status_print_tracking(struct wt_status *s) void wt_status_print(struct wt_status *s) { - unsigned char sha1[20]; const char *branch_color = color(WT_STATUS_HEADER, s); - s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; if (s->branch) { const char *on_what = "On branch "; const char *branch_name = s->branch; @@ -553,8 +557,6 @@ void wt_status_print(struct wt_status *s) wt_status_print_tracking(s); } - wt_status_collect(s); - if (s->is_initial) { color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit"); diff --git a/wt-status.h b/wt-status.h index a0e75177be..09fd9f1091 100644 --- a/wt-status.h +++ b/wt-status.h @@ -31,6 +31,7 @@ struct wt_status { int is_initial; char *branch; const char *reference; + const char **pathspec; int verbose; int amend; int nowarn; From 173e6c8852be3c543689f8613ddf3fdafd9ca7b9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 4 Aug 2009 23:55:22 -0700 Subject: [PATCH 010/884] git stat -s: short status output Give -s(hort) option to "git stat" that shows the status of paths in a more concise way. XY PATH1 -> PATH2 format to be more machine readable than output from "git status", which is about previewing of "git commit" with the same arguments. PATH1 is the path in the HEAD, and " -> PATH2" part is shown only when PATH1 corresponds to a different path in the index/worktree. For unmerged entries, X shows the status of stage #2 (i.e. ours) and Y shows the status of stage #3 (i.e. theirs). For entries that do not have conflicts, X shows the status of the index, and Y shows the status of the work tree. For untracked paths, XY are "??". X Y Meaning ------------------------------------------------- [MD] not updated M [ MD] updated in index A [ MD] added to index D [ MD] deleted from index R [ MD] renamed in index C [ MD] copied in index [MARC] index and work tree matches [ MARC] M work tree changed since index [ MARC] D deleted in work tree D D unmerged, both deleted A U unmerged, added by us U D unmerged, deleted by them U A unmerged, added by them D U unmerged, deleted by us A A unmerged, both added U U unmerged, both modified ? ? untracked When given -z option, the records are terminated by NUL characters for better machine readability. Because the traditional long format is designed for human consumption, NUL termination does not make sense. For this reason, -z option implies -s (short output). Signed-off-by: Junio C Hamano --- builtin-commit.c | 114 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 106 insertions(+), 8 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 5e23ef1f0a..1a360cb37c 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -902,12 +902,87 @@ static int git_status_config(const char *k, const char *v, void *cb) return git_diff_ui_config(k, v, NULL); } +#define quote_path quote_path_relative + +static void short_unmerged(int null_termination, struct string_list_item *it, + struct wt_status *s) +{ + struct wt_status_change_data *d = it->util; + const char *how = "??"; + + switch (d->stagemask) { + case 1: how = "DD"; break; /* both deleted */ + case 2: how = "AU"; break; /* added by us */ + case 3: how = "UD"; break; /* deleted by them */ + case 4: how = "UA"; break; /* added by them */ + case 5: how = "DU"; break; /* deleted by us */ + case 6: how = "AA"; break; /* both added */ + case 7: how = "UU"; break; /* both modified */ + } + printf("%s ", how); + if (null_termination) { + fprintf(stdout, "%s%c", it->string, 0); + } else { + struct strbuf onebuf = STRBUF_INIT; + const char *one; + one = quote_path(it->string, -1, &onebuf, s->prefix); + printf("%s\n", one); + strbuf_release(&onebuf); + } +} + +static void short_status(int null_termination, struct string_list_item *it, + struct wt_status *s) +{ + struct wt_status_change_data *d = it->util; + + printf("%c%c ", + !d->index_status ? ' ' : d->index_status, + !d->worktree_status ? ' ' : d->worktree_status); + if (null_termination) { + fprintf(stdout, "%s%c", it->string, 0); + if (d->head_path) + fprintf(stdout, "%s%c", d->head_path, 0); + } else { + struct strbuf onebuf = STRBUF_INIT; + const char *one; + if (d->head_path) { + one = quote_path(d->head_path, -1, &onebuf, s->prefix); + printf("%s -> ", one); + strbuf_release(&onebuf); + } + one = quote_path(it->string, -1, &onebuf, s->prefix); + printf("%s\n", one); + strbuf_release(&onebuf); + } +} + +static void short_untracked(int null_termination, struct string_list_item *it, + struct wt_status *s) +{ + if (null_termination) { + fprintf(stdout, "?? %s%c", it->string, 0); + } else { + struct strbuf onebuf = STRBUF_INIT; + const char *one; + one = quote_path(it->string, -1, &onebuf, s->prefix); + printf("?? %s\n", one); + strbuf_release(&onebuf); + } +} + int cmd_stat(int argc, const char **argv, const char *prefix) { struct wt_status s; + static int null_termination, shortstatus; + int i; unsigned char sha1[20]; static struct option builtin_stat_options[] = { OPT__VERBOSE(&verbose), + OPT_BOOLEAN('s', "short", &shortstatus, + "show status concicely"), + OPT_BOOLEAN('z', "null", &null_termination, + "terminate entries with NUL"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", @@ -915,6 +990,9 @@ int cmd_stat(int argc, const char **argv, const char *prefix) OPT_END(), }; + if (null_termination) + shortstatus = 1; + wt_status_prepare(&s); git_config(git_status_config, &s); argc = parse_options(argc, argv, prefix, @@ -930,14 +1008,34 @@ int cmd_stat(int argc, const char **argv, const char *prefix) s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; wt_status_collect(&s); - s.verbose = verbose; - if (s.relative_paths) - s.prefix = prefix; - if (s.use_color == -1) - s.use_color = git_use_color_default; - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - wt_status_print(&s); + if (shortstatus) { + for (i = 0; i < s.change.nr; i++) { + struct wt_status_change_data *d; + struct string_list_item *it; + + it = &(s.change.items[i]); + d = it->util; + if (d->stagemask) + short_unmerged(null_termination, it, &s); + else + short_status(null_termination, it, &s); + } + for (i = 0; i < s.untracked.nr; i++) { + struct string_list_item *it; + + it = &(s.untracked.items[i]); + short_untracked(null_termination, it, &s); + } + } else { + s.verbose = verbose; + if (s.relative_paths) + s.prefix = prefix; + if (s.use_color == -1) + s.use_color = git_use_color_default; + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; + wt_status_print(&s); + } return 0; } From 9e4b7ab652561e1807702fe5288e04b8873fc437 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 15 Aug 2009 02:27:39 -0700 Subject: [PATCH 011/884] git status: not "commit --dry-run" anymore This removes tentative "git stat" and make it take over "git status". There are some tests that expect "git status" to exit with non-zero status when there is something staged. Some tests expect "git status path..." to show the status for a partial commit. For these, replace "git status" with "git commit --dry-run". For the ones that do not attempt a dry-run of a partial commit that check the output from the command, check the output from "git status" as well, as they should be identical. Signed-off-by: Junio C Hamano --- Documentation/git-status.txt | 77 +++++++++++++++++++++++++++++++----- Makefile | 1 - builtin-commit.c | 29 ++------------ builtin.h | 1 - git.c | 1 - t/t6040-tracking-info.sh | 2 +- t/t7060-wtstatus.sh | 8 ++-- t/t7506-status-submodule.sh | 6 +-- t/t7508-status.sh | 12 +++--- 9 files changed, 88 insertions(+), 49 deletions(-) diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 84f60f3407..b5939d6b58 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -8,7 +8,7 @@ git-status - Show the working tree status SYNOPSIS -------- -'git status' ... +'git status' [...] [--] [...] DESCRIPTION ----------- @@ -20,25 +20,85 @@ are what you _would_ commit by running `git commit`; the second and third are what you _could_ commit by running 'git-add' before running `git commit`. -The command takes the same set of options as 'git-commit'; it -shows what would be committed if the same options are given to -'git-commit'. +OPTIONS +------- -If there is no path that is different between the index file and -the current HEAD commit (i.e., there is nothing to commit by running -`git commit`), the command exits with non-zero status. +-s:: +--short:: + Give the output in the short-format. + +-u[]:: +--untracked-files[=]:: + Show untracked files (Default: 'all'). ++ +The mode parameter is optional, and is used to specify +the handling of untracked files. The possible options are: ++ +-- + - 'no' - Show no untracked files + - 'normal' - Shows untracked files and directories + - 'all' - Also shows individual files in untracked directories. +-- ++ +See linkgit:git-config[1] for configuration variable +used to change the default for when the option is not +specified. + +-z:: + Terminate entries with NUL, instead of LF. This implies `-s` + (short status) output format. OUTPUT ------ The output from this command is designed to be used as a commit template comment, and all the output lines are prefixed with '#'. +The default, long format, is designed to be human readable, +verbose and descriptive. They are subject to change in any time. The paths mentioned in the output, unlike many other git commands, are made relative to the current directory if you are working in a subdirectory (this is on purpose, to help cutting and pasting). See the status.relativePaths config option below. +In short-format, the status of each path is shown as + + XY PATH1 -> PATH2 + +where `PATH1` is the path in the `HEAD`, and ` -> PATH2` part is +shown only when `PATH1` corresponds to a different path in the +index/worktree (i.e. renamed). + +For unmerged entries, `X` shows the status of stage #2 (i.e. ours) and `Y` +shows the status of stage #3 (i.e. theirs). + +For entries that do not have conflicts, `X` shows the status of the index, +and `Y` shows the status of the work tree. For untracked paths, `XY` are +`??`. + + X Y Meaning + ------------------------------------------------- + [MD] not updated + M [ MD] updated in index + A [ MD] added to index + D [ MD] deleted from index + R [ MD] renamed in index + C [ MD] copied in index + [MARC] index and work tree matches + [ MARC] M work tree changed since index + [ MARC] D deleted in work tree + ------------------------------------------------- + D D unmerged, both deleted + A U unmerged, added by us + U D unmerged, deleted by them + U A unmerged, added by them + D U unmerged, deleted by us + A A unmerged, both added + U U unmerged, both modified + ------------------------------------------------- + ? ? untracked + ------------------------------------------------- + CONFIGURATION ------------- @@ -63,8 +123,7 @@ linkgit:gitignore[5] Author ------ -Written by Linus Torvalds and -Junio C Hamano . +Written by Junio C Hamano . Documentation -------------- diff --git a/Makefile b/Makefile index 39dd334385..daf4296706 100644 --- a/Makefile +++ b/Makefile @@ -378,7 +378,6 @@ BUILT_INS += git-init$X BUILT_INS += git-merge-subtree$X BUILT_INS += git-peek-remote$X BUILT_INS += git-repo-config$X -BUILT_INS += git-stat$X BUILT_INS += git-show$X BUILT_INS += git-stage$X BUILT_INS += git-status$X diff --git a/builtin-commit.c b/builtin-commit.c index 1a360cb37c..6cb0e40484 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -36,11 +36,6 @@ static const char * const builtin_status_usage[] = { NULL }; -static const char * const builtin_stat_usage[] = { - "git stat [options]", - NULL -}; - static unsigned char head_sha1[20], merge_head_sha1[20]; static char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; @@ -971,13 +966,13 @@ static void short_untracked(int null_termination, struct string_list_item *it, } } -int cmd_stat(int argc, const char **argv, const char *prefix) +int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; static int null_termination, shortstatus; int i; unsigned char sha1[20]; - static struct option builtin_stat_options[] = { + static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), OPT_BOOLEAN('s', "short", &shortstatus, "show status concicely"), @@ -996,8 +991,8 @@ int cmd_stat(int argc, const char **argv, const char *prefix) wt_status_prepare(&s); git_config(git_status_config, &s); argc = parse_options(argc, argv, prefix, - builtin_stat_options, - builtin_stat_usage, 0); + builtin_status_options, + builtin_status_usage, 0); handle_untracked_files_arg(&s); if (*argv) @@ -1039,22 +1034,6 @@ int cmd_stat(int argc, const char **argv, const char *prefix) return 0; } -int cmd_status(int argc, const char **argv, const char *prefix) -{ - struct wt_status s; - - wt_status_prepare(&s); - git_config(git_status_config, &s); - if (s.use_color == -1) - s.use_color = git_use_color_default; - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - argc = parse_and_validate_options(argc, argv, builtin_status_usage, - prefix, &s); - return dry_run_commit(argc, argv, prefix, &s); -} - static void print_summary(const char *prefix, const unsigned char *sha1) { struct rev_info rev; diff --git a/builtin.h b/builtin.h index eeaf0b6cf5..20427d2963 100644 --- a/builtin.h +++ b/builtin.h @@ -95,7 +95,6 @@ extern int cmd_send_pack(int argc, const char **argv, const char *prefix); extern int cmd_shortlog(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); -extern int cmd_stat(int argc, const char **argv, const char *prefix); extern int cmd_status(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index de7fcf6dfb..807d875ae0 100644 --- a/git.c +++ b/git.c @@ -350,7 +350,6 @@ static void handle_internal_command(int argc, const char **argv) { "shortlog", cmd_shortlog, USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, - { "stat", cmd_stat, RUN_SETUP | NEED_WORK_TREE }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index 00e1de9627..664b0f8052 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -69,7 +69,7 @@ test_expect_success 'status' ' cd test && git checkout b1 >/dev/null && # reports nothing to commit - test_must_fail git status + test_must_fail git commit --dry-run ) >actual && grep "have 1 and 1 different" actual ' diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh index 1044aa6549..7b5db8066f 100755 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh @@ -50,9 +50,11 @@ test_expect_success 'M/D conflict does not segfault' ' git rm foo && git commit -m delete && test_must_fail git merge master && - test_must_fail git status > ../actual - ) && - test_cmp expect actual + test_must_fail git commit --dry-run >../actual && + test_cmp ../expect ../actual && + git status >../actual && + test_cmp ../expect ../actual + ) ' test_done diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index d9a08aac56..3ca17abad1 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -19,8 +19,8 @@ test_expect_success 'status clean' ' git status | grep "nothing to commit" ' -test_expect_success 'status -a clean' ' - git status -a | +test_expect_success 'commit --dry-run -a clean' ' + git commit --dry-run -a | grep "nothing to commit" ' test_expect_success 'rm submodule contents' ' @@ -31,7 +31,7 @@ test_expect_success 'status clean (empty submodule dir)' ' grep "nothing to commit" ' test_expect_success 'status -a clean (empty submodule dir)' ' - git status -a | + git commit --dry-run -a | grep "nothing to commit" ' diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 93f875f500..1173dbb36e 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -248,8 +248,8 @@ cat <expect # output # untracked EOF -test_expect_success 'status of partial commit excluding new file in index' ' - git status dir1/modified >output && +test_expect_success 'dry-run of partial commit excluding new file in index' ' + git commit --dry-run dir1/modified >output && test_cmp expect output ' @@ -358,7 +358,9 @@ EOF test_expect_success 'status submodule summary (clean submodule)' ' git commit -m "commit submodule" && git config status.submodulesummary 10 && - test_must_fail git status >output && + test_must_fail git commit --dry-run >output && + test_cmp expect output && + git status >output && test_cmp expect output ' @@ -391,9 +393,9 @@ cat >expect <output && + git commit --dry-run --amend >output && test_cmp expect output ' From 41fe87fa49cbeec8f05c3e51d6ffd9c57f6c754c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 22 Aug 2009 12:48:48 -0700 Subject: [PATCH 012/884] send-email: make --no-chain-reply-to the default In http://article.gmane.org/gmane.comp.version-control.git/109790 I threatened to announce a change to the default threading style used by send-email to no-chain-reply-to (i.e. the second and subsequent messages will all be replies to the first one), unless nobody objected, in 1.6.3. Nobody objected, as far as I can dig the list archive. But when nothing happened in 1.6.3 nor 1.6.4, nobody from the camp who complained loudly that led to the message did not complain either. So I am guessing that after all nobody cares about this. But 1.7.0 is a good time to change this, and as I said in the message, I personally think it is a good change, so here it is. Signed-off-by: Junio C Hamano --- Documentation/git-send-email.txt | 6 +++--- git-send-email.perl | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 767cf4d4bd..626c2dccc1 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -84,7 +84,7 @@ See the CONFIGURATION section for 'sendemail.multiedit'. --in-reply-to=:: Specify the contents of the first In-Reply-To header. Subsequent emails will refer to the previous email - instead of this if --chain-reply-to is set (the default) + instead of this if --chain-reply-to is set. Only necessary if --compose is also set. If --compose is not set, this will be prompted for. @@ -171,8 +171,8 @@ Automating email sent. If disabled with "--no-chain-reply-to", all emails after the first will be sent as replies to the first email sent. When using this, it is recommended that the first file given be an overview of the - entire patch series. Default is the value of the 'sendemail.chainreplyto' - configuration value; if that is unspecified, default to --chain-reply-to. + entire patch series. Disabled by default, but the 'sendemail.chainreplyto' + configuration variable can be used to enable it. --identity=:: A configuration identity. When given, causes values in the diff --git a/git-send-email.perl b/git-send-email.perl index 0700d80afc..c1d093033d 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -71,7 +71,7 @@ git send-email [options] --suppress-cc * author, self, sob, cc, cccmd, body, bodycc, all. --[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on. --[no-]suppress-from * Send to self. Default off. - --[no-]chain-reply-to * Chain In-Reply-To: fields. Default on. + --[no-]chain-reply-to * Chain In-Reply-To: fields. Default off. --[no-]thread * Use In-Reply-To: field. Default on. Administering: @@ -188,7 +188,7 @@ my (@suppress_cc); my %config_bool_settings = ( "thread" => [\$thread, 1], - "chainreplyto" => [\$chain_reply_to, 1], + "chainreplyto" => [\$chain_reply_to, undef], "suppressfrom" => [\$suppress_from, undef], "signedoffbycc" => [\$signed_off_by_cc, undef], "signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated From 83b327ba4ec6d29fd59e343b734f642d266aeafc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:46:55 +0700 Subject: [PATCH 013/884] update-index: refactor mark_valid() in preparation for new options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin-update-index.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/builtin-update-index.c b/builtin-update-index.c index 92beaaf4b3..f1b6c8e88e 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -24,8 +24,8 @@ static int info_only; static int force_remove; static int verbose; static int mark_valid_only; -#define MARK_VALID 1 -#define UNMARK_VALID 2 +#define MARK_FLAG 1 +#define UNMARK_FLAG 2 static void report(const char *fmt, ...) { @@ -40,19 +40,15 @@ static void report(const char *fmt, ...) va_end(vp); } -static int mark_valid(const char *path) +static int mark_ce_flags(const char *path, int flag, int mark) { int namelen = strlen(path); int pos = cache_name_pos(path, namelen); if (0 <= pos) { - switch (mark_valid_only) { - case MARK_VALID: - active_cache[pos]->ce_flags |= CE_VALID; - break; - case UNMARK_VALID: - active_cache[pos]->ce_flags &= ~CE_VALID; - break; - } + if (mark) + active_cache[pos]->ce_flags |= flag; + else + active_cache[pos]->ce_flags &= ~flag; cache_tree_invalidate_path(active_cache_tree, path); active_cache_changed = 1; return 0; @@ -276,7 +272,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length) goto free_return; } if (mark_valid_only) { - if (mark_valid(p)) + if (mark_ce_flags(p, CE_VALID, mark_valid_only == MARK_FLAG)) die("Unable to mark file %s", path); goto free_return; } @@ -647,11 +643,11 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(path, "--assume-unchanged")) { - mark_valid_only = MARK_VALID; + mark_valid_only = MARK_FLAG; continue; } if (!strcmp(path, "--no-assume-unchanged")) { - mark_valid_only = UNMARK_VALID; + mark_valid_only = UNMARK_FLAG; continue; } if (!strcmp(path, "--info-only")) { From dbd57f99680eac33626d5058459efd7f118f5170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:46:56 +0700 Subject: [PATCH 014/884] Add test-index-version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 06aaaa0bf70fe37d198893f4e25fa73b6516f8a9 may step index format version up and down, depends on whether extended flags present in the index. This adds a test to check for index format version. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 1 + test-index-version.c | 14 ++++++++++++++ 3 files changed, 16 insertions(+) create mode 100644 test-index-version.c diff --git a/.gitignore b/.gitignore index 41c0b20a76..e3a864c0ad 100644 --- a/.gitignore +++ b/.gitignore @@ -153,6 +153,7 @@ test-date test-delta test-dump-cache-tree test-genrandom +test-index-version test-match-trees test-parse-options test-path-utils diff --git a/Makefile b/Makefile index daf4296706..3c5b890223 100644 --- a/Makefile +++ b/Makefile @@ -1580,6 +1580,7 @@ TEST_PROGRAMS += test-parse-options$X TEST_PROGRAMS += test-path-utils$X TEST_PROGRAMS += test-sha1$X TEST_PROGRAMS += test-sigchain$X +TEST_PROGRAMS += test-index-version$X all:: $(TEST_PROGRAMS) diff --git a/test-index-version.c b/test-index-version.c new file mode 100644 index 0000000000..bfaad9e09e --- /dev/null +++ b/test-index-version.c @@ -0,0 +1,14 @@ +#include "cache.h" + +int main(int argc, const char **argv) +{ + struct cache_header hdr; + int version; + + memset(&hdr,0,sizeof(hdr)); + if (read(0, &hdr, sizeof(hdr)) != sizeof(hdr)) + return 0; + version = ntohl(hdr.hdr_version); + printf("%d\n", version); + return 0; +} From 44a3691362dc71241a5d68d90b07642c46992e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:46:57 +0700 Subject: [PATCH 015/884] Introduce "skip-worktree" bit in index, teach Git to get/set this bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Detail about this bit is in Documentation/git-update-index.txt. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-ls-files.txt | 1 + Documentation/git-update-index.txt | 29 ++++++++++++++ builtin-ls-files.c | 5 ++- builtin-update-index.c | 16 +++++++- cache.h | 4 +- t/t2104-update-index-skip-worktree.sh | 57 +++++++++++++++++++++++++++ 6 files changed, 109 insertions(+), 3 deletions(-) create mode 100755 t/t2104-update-index-skip-worktree.sh diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 021066e95d..6f9d880aa4 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -107,6 +107,7 @@ OPTIONS Identify the file status with the following tags (followed by a space) at the start of each line: H:: cached + S:: skip-worktree M:: unmerged R:: removed/deleted C:: modified/changed diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 25e0bbea86..a10f355b7c 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -15,6 +15,7 @@ SYNOPSIS [--cacheinfo ]\* [--chmod=(+|-)x] [--assume-unchanged | --no-assume-unchanged] + [--skip-worktree | --no-skip-worktree] [--ignore-submodules] [--really-refresh] [--unresolve] [--again | -g] [--info-only] [--index-info] @@ -99,6 +100,13 @@ in the index e.g. when merging in a commit; thus, in case the assumed-untracked file is changed upstream, you will need to handle the situation manually. +--skip-worktree:: +--no-skip-worktree:: + When one of these flags is specified, the object name recorded + for the paths are not updated. Instead, these options + set and unset the "skip-worktree" bit for the paths. See + section "Skip-worktree bit" below for more information. + -g:: --again:: Runs 'git-update-index' itself on the paths whose index @@ -304,6 +312,27 @@ M foo.c <9> now it checks with lstat(2) and finds it has been changed. +Skip-worktree bit +----------------- + +Skip-worktree bit can be defined in one (long) sentence: When reading +an entry, if it is marked as skip-worktree, then Git pretends its +working directory version is up to date and read the index version +instead. + +To elaborate, "reading" means checking for file existence, reading +file attributes or file content. The working directory version may be +present or absent. If present, its content may match against the index +version or not. Writing is not affected by this bit, content safety +is still first priority. Note that Git _can_ update working directory +file, that is marked skip-worktree, if it is safe to do so (i.e. +working directory version matches index version) + +Although this bit looks similar to assume-unchanged bit, its goal is +different from assume-unchanged bit's. Skip-worktree also takes +precedence over assume-unchanged bit when both are set. + + Configuration ------------- diff --git a/builtin-ls-files.c b/builtin-ls-files.c index f473220502..c1afbad453 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -37,6 +37,7 @@ static const char *tag_removed = ""; static const char *tag_other = ""; static const char *tag_killed = ""; static const char *tag_modified = ""; +static const char *tag_skip_worktree = ""; static void show_dir_entry(const char *tag, struct dir_entry *ent) { @@ -178,7 +179,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) continue; if (ce->ce_flags & CE_UPDATE) continue; - show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce); + show_ce_entry(ce_stage(ce) ? tag_unmerged : + (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce); } } if (show_deleted | show_modified) { @@ -490,6 +492,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) tag_modified = "C "; tag_other = "? "; tag_killed = "K "; + tag_skip_worktree = "S "; } if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed) require_work_tree = 1; diff --git a/builtin-update-index.c b/builtin-update-index.c index f1b6c8e88e..5e97d09497 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -24,6 +24,7 @@ static int info_only; static int force_remove; static int verbose; static int mark_valid_only; +static int mark_skip_worktree_only; #define MARK_FLAG 1 #define UNMARK_FLAG 2 @@ -276,6 +277,11 @@ static void update_one(const char *path, const char *prefix, int prefix_length) die("Unable to mark file %s", path); goto free_return; } + if (mark_skip_worktree_only) { + if (mark_ce_flags(p, CE_SKIP_WORKTREE, mark_skip_worktree_only == MARK_FLAG)) + die("Unable to mark file %s", path); + goto free_return; + } if (force_remove) { if (remove_file_from_cache(p)) @@ -384,7 +390,7 @@ static void read_index_info(int line_termination) } static const char update_index_usage[] = -"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] ..."; +"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] ..."; static unsigned char head_sha1[20]; static unsigned char merge_head_sha1[20]; @@ -650,6 +656,14 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) mark_valid_only = UNMARK_FLAG; continue; } + if (!strcmp(path, "--no-skip-worktree")) { + mark_skip_worktree_only = UNMARK_FLAG; + continue; + } + if (!strcmp(path, "--skip-worktree")) { + mark_skip_worktree_only = MARK_FLAG; + continue; + } if (!strcmp(path, "--info-only")) { info_only = 1; continue; diff --git a/cache.h b/cache.h index 9222774e6c..f266246caf 100644 --- a/cache.h +++ b/cache.h @@ -181,10 +181,11 @@ struct cache_entry { * Extended on-disk flags */ #define CE_INTENT_TO_ADD 0x20000000 +#define CE_SKIP_WORKTREE 0x40000000 /* CE_EXTENDED2 is for future extension */ #define CE_EXTENDED2 0x80000000 -#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD) +#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE) /* * Safeguard to avoid saving wrong flags: @@ -233,6 +234,7 @@ static inline size_t ce_namelen(const struct cache_entry *ce) ondisk_cache_entry_size(ce_namelen(ce))) #define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT) #define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE) +#define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE) #define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE) #define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644) diff --git a/t/t2104-update-index-skip-worktree.sh b/t/t2104-update-index-skip-worktree.sh new file mode 100755 index 0000000000..1d0879be06 --- /dev/null +++ b/t/t2104-update-index-skip-worktree.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# +# Copyright (c) 2008 Nguyễn Thái Ngọc Duy +# + +test_description='skip-worktree bit test' + +. ./test-lib.sh + +cat >expect.full <expect.skip < Date: Thu, 20 Aug 2009 20:46:58 +0700 Subject: [PATCH 016/884] Teach Git to respect skip-worktree bit (reading part) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grep: turn on --cached for files that is marked skip-worktree ls-files: do not check for deleted file that is marked skip-worktree update-index: ignore update request if it's skip-worktree, while still allows removing diff*: skip worktree version Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin-commit.c | 5 + builtin-grep.c | 2 +- builtin-ls-files.c | 2 + builtin-update-index.c | 38 ++++--- diff-lib.c | 5 +- diff.c | 2 +- read-cache.c | 8 +- t/t7011-skip-worktree-reading.sh | 163 +++++++++++++++++++++++++++++++ 8 files changed, 199 insertions(+), 26 deletions(-) create mode 100755 t/t7011-skip-worktree-reading.sh diff --git a/builtin-commit.c b/builtin-commit.c index 4bcce06fbf..a0b1fd35cb 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -180,6 +180,11 @@ static void add_remove_files(struct string_list *list) for (i = 0; i < list->nr; i++) { struct stat st; struct string_list_item *p = &(list->items[i]); + int pos = index_name_pos(&the_index, p->string, strlen(p->string)); + struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos]; + + if (ce && ce_skip_worktree(ce)) + continue; if (!lstat(p->string, &st)) { if (add_to_cache(p->string, &st, 0)) diff --git a/builtin-grep.c b/builtin-grep.c index ad0e0a5385..813fe9778a 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -517,7 +517,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached, * are identical, even if worktree file has been modified, so use * cache version instead */ - if (cached || (ce->ce_flags & CE_VALID)) { + if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) { if (ce_stage(ce)) continue; hit |= grep_sha1(opt, ce->sha1, ce->name, 0); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index c1afbad453..ad7e44784f 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -194,6 +194,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) continue; if (ce->ce_flags & CE_UPDATE) continue; + if (ce_skip_worktree(ce)) + continue; err = lstat(ce->name, &st); if (show_deleted && err) show_ce_entry(tag_removed, ce); diff --git a/builtin-update-index.c b/builtin-update-index.c index 5e97d09497..97b9ea61f7 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -172,29 +172,29 @@ static int process_directory(const char *path, int len, struct stat *st) return error("%s: is a directory - add files inside instead", path); } -/* - * Process a regular file - */ -static int process_file(const char *path, int len, struct stat *st) -{ - int pos = cache_name_pos(path, len); - struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos]; - - if (ce && S_ISGITLINK(ce->ce_mode)) - return error("%s is already a gitlink, not replacing", path); - - return add_one_path(ce, path, len, st); -} - static int process_path(const char *path) { - int len; + int pos, len; struct stat st; + struct cache_entry *ce; len = strlen(path); if (has_symlink_leading_path(path, len)) return error("'%s' is beyond a symbolic link", path); + pos = cache_name_pos(path, len); + ce = pos < 0 ? NULL : active_cache[pos]; + if (ce && ce_skip_worktree(ce)) { + /* + * working directory version is assumed "good" + * so updating it does not make sense. + * On the other hand, removing it from index should work + */ + if (allow_remove && remove_file_from_cache(path)) + return error("%s: cannot remove from the index", path); + return 0; + } + /* * First things first: get the stat information, to decide * what to do about the pathname! @@ -205,7 +205,13 @@ static int process_path(const char *path) if (S_ISDIR(st.st_mode)) return process_directory(path, len, &st); - return process_file(path, len, &st); + /* + * Process a regular file + */ + if (ce && S_ISGITLINK(ce->ce_mode)) + return error("%s is already a gitlink, not replacing", path); + + return add_one_path(ce, path, len, &st); } static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, diff --git a/diff-lib.c b/diff-lib.c index 22da66ef14..b0b379d9d2 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -159,7 +159,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } - if (ce_uptodate(ce)) + if (ce_uptodate(ce) || ce_skip_worktree(ce)) continue; /* If CE_VALID is set, don't look at workdir for file removal */ @@ -339,7 +339,8 @@ static void do_oneway_diff(struct unpack_trees_options *o, int match_missing, cached; /* if the entry is not checked out, don't examine work tree */ - cached = o->index_only || (idx && (idx->ce_flags & CE_VALID)); + cached = o->index_only || + (idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx))); /* * Backward compatibility wart - "diff-index -m" does * not mean "do not ignore merges", but "match_missing". diff --git a/diff.c b/diff.c index cd35e0c2d7..3970df4afc 100644 --- a/diff.c +++ b/diff.c @@ -1805,7 +1805,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int * If ce is marked as "assume unchanged", there is no * guarantee that work tree matches what we are looking for. */ - if (ce->ce_flags & CE_VALID) + if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) return 0; /* diff --git a/read-cache.c b/read-cache.c index 4e3e272ee4..5ee7d9da9c 100644 --- a/read-cache.c +++ b/read-cache.c @@ -265,7 +265,7 @@ int ie_match_stat(const struct index_state *istate, * If it's marked as always valid in the index, it's * valid whatever the checked-out copy says. */ - if (!ignore_valid && (ce->ce_flags & CE_VALID)) + if (!ignore_valid && ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))) return 0; /* @@ -1004,11 +1004,7 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, if (ce_uptodate(ce)) return ce; - /* - * CE_VALID means the user promised us that the change to - * the work tree does not matter and told us not to worry. - */ - if (!ignore_valid && (ce->ce_flags & CE_VALID)) { + if (!ignore_valid && ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))) { ce_mark_uptodate(ce); return ce; } diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh new file mode 100755 index 0000000000..e996928de2 --- /dev/null +++ b/t/t7011-skip-worktree-reading.sh @@ -0,0 +1,163 @@ +#!/bin/sh +# +# Copyright (c) 2008 Nguyễn Thái Ngọc Duy +# + +test_description='skip-worktree bit test' + +. ./test-lib.sh + +cat >expect.full <expect.skip < expected && + git ls-files --stage 1 > result && + test_cmp expected result && + test ! -f 1 +} + +setup_dirty() { + git update-index --force-remove 1 && + echo dirty > 1 && + git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --skip-worktree 1 +} + +test_dirty() { + echo "100644 $NULL_SHA1 0 1" > expected && + git ls-files --stage 1 > result && + test_cmp expected result && + echo dirty > expected + test_cmp expected 1 +} + +test_expect_success 'setup' ' + test_commit init && + mkdir sub && + touch ./1 ./2 sub/1 sub/2 && + git add 1 2 sub/1 sub/2 && + git update-index --skip-worktree 1 sub/1 && + git ls-files -t > result && + test_cmp expect.skip result +' + +test_expect_success 'update-index' ' + setup_absent && + git update-index 1 && + test_absent +' + +test_expect_success 'update-index' ' + setup_dirty && + git update-index 1 && + test_dirty +' + +test_expect_success 'update-index --remove' ' + setup_absent && + git update-index --remove 1 && + test -z "$(git ls-files 1)" && + test ! -f 1 +' + +test_expect_success 'update-index --remove' ' + setup_dirty && + git update-index --remove 1 && + test -z "$(git ls-files 1)" && + echo dirty > expected && + test_cmp expected 1 +' + +test_expect_success 'ls-files --delete' ' + setup_absent && + test -z "$(git ls-files -d)" +' + +test_expect_success 'ls-files --delete' ' + setup_dirty && + test -z "$(git ls-files -d)" +' + +test_expect_success 'ls-files --modified' ' + setup_absent && + test -z "$(git ls-files -m)" +' + +test_expect_success 'ls-files --modified' ' + setup_dirty && + test -z "$(git ls-files -m)" +' + +test_expect_success 'grep with skip-worktree file' ' + git update-index --no-skip-worktree 1 && + echo test > 1 && + git update-index 1 && + git update-index --skip-worktree 1 && + rm 1 && + test "$(git grep --no-ext-grep test)" = "1:test" +' + +echo ":000000 100644 $ZERO_SHA0 $NULL_SHA1 A 1" > expected +test_expect_success 'diff-index does not examine skip-worktree absent entries' ' + setup_absent && + git diff-index HEAD -- 1 > result && + test_cmp expected result +' + +test_expect_success 'diff-index does not examine skip-worktree dirty entries' ' + setup_dirty && + git diff-index HEAD -- 1 > result && + test_cmp expected result +' + +test_expect_success 'diff-files does not examine skip-worktree absent entries' ' + setup_absent && + test -z "$(git diff-files -- one)" +' + +test_expect_success 'diff-files does not examine skip-worktree dirty entries' ' + setup_dirty && + test -z "$(git diff-files -- one)" +' + +test_expect_success 'git-rm succeeds on skip-worktree absent entries' ' + setup_absent && + git rm 1 +' + +test_expect_failure 'commit on skip-worktree absent entries' ' + git reset && + setup_absent && + test_must_fail git commit -m null 1 +' + +test_expect_failure 'commit on skip-worktree dirty entries' ' + git reset && + setup_dirty && + test_must_fail git commit -m null 1 +' + +test_done From 52030836943c0d28d8be4202762ede28e921dc21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:46:59 +0700 Subject: [PATCH 017/884] Teach Git to respect skip-worktree bit (writing part) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This part is mainly to remove CE_VALID shortcuts (and as a consequence, ce_uptodate() shortcuts as it may be turned on by CE_VALID) in writing code path if skip-worktree is used. Various tests are added to avoid future breakages. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- t/t7012-skip-worktree-writing.sh | 146 +++++++++++++++++++++++++++++++ unpack-trees.c | 4 +- 2 files changed, 148 insertions(+), 2 deletions(-) create mode 100755 t/t7012-skip-worktree-writing.sh diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh new file mode 100755 index 0000000000..8d8b1c0e25 --- /dev/null +++ b/t/t7012-skip-worktree-writing.sh @@ -0,0 +1,146 @@ +#!/bin/sh +# +# Copyright (c) 2008 Nguyễn Thái Ngọc Duy +# + +test_description='test worktree writing operations when skip-worktree is used' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init && + echo modified >> init.t && + touch added && + git add init.t added && + git commit -m "modified and added" && + git tag top +' + +test_expect_success 'read-tree updates worktree, absent case' ' + git checkout -f top && + git update-index --skip-worktree init.t && + rm init.t && + git read-tree -m -u HEAD^ && + echo init > expected && + test_cmp expected init.t +' + +test_expect_success 'read-tree updates worktree, dirty case' ' + git checkout -f top && + git update-index --skip-worktree init.t && + echo dirty >> init.t && + test_must_fail git read-tree -m -u HEAD^ && + grep -q dirty init.t && + test "$(git ls-files -t init.t)" = "S init.t" && + git update-index --no-skip-worktree init.t +' + +test_expect_success 'read-tree removes worktree, absent case' ' + git checkout -f top && + git update-index --skip-worktree added && + rm added && + git read-tree -m -u HEAD^ && + test ! -f added +' + +test_expect_success 'read-tree removes worktree, dirty case' ' + git checkout -f top && + git update-index --skip-worktree added && + echo dirty >> added && + test_must_fail git read-tree -m -u HEAD^ && + grep -q dirty added && + test "$(git ls-files -t added)" = "S added" && + git update-index --no-skip-worktree added +' + +NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +ZERO_SHA0=0000000000000000000000000000000000000000 +setup_absent() { + test -f 1 && rm 1 + git update-index --remove 1 && + git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --skip-worktree 1 +} + +test_absent() { + echo "100644 $NULL_SHA1 0 1" > expected && + git ls-files --stage 1 > result && + test_cmp expected result && + test ! -f 1 +} + +setup_dirty() { + git update-index --force-remove 1 && + echo dirty > 1 && + git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --skip-worktree 1 +} + +test_dirty() { + echo "100644 $NULL_SHA1 0 1" > expected && + git ls-files --stage 1 > result && + test_cmp expected result && + echo dirty > expected + test_cmp expected 1 +} + +cat >expected < result && + test_cmp expected result +' + +test_expect_success 'git-add ignores worktree content' ' + setup_absent && + git add 1 && + test_absent +' + +test_expect_success 'git-add ignores worktree content' ' + setup_dirty && + git add 1 && + test_dirty +' + +test_expect_success 'git-rm fails if worktree is dirty' ' + setup_dirty && + test_must_fail git rm 1 && + test_dirty +' + +cat >expected < result && + test_cmp expected result +' + +test_expect_success 'git-clean, dirty case' ' + setup_dirty && + git clean -n > result && + test_cmp expected result +' + +test_expect_failure 'git-apply adds file' false +test_expect_failure 'git-apply updates file' false +test_expect_failure 'git-apply removes file' false +test_expect_failure 'git-mv to skip-worktree' false +test_expect_failure 'git-mv from skip-worktree' false +test_expect_failure 'git-checkout' false + +test_done diff --git a/unpack-trees.c b/unpack-trees.c index 720f7a1616..3eda263590 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -450,7 +450,7 @@ static int verify_uptodate(struct cache_entry *ce, { struct stat st; - if (o->index_only || o->reset || ce_uptodate(ce)) + if (o->index_only || (!ce_skip_worktree(ce) && (o->reset || ce_uptodate(ce)))) return 0; if (!lstat(ce->name, &st)) { @@ -1004,7 +1004,7 @@ int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o) if (old && same(old, a)) { int update = 0; - if (o->reset && !ce_uptodate(old)) { + if (o->reset && !ce_uptodate(old) && !ce_skip_worktree(old)) { struct stat st; if (lstat(old->name, &st) || ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID)) From b5041c5f3b9ea70ce7aa9711af6ed6f2d02909b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:00 +0700 Subject: [PATCH 018/884] Avoid writing to buffer in add_excludes_from_file_1() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the next patch, the buffer that is being used within add_excludes_from_file_1() comes from another function and does not have extra space to put \n at the end. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- dir.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dir.c b/dir.c index e05b850acf..1170d64675 100644 --- a/dir.c +++ b/dir.c @@ -229,10 +229,9 @@ static int add_excludes_from_file_1(const char *fname, if (buf_p) *buf_p = buf; - buf[size++] = '\n'; entry = buf; - for (i = 0; i < size; i++) { - if (buf[i] == '\n') { + for (i = 0; i <= size; i++) { + if (i == size || buf[i] == '\n') { if (entry != buf + i && entry[0] != '#') { buf[i - (i && buf[i-1] == '\r')] = 0; add_exclude(entry, base, baselen, which); From c28b3d6e7b0471a02f81324a90b26effae0f4bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:01 +0700 Subject: [PATCH 019/884] Read .gitignore from index if it is skip-worktree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds index as a prerequisite for directory listing (with exclude). At the moment directory listing is used by "git clean", "git add", "git ls-files" and "git status"/"git commit" and unpack_trees()-related commands. These commands have been checked/modified to populate index before doing directory listing. add_excludes_from_file() does not enable this feature, because it is used to read .git/info/exclude and some explicit files specified by "git ls-files". Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- .../technical/api-directory-listing.txt | 3 + builtin-clean.c | 4 +- builtin-ls-files.c | 4 +- dir.c | 65 +++++++++++++------ t/t3001-ls-files-others-exclude.sh | 22 +++++++ t/t7300-clean.sh | 19 ++++++ 6 files changed, 95 insertions(+), 22 deletions(-) diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt index 5bbd18f020..add6f435b5 100644 --- a/Documentation/technical/api-directory-listing.txt +++ b/Documentation/technical/api-directory-listing.txt @@ -58,6 +58,9 @@ The result of the enumeration is left in these fields:: Calling sequence ---------------- +Note: index may be looked at for .gitignore files that are CE_SKIP_WORKTREE +marked. If you to exclude files, make sure you have loaded index first. + * Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0, sizeof(dir))`. diff --git a/builtin-clean.c b/builtin-clean.c index 2d8c735d48..e424b77e6b 100644 --- a/builtin-clean.c +++ b/builtin-clean.c @@ -71,11 +71,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; + if (read_cache() < 0) + die("index file corrupt"); + if (!ignored) setup_standard_excludes(&dir); pathspec = get_pathspec(prefix, argv); - read_cache(); fill_directory(&dir, pathspec); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index ad7e44784f..2e47242b9d 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -485,6 +485,9 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) prefix_offset = strlen(prefix); git_config(git_default_config, NULL); + if (read_cache() < 0) + die("index file corrupt"); + argc = parse_options(argc, argv, prefix, builtin_ls_files_options, ls_files_usage, 0); if (show_tag || show_valid_bit) { @@ -513,7 +516,6 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) pathspec = get_pathspec(prefix, argv); /* be nice with submodule paths ending in a slash */ - read_cache(); if (pathspec) strip_trailing_slash_from_submodules(); diff --git a/dir.c b/dir.c index 1170d64675..e8e5b7917d 100644 --- a/dir.c +++ b/dir.c @@ -200,11 +200,35 @@ void add_exclude(const char *string, const char *base, which->excludes[which->nr++] = x; } +static void *read_skip_worktree_file_from_index(const char *path, size_t *size) +{ + int pos, len; + unsigned long sz; + enum object_type type; + void *data; + struct index_state *istate = &the_index; + + len = strlen(path); + pos = index_name_pos(istate, path, len); + if (pos < 0) + return NULL; + if (!ce_skip_worktree(istate->cache[pos])) + return NULL; + data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz); + if (!data || type != OBJ_BLOB) { + free(data); + return NULL; + } + *size = xsize_t(sz); + return data; +} + static int add_excludes_from_file_1(const char *fname, const char *base, int baselen, char **buf_p, - struct exclude_list *which) + struct exclude_list *which, + int check_index) { struct stat st; int fd, i; @@ -212,20 +236,26 @@ static int add_excludes_from_file_1(const char *fname, char *buf, *entry; fd = open(fname, O_RDONLY); - if (fd < 0 || fstat(fd, &st) < 0) - goto err; - size = xsize_t(st.st_size); - if (size == 0) { + if (fd < 0 || fstat(fd, &st) < 0) { + if (0 <= fd) + close(fd); + if (!check_index || + (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL) + return -1; + } + else { + size = xsize_t(st.st_size); + if (size == 0) { + close(fd); + return 0; + } + buf = xmalloc(size); + if (read_in_full(fd, buf, size) != size) { + close(fd); + return -1; + } close(fd); - return 0; } - buf = xmalloc(size+1); - if (read_in_full(fd, buf, size) != size) - { - free(buf); - goto err; - } - close(fd); if (buf_p) *buf_p = buf; @@ -240,17 +270,12 @@ static int add_excludes_from_file_1(const char *fname, } } return 0; - - err: - if (0 <= fd) - close(fd); - return -1; } void add_excludes_from_file(struct dir_struct *dir, const char *fname) { if (add_excludes_from_file_1(fname, "", 0, NULL, - &dir->exclude_list[EXC_FILE]) < 0) + &dir->exclude_list[EXC_FILE], 0) < 0) die("cannot use %s as an exclude file", fname); } @@ -301,7 +326,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir); add_excludes_from_file_1(dir->basebuf, dir->basebuf, stk->baselen, - &stk->filebuf, el); + &stk->filebuf, el, 1); dir->exclude_stack = stk; current = stk->baselen; } diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index c65bca8388..132c4765cb 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -64,6 +64,8 @@ two/*.4 echo '!*.2 !*.8' >one/two/.gitignore +allignores='.gitignore one/.gitignore one/two/.gitignore' + test_expect_success \ 'git ls-files --others with various exclude options.' \ 'git ls-files --others \ @@ -85,6 +87,26 @@ test_expect_success \ >output && test_cmp expect output' +test_expect_success 'setup skip-worktree gitignore' ' + git add $allignores && + git update-index --skip-worktree $allignores && + rm $allignores +' + +test_expect_success \ + 'git ls-files --others with various exclude options.' \ + 'git ls-files --others \ + --exclude=\*.6 \ + --exclude-per-directory=.gitignore \ + --exclude-from=.git/ignore \ + >output && + test_cmp expect output' + +test_expect_success 'restore gitignore' ' + git checkout $allignores && + rm .git/index +' + cat > excludes-file <<\EOF *.[1-8] e* diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 929d5d4d3b..8073d02be5 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -22,6 +22,25 @@ test_expect_success 'setup' ' ' +test_expect_success 'git clean with skip-worktree .gitignore' ' + git update-index --skip-worktree .gitignore && + rm .gitignore && + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git clean && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so && + git update-index --no-skip-worktree .gitignore && + git checkout .gitignore +' + test_expect_success 'git clean' ' mkdir -p build docs && From 32f54ca31747c47f56abb7ae87a6da0f5b8d97c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:02 +0700 Subject: [PATCH 020/884] unpack-trees(): carry skip-worktree bit over in merged_entry() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this code path, we would remove "old" and replace it with "merge". "old" may have skip-worktree bit, so re-add it to "merge". Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- unpack-trees.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unpack-trees.c b/unpack-trees.c index 3eda263590..dc6d74a16b 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -680,6 +680,8 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, } else { if (verify_uptodate(old, o)) return -1; + if (ce_skip_worktree(old)) + update |= CE_SKIP_WORKTREE; invalidate_ce_path(old, o); } } From c84de70781674a35b9bfd20aa5bc8c47582615df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:03 +0700 Subject: [PATCH 021/884] excluded_1(): support exclude files in index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Index does not really have "directories", attempts to match "foo/" against index will fail unless someone tries to reconstruct directories from a list of file. Observing that dtype in this function can never be NULL (otherwise it would segfault), dtype NULL will be used to say "hey.. you are matching against index" and behave properly. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- dir.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dir.c b/dir.c index e8e5b7917d..7735cea631 100644 --- a/dir.c +++ b/dir.c @@ -349,6 +349,12 @@ static int excluded_1(const char *pathname, int to_exclude = x->to_exclude; if (x->flags & EXC_FLAG_MUSTBEDIR) { + if (!dtype) { + if (!prefixcmp(pathname, exclude)) + return to_exclude; + else + continue; + } if (*dtype == DT_UNKNOWN) *dtype = get_dtype(NULL, pathname, pathlen); if (*dtype != DT_DIR) From cb097534230a6bd7438e19ce6dff719697cbf983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:04 +0700 Subject: [PATCH 022/884] dir.c: export excluded_1() and add_excludes_from_file_1() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These functions are used to handle .gitignore. They are now exported so that sparse checkout can reuse. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- dir.c | 32 ++++++++++++++++---------------- dir.h | 4 ++++ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/dir.c b/dir.c index 7735cea631..6b1c47822f 100644 --- a/dir.c +++ b/dir.c @@ -223,12 +223,12 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size) return data; } -static int add_excludes_from_file_1(const char *fname, - const char *base, - int baselen, - char **buf_p, - struct exclude_list *which, - int check_index) +int add_excludes_from_file_to_list(const char *fname, + const char *base, + int baselen, + char **buf_p, + struct exclude_list *which, + int check_index) { struct stat st; int fd, i; @@ -274,8 +274,8 @@ static int add_excludes_from_file_1(const char *fname, void add_excludes_from_file(struct dir_struct *dir, const char *fname) { - if (add_excludes_from_file_1(fname, "", 0, NULL, - &dir->exclude_list[EXC_FILE], 0) < 0) + if (add_excludes_from_file_to_list(fname, "", 0, NULL, + &dir->exclude_list[EXC_FILE], 0) < 0) die("cannot use %s as an exclude file", fname); } @@ -324,9 +324,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) memcpy(dir->basebuf + current, base + current, stk->baselen - current); strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir); - add_excludes_from_file_1(dir->basebuf, - dir->basebuf, stk->baselen, - &stk->filebuf, el, 1); + add_excludes_from_file_to_list(dir->basebuf, + dir->basebuf, stk->baselen, + &stk->filebuf, el, 1); dir->exclude_stack = stk; current = stk->baselen; } @@ -336,9 +336,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) /* Scan the list and let the last match determine the fate. * Return 1 for exclude, 0 for include and -1 for undecided. */ -static int excluded_1(const char *pathname, - int pathlen, const char *basename, int *dtype, - struct exclude_list *el) +int excluded_from_list(const char *pathname, + int pathlen, const char *basename, int *dtype, + struct exclude_list *el) { int i; @@ -412,8 +412,8 @@ int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p) prep_exclude(dir, pathname, basename-pathname); for (st = EXC_CMDL; st <= EXC_FILE; st++) { - switch (excluded_1(pathname, pathlen, basename, - dtype_p, &dir->exclude_list[st])) { + switch (excluded_from_list(pathname, pathlen, basename, + dtype_p, &dir->exclude_list[st])) { case 0: return 0; case 1: diff --git a/dir.h b/dir.h index a6314464f9..472e11e659 100644 --- a/dir.h +++ b/dir.h @@ -69,7 +69,11 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen, extern int fill_directory(struct dir_struct *dir, const char **pathspec); extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec); +extern int excluded_from_list(const char *pathname, int pathlen, const char *basename, + int *dtype, struct exclude_list *el); extern int excluded(struct dir_struct *, const char *, int *); +extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, + char **buf_p, struct exclude_list *which, int check_index); extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which); From ed5336a7541e19b267de53afc8d15cffdbde8286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:05 +0700 Subject: [PATCH 023/884] Introduce "sparse checkout" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With skip-worktree bit, you can manually set it to unwanted files, then remove them: you would have the so-called sparse checkout. The disadvantages are: - Porcelain tools are not aware of this. Everytime you do an operation that may update working directory, skip-worktree may be cleared out. You have to set them again. - You still have to remove skip-worktree'd files manually, which is boring and ineffective. These will be addressed in the following patches. This patch gives an idea what is "sparse checkout" in Documentation/git-read-tree.txt. This file is chosen instead of git-checkout.txt because it is quite technical and user-unfriendly. I'd expect git-checkout.txt to have something when Porcelain support is done. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-read-tree.txt | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 4a932b08c6..8b3971685a 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -360,6 +360,50 @@ middle of doing, and when your working tree is ready (i.e. you have finished your work-in-progress), attempt the merge again. +Sparse checkout +--------------- + +"Sparse checkout" allows to sparsely populate working directory. +It uses skip-worktree bit (see linkgit:git-update-index[1]) to tell +Git whether a file on working directory is worth looking at. + +"git read-tree" and other merge-based commands ("git merge", "git +checkout"...) can help maintaining skip-worktree bitmap and working +directory update. `$GIT_DIR/info/sparse-checkout` is used to +define the skip-worktree reference bitmap. When "git read-tree" needs +to update working directory, it will reset skip-worktree bit in index +based on this file, which uses the same syntax as .gitignore files. +If an entry matches a pattern in this file, skip-worktree will be +set on that entry. Otherwise, skip-worktree will be unset. + +Then it compares the new skip-worktree value with the previous one. If +skip-worktree turns from unset to set, it will add the corresponding +file back. If it turns from set to unset, that file will be removed. + +While `$GIT_DIR/info/sparse-checkout` is usually used to specify what +files are in. You can also specify what files are _not_ in, using +negate patterns. For example, to remove file "unwanted": + +---------------- +* +!unwanted +---------------- + +Another tricky thing is fully repopulating working directory when you +no longer want sparse checkout. You cannot just disable "sparse +checkout" because skip-worktree are still in the index and you working +directory is still sparsely populated. You should re-populate working +directory with the `$GIT_DIR/info/sparse-checkout` file content as +follows: + +---------------- +* +---------------- + +Then you can disable sparse checkout. Sparse checkout support in "git +read-tree" and similar commands is disabled by default. + + SEE ALSO -------- linkgit:git-write-tree[1]; linkgit:git-ls-files[1]; From e663db2f446b84bfe39b4dcb3d26b33826d52c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:06 +0700 Subject: [PATCH 024/884] unpack-trees(): add CE_WT_REMOVE to remove on worktree alone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CE_REMOVE now removes both worktree and index versions. Sparse checkout must be able to remove worktree version while keep the index intact when checkout area is narrowed. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- cache.h | 3 +++ unpack-trees.c | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cache.h b/cache.h index f266246caf..d3f81a1c7d 100644 --- a/cache.h +++ b/cache.h @@ -177,6 +177,9 @@ struct cache_entry { #define CE_HASHED (0x100000) #define CE_UNHASHED (0x200000) +/* Only remove in work directory, not index */ +#define CE_WT_REMOVE (0x400000) + /* * Extended on-disk flags */ diff --git a/unpack-trees.c b/unpack-trees.c index dc6d74a16b..6a51a69b2a 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -78,7 +78,7 @@ static int check_updates(struct unpack_trees_options *o) if (o->update && o->verbose_update) { for (total = cnt = 0; cnt < index->cache_nr; cnt++) { struct cache_entry *ce = index->cache[cnt]; - if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) + if (ce->ce_flags & (CE_UPDATE | CE_REMOVE | CE_WT_REMOVE)) total++; } @@ -92,6 +92,13 @@ static int check_updates(struct unpack_trees_options *o) for (i = 0; i < index->cache_nr; i++) { struct cache_entry *ce = index->cache[i]; + if (ce->ce_flags & CE_WT_REMOVE) { + display_progress(progress, ++cnt); + if (o->update) + unlink_entry(ce); + continue; + } + if (ce->ce_flags & CE_REMOVE) { display_progress(progress, ++cnt); if (o->update) From 35a5aa79d040e2121b6f6bbb17b173f4644699e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:07 +0700 Subject: [PATCH 025/884] unpack-trees.c: generalize verify_* functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- unpack-trees.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index 6a51a69b2a..8eb4b7095c 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -452,8 +452,9 @@ static int same(struct cache_entry *a, struct cache_entry *b) * When a CE gets turned into an unmerged entry, we * want it to be up-to-date */ -static int verify_uptodate(struct cache_entry *ce, - struct unpack_trees_options *o) +static int verify_uptodate_1(struct cache_entry *ce, + struct unpack_trees_options *o, + const char *error_msg) { struct stat st; @@ -478,7 +479,13 @@ static int verify_uptodate(struct cache_entry *ce, if (errno == ENOENT) return 0; return o->gently ? -1 : - error(ERRORMSG(o, not_uptodate_file), ce->name); + error(error_msg, ce->name); +} + +static int verify_uptodate(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file)); } static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o) @@ -586,8 +593,9 @@ static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst, * We do not want to remove or overwrite a working tree file that * is not tracked, unless it is ignored. */ -static int verify_absent(struct cache_entry *ce, const char *action, - struct unpack_trees_options *o) +static int verify_absent_1(struct cache_entry *ce, const char *action, + struct unpack_trees_options *o, + const char *error_msg) { struct stat st; @@ -667,6 +675,11 @@ static int verify_absent(struct cache_entry *ce, const char *action, } return 0; } +static int verify_absent(struct cache_entry *ce, const char *action, + struct unpack_trees_options *o) +{ + return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked)); +} static int merged_entry(struct cache_entry *merge, struct cache_entry *old, struct unpack_trees_options *o) From 08aefc9e47eb2eaf231352223c939d69b0fb3759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:08 +0700 Subject: [PATCH 026/884] unpack-trees(): "enable" sparse checkout and load $GIT_DIR/info/sparse-checkout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch introduces core.sparseCheckout, which will control whether sparse checkout support is enabled in unpack_trees() It also loads sparse-checkout file that will be used in the next patch. I split it out so the next patch will be shorter, easier to read. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ Documentation/git-read-tree.txt | 4 +++- cache.h | 1 + config.c | 5 +++++ environment.c | 1 + unpack-trees.c | 36 +++++++++++++++++++++++++++------ unpack-trees.h | 4 ++++ 7 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 7791c32bc3..5825c914fb 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -439,6 +439,10 @@ On some file system/operating system combinations, this is unreliable. Set this config setting to 'rename' there; However, This will remove the check that makes sure that existing object files will not get overwritten. +core.sparseCheckout:: + Enable "sparse checkout" feature. See section "Sparse checkout" in + linkgit:git-read-tree[1] for more information. + add.ignore-errors:: Tells 'git-add' to continue adding files when some files cannot be added due to indexing errors. Equivalent to the '--ignore-errors' diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 8b3971685a..fc3f08b81c 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -401,7 +401,9 @@ follows: ---------------- Then you can disable sparse checkout. Sparse checkout support in "git -read-tree" and similar commands is disabled by default. +read-tree" and similar commands is disabled by default. You need to +turn `core.sparseCheckout` on in order to have sparse checkout +support. SEE ALSO diff --git a/cache.h b/cache.h index d3f81a1c7d..2de00f8120 100644 --- a/cache.h +++ b/cache.h @@ -526,6 +526,7 @@ extern size_t delta_base_cache_limit; extern int auto_crlf; extern int fsync_object_files; extern int core_preload_index; +extern int core_apply_sparse_checkout; enum safe_crlf { SAFE_CRLF_FALSE = 0, diff --git a/config.c b/config.c index e87edeab0c..abd762ebfa 100644 --- a/config.c +++ b/config.c @@ -503,6 +503,11 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.sparsecheckout")) { + core_apply_sparse_checkout = git_config_bool(var, value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } diff --git a/environment.c b/environment.c index 8f5eaa7dd8..020422c034 100644 --- a/environment.c +++ b/environment.c @@ -48,6 +48,7 @@ enum push_default_type push_default = PUSH_DEFAULT_MATCHING; #endif enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; int grafts_replace_parents = 1; +int core_apply_sparse_checkout; /* Parallel index stat data preload? */ int core_preload_index = 0; diff --git a/unpack-trees.c b/unpack-trees.c index 8eb4b7095c..44f8fdf808 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -378,6 +378,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options { int ret; static struct cache_entry *dfc; + struct exclude_list el; if (len > MAX_UNPACK_TREES) die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES); @@ -387,6 +388,16 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options state.quiet = 1; state.refresh_cache = 1; + memset(&el, 0, sizeof(el)); + if (!core_apply_sparse_checkout || !o->update) + o->skip_sparse_checkout = 1; + if (!o->skip_sparse_checkout) { + if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, NULL, &el, 0) < 0) + o->skip_sparse_checkout = 1; + else + o->el = ⪙ + } + memset(&o->result, 0, sizeof(o->result)); o->result.initialized = 1; if (o->src_index) { @@ -407,26 +418,39 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options info.fn = unpack_callback; info.data = o; - if (traverse_trees(len, t, &info) < 0) - return unpack_failed(o, NULL); + if (traverse_trees(len, t, &info) < 0) { + ret = unpack_failed(o, NULL); + goto done; + } } /* Any left-over entries in the index? */ if (o->merge) { while (o->pos < o->src_index->cache_nr) { struct cache_entry *ce = o->src_index->cache[o->pos]; - if (unpack_index_entry(ce, o) < 0) - return unpack_failed(o, NULL); + if (unpack_index_entry(ce, o) < 0) { + ret = unpack_failed(o, NULL); + goto done; + } } } - if (o->trivial_merges_only && o->nontrivial_merge) - return unpack_failed(o, "Merge requires file-level merging"); + if (o->trivial_merges_only && o->nontrivial_merge) { + ret = unpack_failed(o, "Merge requires file-level merging"); + goto done; + } o->src_index = NULL; ret = check_updates(o) ? (-2) : 0; if (o->dst_index) *o->dst_index = o->result; + +done: + for (i = 0;i < el.nr;i++) + free(el.excludes[i]); + if (el.excludes) + free(el.excludes); + return ret; } diff --git a/unpack-trees.h b/unpack-trees.h index d19df44f40..5c9e98a666 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -4,6 +4,7 @@ #define MAX_UNPACK_TREES 8 struct unpack_trees_options; +struct exclude_list; typedef int (*merge_fn_t)(struct cache_entry **src, struct unpack_trees_options *options); @@ -28,6 +29,7 @@ struct unpack_trees_options { skip_unmerged, initial_checkout, diff_index_cached, + skip_sparse_checkout, gently; const char *prefix; int pos; @@ -44,6 +46,8 @@ struct unpack_trees_options { struct index_state *dst_index; struct index_state *src_index; struct index_state result; + + struct exclude_list *el; /* for internal use */ }; extern int unpack_trees(unsigned n, struct tree_desc *t, From e800ec9d72a0fafbc323c41e90f7757062c2680e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:09 +0700 Subject: [PATCH 027/884] unpack_trees(): apply $GIT_DIR/info/sparse-checkout to the final index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- unpack-trees.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++++- unpack-trees.h | 2 ++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/unpack-trees.c b/unpack-trees.c index 44f8fdf808..2d8ecb73b1 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -32,6 +32,12 @@ static struct unpack_trees_error_msgs unpack_plumbing_errors = { /* bind_overlap */ "Entry '%s' overlaps with '%s'. Cannot bind.", + + /* sparse_not_uptodate_file */ + "Entry '%s' not uptodate. Cannot update sparse checkout.", + + /* would_lose_orphaned */ + "Working tree file '%s' would be %s by sparse checkout update.", }; #define ERRORMSG(o,fld) \ @@ -125,6 +131,57 @@ static int check_updates(struct unpack_trees_options *o) return errs != 0; } +static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o); +static int verify_absent_sparse(struct cache_entry *ce, const char *action, struct unpack_trees_options *o); + +static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o) +{ + const char *basename; + + if (ce_stage(ce)) + return 0; + + basename = strrchr(ce->name, '/'); + basename = basename ? basename+1 : ce->name; + return excluded_from_list(ce->name, ce_namelen(ce), basename, NULL, o->el) <= 0; +} + +static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o) +{ + int was_skip_worktree = ce_skip_worktree(ce); + + if (will_have_skip_worktree(ce, o)) + ce->ce_flags |= CE_SKIP_WORKTREE; + else + ce->ce_flags &= ~CE_SKIP_WORKTREE; + + /* + * We only care about files getting into the checkout area + * If merge strategies want to remove some, go ahead, this + * flag will be removed eventually in unpack_trees() if it's + * outside checkout area. + */ + if (ce->ce_flags & CE_REMOVE) + return 0; + + if (!was_skip_worktree && ce_skip_worktree(ce)) { + /* + * If CE_UPDATE is set, verify_uptodate() must be called already + * also stat info may have lost after merged_entry() so calling + * verify_uptodate() again may fail + */ + if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate_sparse(ce, o)) + return -1; + ce->ce_flags |= CE_WT_REMOVE; + } + if (was_skip_worktree && !ce_skip_worktree(ce)) { + if (verify_absent_sparse(ce, "overwritten", o)) + return -1; + ce->ce_flags |= CE_UPDATE; + } + return 0; +} + static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o) { int ret = o->fn(src, o); @@ -376,7 +433,7 @@ static int unpack_failed(struct unpack_trees_options *o, const char *message) */ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) { - int ret; + int i, ret; static struct cache_entry *dfc; struct exclude_list el; @@ -440,6 +497,17 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options goto done; } + if (!o->skip_sparse_checkout) { + for (i = 0;i < o->result.cache_nr;i++) { + struct cache_entry *ce = o->result.cache[i]; + + if (apply_sparse_checkout(ce, o)) { + ret = -1; + goto done; + } + } + } + o->src_index = NULL; ret = check_updates(o) ? (-2) : 0; if (o->dst_index) @@ -512,6 +580,12 @@ static int verify_uptodate(struct cache_entry *ce, return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file)); } +static int verify_uptodate_sparse(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + return verify_uptodate_1(ce, o, ERRORMSG(o, sparse_not_uptodate_file)); +} + static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o) { if (ce) @@ -705,6 +779,12 @@ static int verify_absent(struct cache_entry *ce, const char *action, return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked)); } +static int verify_absent_sparse(struct cache_entry *ce, const char *action, + struct unpack_trees_options *o) +{ + return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_orphaned)); +} + static int merged_entry(struct cache_entry *merge, struct cache_entry *old, struct unpack_trees_options *o) { diff --git a/unpack-trees.h b/unpack-trees.h index 5c9e98a666..95ff36c824 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -15,6 +15,8 @@ struct unpack_trees_error_msgs { const char *not_uptodate_dir; const char *would_lose_untracked; const char *bind_overlap; + const char *sparse_not_uptodate_file; + const char *would_lose_orphaned; }; struct unpack_trees_options { From f1f523eae99020d58ad6e7a1ef8187569e15c270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:10 +0700 Subject: [PATCH 028/884] unpack-trees(): ignore worktree check outside checkout area MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit verify_absent() and verify_uptodate() are used to ensure worktree is safe to be updated, then CE_REMOVE or CE_UPDATE will be set. Finally check_updates() bases on CE_REMOVE, CE_UPDATE and the recently added CE_WT_REMOVE to update working directory accordingly. The entries that are checked may eventually be left out of checkout area (done later in apply_sparse_checkout()). We don't want to update outside checkout area. This patch teaches Git to assume "good", skip these checks when it's sure those entries will be outside checkout area, and clear CE_REMOVE|CE_UPDATE that could be set due to this assumption. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- unpack-trees.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/unpack-trees.c b/unpack-trees.c index 2d8ecb73b1..72743b34df 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -505,6 +505,14 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options ret = -1; goto done; } + /* + * Merge strategies may set CE_UPDATE|CE_REMOVE outside checkout + * area as a result of ce_skip_worktree() shortcuts in + * verify_absent() and verify_uptodate(). Clear them. + */ + if (ce_skip_worktree(ce)) + ce->ce_flags &= ~(CE_UPDATE | CE_REMOVE); + } } @@ -577,6 +585,8 @@ static int verify_uptodate_1(struct cache_entry *ce, static int verify_uptodate(struct cache_entry *ce, struct unpack_trees_options *o) { + if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o)) + return 0; return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file)); } @@ -776,6 +786,8 @@ static int verify_absent_1(struct cache_entry *ce, const char *action, static int verify_absent(struct cache_entry *ce, const char *action, struct unpack_trees_options *o) { + if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o)) + return 0; return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked)); } From a5d07d0f5c838acb2c9d69a19907c50f72848b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:11 +0700 Subject: [PATCH 029/884] read-tree: add --no-sparse-checkout to disable sparse checkout support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-read-tree.txt | 6 +++++- builtin-read-tree.c | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index fc3f08b81c..ea7b0b2f5c 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- 'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=] [-u [--exclude-per-directory=] | -i]] - [--index-output=] + [--index-output=] [--no-sparse-checkout] [ []] @@ -110,6 +110,10 @@ OPTIONS directories the index file and index output file are located in. +--no-sparse-checkout:: + Disable sparse checkout support even if `core.sparseCheckout` + is true. + :: The id of the tree object(s) to be read/merged. diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 9c2d634d6d..f5acb1aa93 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -31,7 +31,7 @@ static int list_tree(unsigned char *sha1) } static const char * const read_tree_usage[] = { - "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=] [-u [--exclude-per-directory=] | -i]] [--index-output=] [ []]", + "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=] [-u [--exclude-per-directory=] | -i]] [--no-sparse-checkout] [--index-output=] [ []]", NULL }; @@ -98,6 +98,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) PARSE_OPT_NONEG, exclude_per_directory_cb }, OPT_SET_INT('i', NULL, &opts.index_only, "don't check the working tree after merging", 1), + OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout, + "skip applying sparse checkout filter", 1), OPT_END() }; From d6b38f61c8125423abdf2b9c10f2187c5fecd80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:12 +0700 Subject: [PATCH 030/884] Add tests for sparse checkout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- t/t1009-read-tree-sparse-checkout.sh | 154 +++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100755 t/t1009-read-tree-sparse-checkout.sh diff --git a/t/t1009-read-tree-sparse-checkout.sh b/t/t1009-read-tree-sparse-checkout.sh new file mode 100755 index 0000000000..2192f5abc7 --- /dev/null +++ b/t/t1009-read-tree-sparse-checkout.sh @@ -0,0 +1,154 @@ +#!/bin/sh + +test_description='sparse checkout tests' + +. ./test-lib.sh + +cat >expected <> init.t && + mkdir sub && + touch sub/added && + git add init.t sub/added && + git commit -m "modified and added" && + git tag top && + git rm sub/added && + git commit -m removed && + git tag removed && + git checkout top && + git ls-files --stage > result && + test_cmp expected result +' + +cat >expected.swt < result && + test_cmp expected result && + git ls-files -t > result && + test_cmp expected.swt result +' + +test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' ' + echo > .git/info/sparse-checkout + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test -f sub/added +' + +test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' ' + git config core.sparsecheckout true && + echo > .git/info/sparse-checkout && + git read-tree --no-sparse-checkout -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files --stage > result && + test_cmp expected result && + git ls-files -t > result && + test_cmp expected.swt result && + test ! -f init.t && + test ! -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test ! -f init.t && + test -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + echo sub >> .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test ! -f init.t && + test -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test ! -f sub/added +' + +test_expect_success 'read-tree updates worktree, absent case' ' + echo sub/added > .git/info/sparse-checkout && + git checkout -f top && + git read-tree -m -u HEAD^ && + test ! -f init.t +' + +test_expect_success 'read-tree updates worktree, dirty case' ' + echo sub/added > .git/info/sparse-checkout && + git checkout -f top && + echo dirty > init.t && + git read-tree -m -u HEAD^ && + grep -q dirty init.t && + rm init.t +' + +test_expect_success 'read-tree removes worktree, dirty case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f top && + echo dirty > added && + git read-tree -m -u HEAD^ && + grep -q dirty added +' + +test_expect_success 'read-tree adds to worktree, absent case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f removed && + git read-tree -u -m HEAD^ && + test ! -f sub/added +' + +test_expect_success 'read-tree adds to worktree, dirty case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f removed && + mkdir sub && + echo dirty > sub/added && + git read-tree -u -m HEAD^ && + grep -q dirty sub/added +' + +test_done From 9e1afb16753d583a696c988d33f45655f81e8f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:13 +0700 Subject: [PATCH 031/884] sparse checkout: inhibit empty worktree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The way sparse checkout works, users may empty their worktree completely, because of non-matching sparse-checkout spec, or empty spec. I believe this is not desired. This patch makes Git refuse to produce such worktree. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- t/t1009-read-tree-sparse-checkout.sh | 10 +++------- unpack-trees.c | 7 +++++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/t/t1009-read-tree-sparse-checkout.sh b/t/t1009-read-tree-sparse-checkout.sh index 2192f5abc7..62246dbf95 100755 --- a/t/t1009-read-tree-sparse-checkout.sh +++ b/t/t1009-read-tree-sparse-checkout.sh @@ -55,20 +55,16 @@ test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse- test -f sub/added ' -cat >expected.swt < .git/info/sparse-checkout && - git read-tree -m -u HEAD && + test_must_fail git read-tree -m -u HEAD && git ls-files --stage > result && test_cmp expected result && git ls-files -t > result && test_cmp expected.swt result && - test ! -f init.t && - test ! -f sub/added + test -f init.t && + test -f sub/added ' cat >expected.swt <skip_sparse_checkout) { + int empty_worktree = 1; for (i = 0;i < o->result.cache_nr;i++) { struct cache_entry *ce = o->result.cache[i]; @@ -512,8 +513,14 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options */ if (ce_skip_worktree(ce)) ce->ce_flags &= ~(CE_UPDATE | CE_REMOVE); + else + empty_worktree = 0; } + if (o->result.cache_nr && empty_worktree) { + ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory"); + goto done; + } } o->src_index = NULL; From 0e098b6d79fbcab763874f2b6fde5aa82144d150 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Thu, 27 Aug 2009 09:38:27 +0200 Subject: [PATCH 032/884] Make test case number unique Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- t/{t4037-whitespace-status.sh => t4040-whitespace-status.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename t/{t4037-whitespace-status.sh => t4040-whitespace-status.sh} (100%) diff --git a/t/t4037-whitespace-status.sh b/t/t4040-whitespace-status.sh similarity index 100% rename from t/t4037-whitespace-status.sh rename to t/t4040-whitespace-status.sh From 97bf2a08095197f43b20b3bc1124552ae24d71bf Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Sun, 30 Aug 2009 22:27:02 +0200 Subject: [PATCH 033/884] diff.c: fix typoes in comments Should be squashed when we reroll 'next' into the main commit. --- diff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diff.c b/diff.c index 91d6ea21a9..24bd3fce3f 100644 --- a/diff.c +++ b/diff.c @@ -2382,7 +2382,7 @@ int diff_setup_done(struct diff_options *options) * Most of the time we can say "there are changes" * only by checking if there are changed paths, but * --ignore-whitespace* options force us to look - * inside contets. + * inside contents. */ if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) || @@ -3346,7 +3346,7 @@ free_queue: fclose(options->file); /* - * Report the contents level differences with HAS_CHANGES; + * Report the content-level differences with HAS_CHANGES; * diff_addremove/diff_change does not set the bit when * DIFF_FROM_CONTENTS is in effect (e.g. with -w). */ From aa7dd05e6a622ec17d034980c4440b0aed583c6e Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 1 Sep 2009 13:39:16 +0200 Subject: [PATCH 034/884] gitweb: Add optional "time to generate page" info in footer Add "This page took XXX seconds and Y git commands to generate" to page footer, if global feature 'timed' is enabled (disabled by default). Requires Time::HiRes installed for high precision 'wallclock' time. Note that Time::HiRes is being required unconditionally; this is because setting $t0 variable needs to be done fairly early to have running time of the whole script. If Time::HiRes module were required only if 'timed' feature is enabled, the earliest place where starting time ($t0) could be calculated would be after reading gitweb config, making "time to generate page" info inaccurate. This code is based on example code by Petr 'Pasky' Baudis. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.css | 7 +++++++ gitweb/gitweb.perl | 29 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 8f68fe3091..9893443edc 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -75,6 +75,13 @@ div.page_footer_text { font-style: italic; } +div#generating_info { + margin: 4px; + font-size: smaller; + text-align: center; + color: #505050; +} + div.page_body { padding: 8px; font-family: monospace; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 2cb60bedc6..18cbf35391 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -18,6 +18,12 @@ use File::Find qw(); use File::Basename qw(basename); binmode STDOUT, ':utf8'; +our $t0; +if (eval { require Time::HiRes; 1; }) { + $t0 = [Time::HiRes::gettimeofday()]; +} +our $number_of_git_cmds = 0; + BEGIN { CGI->compile() if $ENV{'MOD_PERL'}; } @@ -394,6 +400,13 @@ our %feature = ( 'sub' => \&feature_avatar, 'override' => 0, 'default' => ['']}, + + # Enable displaying how much time and how many git commands + # it took to generate and display page. Disabled by default. + # Project specific override is not supported. + 'timed' => { + 'override' => 0, + 'default' => [0]}, ); sub gitweb_get_feature { @@ -507,6 +520,7 @@ if (-e $GITWEB_CONFIG) { # version of the core git binary our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown"; +$number_of_git_cmds++; $projects_list ||= $projectroot; @@ -1955,6 +1969,7 @@ sub get_feed_info { # returns path to the core git executable and the --git-dir parameter as list sub git_cmd { + $number_of_git_cmds++; return $GIT, '--git-dir='.$git_dir; } @@ -3205,6 +3220,20 @@ sub git_footer_html { } print "\n"; # class="page_footer" + if (defined $t0 && gitweb_check_feature('timed')) { + print "
\n"; + print 'This page took '. + ''. + Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]). + ' seconds '. + ' and '. + ''. + $number_of_git_cmds. + ' git commands '. + " to generate.\n"; + print "
\n"; # class="page_footer" + } + if (-f $site_footer) { insert_file($site_footer); } From 4af819d4cad206648b832f4d6103547981ab8004 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 1 Sep 2009 13:39:17 +0200 Subject: [PATCH 035/884] gitweb: Incremental blame (using JavaScript) Add 'blame_incremental' view, which uses "git blame --incremental" and JavaScript (Ajax), where 'blame' use "git blame --porcelain". * gitweb generates initial info by putting file contents (from "git cat-file") together with line numbers in blame table * then gitweb makes web browser JavaScript engine call startBlame() function from gitweb.js * startBlame() opens XMLHttpRequest connection to 'blame_data' view, which in turn calls "git blame --incremental" for a file, and streams output of git-blame to JavaScript (gitweb.js) * XMLHttpRequest event handler updates line info in blame view as soon as it gets data from 'blame_data' (from server), and it also updates progress info * when 'blame_data' ends, and gitweb.js finishes updating line info, it fixes colors to match (as far as possible) ordinary 'blame' view, and updates information about how long it took to generate page. Gitweb deals with streamed 'blame_data' server errors by displaying them in the progress info area (just in case). The 'blame_incremental' view tries to be equivalent to 'blame' action; there are however a few differences in output between 'blame' and 'blame_incremental' view: * 'blame_incremental' always used query form for this part of link(s) which is generated by JavaScript code. The difference is visible if we use path_info link (pass some or all arguments in path_info). Changing this would require implementing something akin to href() subroutine from gitweb.perl in JavaScript (in gitweb.js). * 'blame_incremental' always uses "rowspan" attribute, even if rowspan="1". This simplifies code, and is not visible to user. * The progress bar and progress info are still there even after JavaScript part of 'blame_incremental' finishes work. Note that currently no link generated by gitweb leads to this new view. This code is based on patch by Petr Baudis patch, which in turn was tweaked up version of Fredrik Kuivinen 's proof of concept patch. This patch adds GITWEB_JS compile configuration option, and modifies git-instaweb.sh to take gitweb.js into account. The code for git-instaweb.sh was taken from Pasky's patch. Signed-off-by: Fredrik Kuivinen Signed-off-by: Petr Baudis Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- Makefile | 6 +- git-instaweb.sh | 7 + gitweb/README | 4 + gitweb/gitweb.css | 11 + gitweb/gitweb.js | 726 +++++++++++++++++++++++++++++++++++++++++++++ gitweb/gitweb.perl | 274 +++++++++++------ 6 files changed, 940 insertions(+), 88 deletions(-) create mode 100644 gitweb/gitweb.js diff --git a/Makefile b/Makefile index daf4296706..2ad3b1e40c 100644 --- a/Makefile +++ b/Makefile @@ -265,6 +265,7 @@ GITWEB_HOMETEXT = indextext.html GITWEB_CSS = gitweb.css GITWEB_LOGO = git-logo.png GITWEB_FAVICON = git-favicon.png +GITWEB_JS = gitweb.js GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = @@ -1407,13 +1408,14 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ + -e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \ -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \ -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \ $< >$@+ && \ chmod +x $@+ && \ mv $@+ $@ -git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css +git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css gitweb/gitweb.js $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ @@ -1422,6 +1424,8 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css -e '/@@GITWEB_CGI@@/d' \ -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \ -e '/@@GITWEB_CSS@@/d' \ + -e '/@@GITWEB_JS@@/r gitweb/gitweb.js' \ + -e '/@@GITWEB_JS@@/d' \ -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \ $@.sh > $@+ && \ chmod +x $@+ && \ diff --git a/git-instaweb.sh b/git-instaweb.sh index 5f4419b69b..6f381c88da 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -331,8 +331,15 @@ gitweb_css () { EOFGITWEB } +gitweb_js () { + cat > "$1" <<\EOFGITWEB +@@GITWEB_JS@@ +EOFGITWEB +} + gitweb_cgi "$GIT_DIR/gitweb/gitweb.cgi" gitweb_css "$GIT_DIR/gitweb/gitweb.css" +gitweb_js "$GIT_DIR/gitweb/gitweb.js" case "$httpd" in *lighttpd*) diff --git a/gitweb/README b/gitweb/README index 9056d1e090..cbcd993ecb 100644 --- a/gitweb/README +++ b/gitweb/README @@ -92,6 +92,10 @@ You can specify the following configuration variables when building GIT: web browsers that support favicons (website icons) may display them in the browser's URL bar and next to site name in bookmarks). Relative to base URI of gitweb. [Default: git-favicon.png] + * GITWEB_JS + Points to the localtion where you put gitweb.js on your web server + (or to be more generic URI of JavaScript code used by gitweb). + Relative to base URI of gitweb. [Default: gitweb.js] * GITWEB_CONFIG This Perl file will be loaded using 'do' and can be used to override any of the options above as well as some other options -- see the "Runtime diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 9893443edc..4a2e496568 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -348,6 +348,17 @@ td.mode { font-family: monospace; } +/* progress of blame_interactive */ +div#progress_bar { + height: 2px; + margin-bottom: -2px; + background-color: #d8d9d0; +} +div#progress_info { + float: right; + text-align: right; +} + /* styling of diffs (patchsets): commitdiff and blobdiff views */ div.diff.header, div.diff.extended_header { diff --git a/gitweb/gitweb.js b/gitweb/gitweb.js new file mode 100644 index 0000000000..b15e2a7944 --- /dev/null +++ b/gitweb/gitweb.js @@ -0,0 +1,726 @@ +// Copyright (C) 2007, Fredrik Kuivinen +// 2007, Petr Baudis +// 2008-2009, Jakub Narebski + +/** + * @fileOverview JavaScript code for gitweb (git web interface). + * @license GPLv2 or later + */ + +/* + * This code uses DOM methods instead of (nonstandard) innerHTML + * to modify page. + * + * innerHTML is non-standard IE extension, though supported by most + * browsers; however Firefox up to version 1.5 didn't implement it in + * a strict mode (application/xml+xhtml mimetype). + * + * Also my simple benchmarks show that using elem.firstChild.data = + * 'content' is slightly faster than elem.innerHTML = 'content'. It + * is however more fragile (text element fragment must exists), and + * less feature-rich (we cannot add HTML). + * + * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the + * equivalent using DOM 2 Core is usually shown in comments. + */ + + +/* ============================================================ */ +/* generic utility functions */ + + +/** + * pad number N with nonbreakable spaces on the left, to WIDTH characters + * example: padLeftStr(12, 3, ' ') == ' 12' + * (' ' is nonbreakable space) + * + * @param {Number|String} input: number to pad + * @param {Number} width: visible width of output + * @param {String} str: string to prefix to string, e.g. ' ' + * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR + */ +function padLeftStr(input, width, str) { + var prefix = ''; + + width -= input.toString().length; + while (width > 1) { + prefix += str; + width--; + } + return prefix + input; +} + +/** + * Pad INPUT on the left to SIZE width, using given padding character CH, + * for example padLeft('a', 3, '_') is '__a'. + * + * @param {String} input: input value converted to string. + * @param {Number} width: desired length of output. + * @param {String} ch: single character to prefix to string. + * + * @returns {String} Modified string, at least SIZE length. + */ +function padLeft(input, width, ch) { + var s = input + ""; + while (s.length < width) { + s = ch + s; + } + return s; +} + +/** + * Create XMLHttpRequest object in cross-browser way + * @returns XMLHttpRequest object, or null + */ +function createRequestObject() { + try { + return new XMLHttpRequest(); + } catch (e) {} + try { + return window.createRequest(); + } catch (e) {} + try { + return new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) {} + try { + return new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) {} + + return null; +} + +/* ============================================================ */ +/* utility/helper functions (and variables) */ + +var xhr; // XMLHttpRequest object +var projectUrl; // partial query + separator ('?' or ';') + +// 'commits' is an associative map. It maps SHA1s to Commit objects. +var commits = {}; + +/** + * constructor for Commit objects, used in 'blame' + * @class Represents a blamed commit + * @param {String} sha1: SHA-1 identifier of a commit + */ +function Commit(sha1) { + if (this instanceof Commit) { + this.sha1 = sha1; + this.nprevious = 0; /* number of 'previous', effective parents */ + } else { + return new Commit(sha1); + } +} + +/* ............................................................ */ +/* progress info, timing, error reporting */ + +var blamedLines = 0; +var totalLines = '???'; +var div_progress_bar; +var div_progress_info; + +/** + * Detects how many lines does a blamed file have, + * This information is used in progress info + * + * @returns {Number|String} Number of lines in file, or string '...' + */ +function countLines() { + var table = + document.getElementById('blame_table') || + document.getElementsByTagName('table')[0]; + + if (table) { + return table.getElementsByTagName('tr').length - 1; // for header + } else { + return '...'; + } +} + +/** + * update progress info and length (width) of progress bar + * + * @globals div_progress_info, div_progress_bar, blamedLines, totalLines + */ +function updateProgressInfo() { + if (!div_progress_info) { + div_progress_info = document.getElementById('progress_info'); + } + if (!div_progress_bar) { + div_progress_bar = document.getElementById('progress_bar'); + } + if (!div_progress_info && !div_progress_bar) { + return; + } + + var percentage = Math.floor(100.0*blamedLines/totalLines); + + if (div_progress_info) { + div_progress_info.firstChild.data = blamedLines + ' / ' + totalLines + + ' (' + padLeftStr(percentage, 3, ' ') + '%)'; + } + + if (div_progress_bar) { + //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;'); + div_progress_bar.style.width = percentage + '%'; + } +} + + +var t_interval_server = ''; +var cmds_server = ''; +var t0 = new Date(); + +/** + * write how much it took to generate data, and to run script + * + * @globals t0, t_interval_server, cmds_server + */ +function writeTimeInterval() { + var info_time = document.getElementById('generating_time'); + if (!info_time || !t_interval_server) { + return; + } + var t1 = new Date(); + info_time.firstChild.data += ' + (' + + t_interval_server + ' sec server blame_data / ' + + (t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)'; + + var info_cmds = document.getElementById('generating_cmd'); + if (!info_time || !cmds_server) { + return; + } + info_cmds.firstChild.data += ' + ' + cmds_server; +} + +/** + * show an error message alert to user within page (in prohress info area) + * @param {String} str: plain text error message (no HTML) + * + * @globals div_progress_info + */ +function errorInfo(str) { + if (!div_progress_info) { + div_progress_info = document.getElementById('progress_info'); + } + if (div_progress_info) { + div_progress_info.className = 'error'; + div_progress_info.firstChild.data = str; + } +} + +/* ............................................................ */ +/* coloring rows like 'blame' after 'blame_data' finishes */ + +/** + * returns true if given row element (tr) is first in commit group + * to be used only after 'blame_data' finishes (after processing) + * + * @param {HTMLElement} tr: table row + * @returns {Boolean} true if TR is first in commit group + */ +function isStartOfGroup(tr) { + return tr.firstChild.className === 'sha1'; +} + +var colorRe = /(?:light|dark)/; + +/** + * change colors to use zebra coloring (2 colors) instead of 3 colors + * concatenate neighbour commit groups belonging to the same commit + * + * @globals colorRe + */ +function fixColorsAndGroups() { + var colorClasses = ['light', 'dark']; + var linenum = 1; + var tr, prev_group; + var colorClass = 0; + var table = + document.getElementById('blame_table') || + document.getElementsByTagName('table')[0]; + + while ((tr = document.getElementById('l'+linenum))) { + // index origin is 0, which is table header; start from 1 + //while ((tr = table.rows[linenum])) { // <- it is slower + if (isStartOfGroup(tr, linenum, document)) { + if (prev_group && + prev_group.firstChild.firstChild.href === + tr.firstChild.firstChild.href) { + // we have to concatenate groups + var prev_rows = prev_group.firstChild.rowSpan || 1; + var curr_rows = tr.firstChild.rowSpan || 1; + prev_group.firstChild.rowSpan = prev_rows + curr_rows; + //tr.removeChild(tr.firstChild); + tr.deleteCell(0); // DOM2 HTML way + } else { + colorClass = (colorClass + 1) % 2; + prev_group = tr; + } + } + var tr_class = tr.className; + tr.className = tr_class.replace(colorRe, colorClasses[colorClass]); + linenum++; + } +} + +/* ............................................................ */ +/* time and data */ + +/** + * used to extract hours and minutes from timezone info, e.g '-0900' + * @constant + */ +var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/; + +/** + * return date in local time formatted in iso-8601 like format + * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' + * + * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {String} date in local time in iso-8601 like format + * + * @globals tzRe + */ +function formatDateISOLocal(epoch, timezoneInfo) { + var match = tzRe.exec(timezoneInfo); + // date corrected by timezone + var localDate = new Date(1000 * (epoch + + (parseInt(match[1],10)*3600 + parseInt(match[2],10)*60))); + var localDateStr = // e.g. '2005-08-07' + localDate.getUTCFullYear() + '-' + + padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + + padLeft(localDate.getUTCDate(), 2, '0'); + var localTimeStr = // e.g. '21:49:46' + padLeft(localDate.getUTCHours(), 2, '0') + ':' + + padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + + padLeft(localDate.getUTCSeconds(), 2, '0'); + + return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; +} + +/* ............................................................ */ +/* unquoting/unescaping filenames */ + +/**#@+ + * @constant + */ +var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g; +var octEscRe = /^[0-7]{1,3}$/; +var maybeQuotedRe = /^\"(.*)\"$/; +/**#@-*/ + +/** + * unquote maybe git-quoted filename + * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a' + * + * @param {String} str: git-quoted string + * @returns {String} Unquoted and unescaped string + * + * @globals escCodeRe, octEscRe, maybeQuotedRe + */ +function unquote(str) { + function unq(seq) { + var es = { + // character escape codes, aka escape sequences (from C) + // replacements are to some extent JavaScript specific + t: "\t", // tab (HT, TAB) + n: "\n", // newline (NL) + r: "\r", // return (CR) + f: "\f", // form feed (FF) + b: "\b", // backspace (BS) + a: "\x07", // alarm (bell) (BEL) + e: "\x1B", // escape (ESC) + v: "\v" // vertical tab (VT) + }; + + if (seq.search(octEscRe) !== -1) { + // octal char sequence + return String.fromCharCode(parseInt(seq, 8)); + } else if (seq in es) { + // C escape sequence, aka character escape code + return es[seq]; + } + // quoted ordinary character + return seq; + } + + var match = str.match(maybeQuotedRe); + if (match) { + str = match[1]; + // perhaps str = eval('"'+str+'"'); would be enough? + str = str.replace(escCodeRe, + function (substr, p1, offset, s) { return unq(p1); }); + } + return str; +} + +/* ============================================================ */ +/* main part: parsing response */ + +/** + * Function called for each blame entry, as soon as it finishes. + * It updates page via DOM manipulation, adding sha1 info, etc. + * + * @param {Commit} commit: blamed commit + * @param {Object} group: object representing group of lines, + * which blame the same commit (blame entry) + * + * @globals blamedLines + */ +function handleLine(commit, group) { + /* + This is the structure of the HTML fragment we are working + with: + + + + 123 + # times (my ext3 doesn't). + + */ + + var resline = group.resline; + + // format date and time string only once per commit + if (!commit.info) { + /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */ + commit.info = commit.author + ', ' + + formatDateISOLocal(commit.authorTime, commit.authorTimezone); + } + + // loop over lines in commit group + for (var i = 0; i < group.numlines; i++, resline++) { + var tr = document.getElementById('l'+resline); + if (!tr) { + break; + } + /* + + + 123 + # times (my ext3 doesn't). + + */ + var td_sha1 = tr.firstChild; + var a_sha1 = td_sha1.firstChild; + var a_linenr = td_sha1.nextSibling.firstChild; + + /* */ + var tr_class = 'light'; // or tr.className + if (commit.boundary) { + tr_class += ' boundary'; + } + if (commit.nprevious === 0) { + tr_class += ' no-previous'; + } else if (commit.nprevious > 1) { + tr_class += ' multiple-previous'; + } + tr.className = tr_class; + + /* ? */ + if (i === 0) { + td_sha1.title = commit.info; + td_sha1.rowSpan = group.numlines; + + a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1; + a_sha1.firstChild.data = commit.sha1.substr(0, 8); + if (group.numlines >= 2) { + var fragment = document.createDocumentFragment(); + var br = document.createElement("br"); + var text = document.createTextNode( + commit.author.match(/\b([A-Z])\B/g).join('')); + if (br && text) { + var elem = fragment || td_sha1; + elem.appendChild(br); + elem.appendChild(text); + if (fragment) { + td_sha1.appendChild(fragment); + } + } + } + } else { + //tr.removeChild(td_sha1); // DOM2 Core way + tr.deleteCell(0); // DOM2 HTML way + } + + /* 123 */ + var linenr_commit = + ('previous' in commit ? commit.previous : commit.sha1); + var linenr_filename = + ('file_parent' in commit ? commit.file_parent : commit.filename); + a_linenr.href = projectUrl + 'a=blame_incremental' + + ';hb=' + linenr_commit + + ';f=' + encodeURIComponent(linenr_filename) + + '#l' + (group.srcline + i); + + blamedLines++; + + //updateProgressInfo(); + } +} + +// ---------------------------------------------------------------------- + +var inProgress = false; // are we processing response + +/**#@+ + * @constant + */ +var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/; +var infoRe = /^([a-z-]+) ?(.*)/; +var endRe = /^END ?([^ ]*) ?(.*)/; +/**@-*/ + +var curCommit = new Commit(); +var curGroup = {}; + +var pollTimer = null; + +/** + * Parse output from 'git blame --incremental [...]', received via + * XMLHttpRequest from server (blamedataUrl), and call handleLine + * (which updates page) as soon as blame entry is completed. + * + * @param {String[]} lines: new complete lines from blamedata server + * + * @globals commits, curCommit, curGroup, t_interval_server, cmds_server + * @globals sha1Re, infoRe, endRe + */ +function processBlameLines(lines) { + var match; + + for (var i = 0, len = lines.length; i < len; i++) { + + if ((match = sha1Re.exec(lines[i]))) { + var sha1 = match[1]; + var srcline = parseInt(match[2], 10); + var resline = parseInt(match[3], 10); + var numlines = parseInt(match[4], 10); + + var c = commits[sha1]; + if (!c) { + c = new Commit(sha1); + commits[sha1] = c; + } + curCommit = c; + + curGroup.srcline = srcline; + curGroup.resline = resline; + curGroup.numlines = numlines; + + } else if ((match = infoRe.exec(lines[i]))) { + var info = match[1]; + var data = match[2]; + switch (info) { + case 'filename': + curCommit.filename = unquote(data); + // 'filename' information terminates the entry + handleLine(curCommit, curGroup); + updateProgressInfo(); + break; + case 'author': + curCommit.author = data; + break; + case 'author-time': + curCommit.authorTime = parseInt(data, 10); + break; + case 'author-tz': + curCommit.authorTimezone = data; + break; + case 'previous': + curCommit.nprevious++; + // store only first 'previous' header + if (!'previous' in curCommit) { + var parts = data.split(' ', 2); + curCommit.previous = parts[0]; + curCommit.file_parent = unquote(parts[1]); + } + break; + case 'boundary': + curCommit.boundary = true; + break; + } // end switch + + } else if ((match = endRe.exec(lines[i]))) { + t_interval_server = match[1]; + cmds_server = match[2]; + + } else if (lines[i] !== '') { + // malformed line + + } // end if (match) + + } // end for (lines) +} + +/** + * Process new data and return pointer to end of processed part + * + * @param {String} unprocessed: new data (from nextReadPos) + * @param {Number} nextReadPos: end of last processed data + * @return {Number} end of processed data (new value for nextReadPos) + */ +function processData(unprocessed, nextReadPos) { + var lastLineEnd = unprocessed.lastIndexOf('\n'); + if (lastLineEnd !== -1) { + var lines = unprocessed.substring(0, lastLineEnd).split('\n'); + nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */; + + processBlameLines(lines); + } // end if + + return nextReadPos; +} + +/** + * Handle XMLHttpRequest errors + * + * @param {XMLHttpRequest} xhr: XMLHttpRequest object + * + * @globals pollTimer, commits, inProgress + */ +function handleError(xhr) { + errorInfo('Server error: ' + + xhr.status + ' - ' + (xhr.statusText || 'Error contacting server')); + + clearInterval(pollTimer); + commits = {}; // free memory + + inProgress = false; +} + +/** + * Called after XMLHttpRequest finishes (loads) + * + * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused) + * + * @globals pollTimer, commits, inProgress + */ +function responseLoaded(xhr) { + clearInterval(pollTimer); + + fixColorsAndGroups(); + writeTimeInterval(); + commits = {}; // free memory + + inProgress = false; +} + +/** + * handler for XMLHttpRequest onreadystatechange event + * @see startBlame + * + * @globals xhr, inProgress + */ +function handleResponse() { + + /* + * xhr.readyState + * + * Value Constant (W3C) Description + * ------------------------------------------------------------------- + * 0 UNSENT open() has not been called yet. + * 1 OPENED send() has not been called yet. + * 2 HEADERS_RECEIVED send() has been called, and headers + * and status are available. + * 3 LOADING Downloading; responseText holds partial data. + * 4 DONE The operation is complete. + */ + + if (xhr.readyState !== 4 && xhr.readyState !== 3) { + return; + } + + // the server returned error + if (xhr.readyState === 3 && xhr.status !== 200) { + return; + } + if (xhr.readyState === 4 && xhr.status !== 200) { + handleError(xhr); + return; + } + + // In konqueror xhr.responseText is sometimes null here... + if (xhr.responseText === null) { + return; + } + + // in case we were called before finished processing + if (inProgress) { + return; + } else { + inProgress = true; + } + + // extract new whole (complete) lines, and process them + while (xhr.prevDataLength !== xhr.responseText.length) { + if (xhr.readyState === 4 && + xhr.prevDataLength === xhr.responseText.length) { + break; + } + + xhr.prevDataLength = xhr.responseText.length; + var unprocessed = xhr.responseText.substring(xhr.nextReadPos); + xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos); + } // end while + + // did we finish work? + if (xhr.readyState === 4 && + xhr.prevDataLength === xhr.responseText.length) { + responseLoaded(xhr); + } + + inProgress = false; +} + +// ============================================================ +// ------------------------------------------------------------ + +/** + * Incrementally update line data in blame_incremental view in gitweb. + * + * @param {String} blamedataUrl: URL to server script generating blame data. + * @param {String} bUrl: partial URL to project, used to generate links. + * + * Called from 'blame_incremental' view after loading table with + * file contents, a base for blame view. + * + * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer +*/ +function startBlame(blamedataUrl, bUrl) { + + xhr = createRequestObject(); + if (!xhr) { + errorInfo('ERROR: XMLHttpRequest not supported'); + return; + } + + t0 = new Date(); + projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';'); + if ((div_progress_bar = document.getElementById('progress_bar'))) { + //div_progress_bar.setAttribute('style', 'width: 100%;'); + div_progress_bar.style.cssText = 'width: 100%;'; + } + totalLines = countLines(); + updateProgressInfo(); + + /* add extra properties to xhr object to help processing response */ + xhr.prevDataLength = -1; // used to detect if we have new data + xhr.nextReadPos = 0; // where unread part of response starts + + xhr.onreadystatechange = handleResponse; + //xhr.onreadystatechange = function () { handleResponse(xhr); }; + + xhr.open('GET', blamedataUrl); + xhr.setRequestHeader('Accept', 'text/plain'); + xhr.send(null); + + // not all browsers call onreadystatechange event on each server flush + // poll response using timer every second to handle this issue + pollTimer = setInterval(xhr.onreadystatechange, 1000); +} + +// end of gitweb.js diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 18cbf35391..6cdd8c39b4 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -96,6 +96,8 @@ our $stylesheet = undef; our $logo = "++GITWEB_LOGO++"; # URI of GIT favicon, assumed to be image/png type our $favicon = "++GITWEB_FAVICON++"; +# URI of gitweb.js (JavaScript code for gitweb) +our $javascript = "++GITWEB_JS++"; # URI and label (title) of GIT logo link #our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/"; @@ -564,6 +566,8 @@ our %cgi_param_mapping = @cgi_param_mapping; # we will also need to know the possible actions, for validation our %actions = ( "blame" => \&git_blame, + "blame_incremental" => \&git_blame_incremental, + "blame_data" => \&git_blame_data, "blobdiff" => \&git_blobdiff, "blobdiff_plain" => \&git_blobdiff_plain, "blob" => \&git_blob, @@ -4787,7 +4791,9 @@ sub git_tag { git_footer_html(); } -sub git_blame { +sub git_blame_common { + my $format = shift || 'porcelain'; + # permissions gitweb_check_feature('blame') or die_error(403, "Blame view not allowed"); @@ -4809,10 +4815,43 @@ sub git_blame { } } - # run git-blame --porcelain - open my $fd, "-|", git_cmd(), "blame", '-p', - $hash_base, '--', $file_name - or die_error(500, "Open git-blame failed"); + my $fd; + if ($format eq 'incremental') { + # get file contents (as base) + open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash + or die_error(500, "Open git-cat-file failed"); + } elsif ($format eq 'data') { + # run git-blame --incremental + open $fd, "-|", git_cmd(), "blame", "--incremental", + $hash_base, "--", $file_name + or die_error(500, "Open git-blame --incremental failed"); + } else { + # run git-blame --porcelain + open $fd, "-|", git_cmd(), "blame", '-p', + $hash_base, '--', $file_name + or die_error(500, "Open git-blame --porcelain failed"); + } + + # incremental blame data returns early + if ($format eq 'data') { + print $cgi->header( + -type=>"text/plain", -charset => "utf-8", + -status=> "200 OK"); + local $| = 1; # output autoflush + print while <$fd>; + close $fd + or print "ERROR $!\n"; + + print 'END'; + if (defined $t0 && gitweb_check_feature('timed')) { + print ' '. + Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]). + ' '.$number_of_git_cmds; + } + print "\n"; + + return; + } # page header git_header_html(); @@ -4823,109 +4862,170 @@ sub git_blame { $cgi->a({-href => href(action=>"history", -replay=>1)}, "history") . " | " . - $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, + $cgi->a({-href => href(action=>$action, file_name=>$file_name)}, "HEAD"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype, $hash_base); # page body + if ($format eq 'incremental') { + print "\n"; + + print qq!
\n!; + } + + print qq!
\n!; + print qq!
... / ...
\n! + if ($format eq 'incremental'); + print qq!\n!. + #qq!\n!. + qq!\n!. + qq!\n!. + qq!\n!. + qq!\n!; + my @rev_color = qw(light dark); my $num_colors = scalar(@rev_color); my $current_color = 0; - my %metainfo = (); - print < -
CommitLineData
- -HTML - LINE: - while (my $line = <$fd>) { - chomp $line; - # the header: [] - # no for subsequent lines in group of lines - my ($full_rev, $orig_lineno, $lineno, $group_size) = - ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/); - if (!exists $metainfo{$full_rev}) { - $metainfo{$full_rev} = { 'nprevious' => 0 }; + if ($format eq 'incremental') { + my $color_class = $rev_color[$current_color]; + + #contents of a file + my $linenr = 0; + LINE: + while (my $line = <$fd>) { + chomp $line; + $linenr++; + + print qq!!. + qq!!. + qq!!; + print qq!\n"; + print qq!\n!; } - my $meta = $metainfo{$full_rev}; - my $data; - while ($data = <$fd>) { - chomp $data; - last if ($data =~ s/^\t//); # contents of line - if ($data =~ /^(\S+)(?: (.*))?$/) { - $meta->{$1} = $2 unless exists $meta->{$1}; + + } else { # porcelain, i.e. ordinary blame + my %metainfo = (); # saves information about commits + + # blame data + LINE: + while (my $line = <$fd>) { + chomp $line; + # the header: [] + # no for subsequent lines in group of lines + my ($full_rev, $orig_lineno, $lineno, $group_size) = + ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/); + if (!exists $metainfo{$full_rev}) { + $metainfo{$full_rev} = { 'nprevious' => 0 }; } - if ($data =~ /^previous /) { - $meta->{'nprevious'}++; - } - } - my $short_rev = substr($full_rev, 0, 8); - my $author = $meta->{'author'}; - my %date = - parse_date($meta->{'author-time'}, $meta->{'author-tz'}); - my $date = $date{'iso-tz'}; - if ($group_size) { - $current_color = ($current_color + 1) % $num_colors; - } - my $tr_class = $rev_color[$current_color]; - $tr_class .= ' boundary' if (exists $meta->{'boundary'}); - $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0); - $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1); - print "\n"; - if ($group_size) { - print "\n"; - } - # 'previous' - if (exists $meta->{'previous'} && - $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) { - $meta->{'parent'} = $1; - $meta->{'file_parent'} = unquote($2); - } - my $linenr_commit = - exists($meta->{'parent'}) ? - $meta->{'parent'} : $full_rev; - my $linenr_filename = - exists($meta->{'file_parent'}) ? - $meta->{'file_parent'} : unquote($meta->{'filename'}); - my $blamed = href(action => 'blame', - file_name => $linenr_filename, - hash_base => $linenr_commit); - print ""; - print "\n"; - print "\n"; + my $short_rev = substr($full_rev, 0, 8); + my $author = $meta->{'author'}; + my %date = + parse_date($meta->{'author-time'}, $meta->{'author-tz'}); + my $date = $date{'iso-tz'}; + if ($group_size) { + $current_color = ($current_color + 1) % $num_colors; + } + my $tr_class = $rev_color[$current_color]; + $tr_class .= ' boundary' if (exists $meta->{'boundary'}); + $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0); + $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1); + print "\n"; + if ($group_size) { + print "\n"; + } + # 'previous' + if (exists $meta->{'previous'} && + $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) { + $meta->{'parent'} = $1; + $meta->{'file_parent'} = unquote($2); + } + my $linenr_commit = + exists($meta->{'parent'}) ? + $meta->{'parent'} : $full_rev; + my $linenr_filename = + exists($meta->{'file_parent'}) ? + $meta->{'file_parent'} : unquote($meta->{'filename'}); + my $blamed = href(action => 'blame', + file_name => $linenr_filename, + hash_base => $linenr_commit); + print ""; + print "\n"; + print "\n"; + } # end while + } - print "
CommitLineData
!. + qq!$linenr! . esc_html($line) . "
1); - print ">"; - print $cgi->a({-href => href(action=>"commit", - hash=>$full_rev, - file_name=>$file_name)}, - esc_html($short_rev)); - if ($group_size >= 2) { - my @author_initials = ($author =~ /\b([[:upper:]])\B/g); - if (@author_initials) { - print "
" . - esc_html(join('', @author_initials)); - # or join('.', ...) + my $meta = $metainfo{$full_rev}; + my $data; + while ($data = <$fd>) { + chomp $data; + last if ($data =~ s/^\t//); # contents of line + if ($data =~ /^(\S+)(?: (.*))?$/) { + $meta->{$1} = $2 unless exists $meta->{$1}; + } + if ($data =~ /^previous /) { + $meta->{'nprevious'}++; } } - print "
"; - print $cgi->a({ -href => "$blamed#l$orig_lineno", - -class => "linenr" }, - esc_html($lineno)); - print "" . esc_html($data) . "
1); + print ">"; + print $cgi->a({-href => href(action=>"commit", + hash=>$full_rev, + file_name=>$file_name)}, + esc_html($short_rev)); + if ($group_size >= 2) { + my @author_initials = ($author =~ /\b([[:upper:]])\B/g); + if (@author_initials) { + print "
" . + esc_html(join('', @author_initials)); + # or join('.', ...) + } + } + print "
"; + print $cgi->a({ -href => "$blamed#l$orig_lineno", + -class => "linenr" }, + esc_html($lineno)); + print "" . esc_html($data) . "
\n"; - print "
"; + + # footer + print "\n". + "\n"; # class="blame" + print "\n"; # class="blame_body" close $fd or print "Reading blob failed\n"; - # page footer + if ($format eq 'incremental') { + print qq!\n!. + qq!\n!; + } + git_footer_html(); } +sub git_blame { + git_blame_common(); +} + +sub git_blame_incremental { + git_blame_common('incremental'); +} + +sub git_blame_data { + git_blame_common('data'); +} + sub git_tags { my $head = git_get_head_hash($project); git_header_html(); From e206d62a922d80f2d00427ddfb93435adbf1cc59 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 1 Sep 2009 13:39:18 +0200 Subject: [PATCH 036/884] gitweb: Colorize 'blame_incremental' view during processing This requires using 3 colors, not only two, to choose a color that is different from colors of up to 2 neighbors. gitweb.js selects the least used color, if more than one color is possible. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.css | 5 +++ gitweb/gitweb.js | 108 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 4a2e496568..c101e4af75 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -257,6 +257,11 @@ tr.no-previous td.linenr { font-weight: bold; } +/* for 'blame_incremental', during processing */ +tr.color1 { background-color: #f6fff6; } +tr.color2 { background-color: #f6f6ff; } +tr.color3 { background-color: #fff6f6; } + td { padding: 2px 5px; font-size: 100%; diff --git a/gitweb/gitweb.js b/gitweb/gitweb.js index b15e2a7944..22570f5e53 100644 --- a/gitweb/gitweb.js +++ b/gitweb/gitweb.js @@ -210,6 +210,101 @@ function errorInfo(str) { } } +/* ............................................................ */ +/* coloring rows during blame_data (git blame --incremental) run */ + +/** + * used to extract N from 'colorN', where N is a number, + * @constant + */ +var colorRe = /\bcolor([0-9]*)\b/; + +/** + * return N if , otherwise return null + * (some browsers require CSS class names to begin with letter) + * + * @param {HTMLElement} tr: table row element to check + * @param {String} tr.className: 'class' attribute of tr element + * @returns {Number|null} N if tr.className == 'colorN', otherwise null + * + * @globals colorRe + */ +function getColorNo(tr) { + if (!tr) { + return null; + } + var className = tr.className; + if (className) { + var match = colorRe.exec(className); + if (match) { + return parseInt(match[1], 10); + } + } + return null; +} + +var colorsFreq = [0, 0, 0]; +/** + * return one of given possible colors (curently least used one) + * example: chooseColorNoFrom(2, 3) returns 2 or 3 + * + * @param {Number[]} arguments: one or more numbers + * assumes that 1 <= arguments[i] <= colorsFreq.length + * @returns {Number} Least used color number from arguments + * @globals colorsFreq + */ +function chooseColorNoFrom() { + // choose the color which is least used + var colorNo = arguments[0]; + for (var i = 1; i < arguments.length; i++) { + if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) { + colorNo = arguments[i]; + } + } + colorsFreq[colorNo-1]++; + return colorNo; +} + +/** + * given two neigbour elements, find color which would be different + * from color of both of neighbours; used to 3-color blame table + * + * @param {HTMLElement} tr_prev + * @param {HTMLElement} tr_next + * @returns {Number} color number N such that + * colorN != tr_prev.className && colorN != tr_next.className + */ +function findColorNo(tr_prev, tr_next) { + var color_prev = getColorNo(tr_prev); + var color_next = getColorNo(tr_next); + + + // neither of neighbours has color set + // THEN we can use any of 3 possible colors + if (!color_prev && !color_next) { + return chooseColorNoFrom(1,2,3); + } + + // either both neighbours have the same color, + // or only one of neighbours have color set + // THEN we can use any color except given + var color; + if (color_prev === color_next) { + color = color_prev; // = color_next; + } else if (!color_prev) { + color = color_next; + } else if (!color_next) { + color = color_prev; + } + if (color) { + return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1); + } + + // neighbours have different colors + // THEN there is only one color left + return (3 - ((color_prev + color_next) % 3)); +} + /* ............................................................ */ /* coloring rows like 'blame' after 'blame_data' finishes */ @@ -224,8 +319,6 @@ function isStartOfGroup(tr) { return tr.firstChild.className === 'sha1'; } -var colorRe = /(?:light|dark)/; - /** * change colors to use zebra coloring (2 colors) instead of 3 colors * concatenate neighbour commit groups belonging to the same commit @@ -391,6 +484,12 @@ function handleLine(commit, group) { formatDateISOLocal(commit.authorTime, commit.authorTimezone); } + // color depends on group of lines, not only on blamed commit + var colorNo = findColorNo( + document.getElementById('l'+(resline-1)), + document.getElementById('l'+(resline+group.numlines)) + ); + // loop over lines in commit group for (var i = 0; i < group.numlines; i++, resline++) { var tr = document.getElementById('l'+resline); @@ -409,7 +508,10 @@ function handleLine(commit, group) { var a_linenr = td_sha1.nextSibling.firstChild; /* */ - var tr_class = 'light'; // or tr.className + var tr_class = ''; + if (colorNo !== null) { + tr_class = 'color'+colorNo; + } if (commit.boundary) { tr_class += ' boundary'; } From c4ccf61f4caf8c293713586531b4a2ea709756c8 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 1 Sep 2009 13:39:19 +0200 Subject: [PATCH 037/884] gitweb: Create links leading to 'blame_incremental' using JavaScript The new 'blame_incremental' view requires JavaScript to run. Not all web browsers implement JavaScript (e.g. text browsers such as Lynx), and not all users have JavaScript enabled. Therefore instead of unconditionally linking to 'blame_incremental' view, we use JavaScript to convert those links to lead to view utilizing JavaScript, by adding 'js=1' to link. Currently the only action that takes 'js=1' into account is 'blame', which then acts as if it was called as 'blame_incremental' action. Possible enhancement would be to do JavaScript redirect by setting window.location instead of modifying $format and $action in git_blame_common() subroutine. The only JavaScript-aware/using view is currently 'blame_incremental'. While at it move reading JavaScript to git_footer_html() subroutine. Note that in this view we do not add 'js=1' currently (even though perhaps we should; note that for consistency we should also add 'js=1' in links added by JavaScript part of 'blame_incremental'). This idea was originally implemented by Petr Baudis in http://article.gmane.org/gmane.comp.version-control.git/47614 but it added \n!; + if ($action eq 'blame_incremental') { + print qq!\n!; + } else { + print qq!\n!; + } + print "\n" . ""; } @@ -4793,6 +4807,10 @@ sub git_tag { sub git_blame_common { my $format = shift || 'porcelain'; + if ($format eq 'porcelain' && $cgi->param('js')) { + $format = 'incremental'; + $action = 'blame_incremental'; # for page title etc + } # permissions gitweb_check_feature('blame') @@ -4872,7 +4890,7 @@ sub git_blame_common { if ($format eq 'incremental') { print "\n"; @@ -5003,14 +5021,6 @@ sub git_blame_common { close $fd or print "Reading blob failed\n"; - if ($format eq 'incremental') { - print qq!\n!. - qq!\n!; - } - git_footer_html(); } From 63267de2acc18027fc0208c0937fd62b91301fec Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 1 Sep 2009 13:39:20 +0200 Subject: [PATCH 038/884] gitweb: Minify gitweb.js if JSMIN is defined It requires that $JSMIN command can function as a filter. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- Makefile | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Makefile b/Makefile index 2ad3b1e40c..9b7dc036b6 100644 --- a/Makefile +++ b/Makefile @@ -198,6 +198,9 @@ all:: # memory allocators with the nedmalloc allocator written by Niall Douglas. # # Define NO_REGEX if you have no or inferior regex support in your C library. +# +# Define JSMIN to point to JavaScript minifier that functions as +# a filter to have gitweb.js minified. GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -250,6 +253,9 @@ lib = lib # DESTDIR= pathsep = : +# JavaScript minifier invocation that can function as filter +JSMIN = + # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf @@ -265,7 +271,11 @@ GITWEB_HOMETEXT = indextext.html GITWEB_CSS = gitweb.css GITWEB_LOGO = git-logo.png GITWEB_FAVICON = git-favicon.png +ifdef JSMIN +GITWEB_JS = gitweb.min.js +else GITWEB_JS = gitweb.js +endif GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = @@ -1388,8 +1398,13 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl chmod +x $@+ && \ mv $@+ $@ +ifdef JSMIN +OTHER_PROGRAMS += gitweb/gitweb.cgi gitweb/gitweb.min.js +gitweb/gitweb.cgi: gitweb/gitweb.perl gitweb/gitweb.min.js +else OTHER_PROGRAMS += gitweb/gitweb.cgi gitweb/gitweb.cgi: gitweb/gitweb.perl +endif $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ @@ -1440,6 +1455,11 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh mv $@+ $@ endif # NO_PERL +ifdef JSMIN +gitweb/gitweb.min.js: gitweb/gitweb.js + $(QUIET_GEN)$(JSMIN) <$< >$@ +endif # JSMIN + configure: configure.ac $(QUIET_GEN)$(RM) $@ $<+ && \ sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ From ef2035c5e55f852905b012dfb2dd242b4f77da70 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Sep 2009 01:41:47 -0700 Subject: [PATCH 039/884] apply --whitespace=fix: fix handling of blank lines at the eof b94f2ed (builtin-apply.c: make it more line oriented, 2008-01-26) broke the logic used to detect if a hunk adds blank lines at the end of the file. With the new code after that commit: - img holds the contents of the file that the hunk is being applied to; - preimage has the lines the hunk expects to be in img; and - postimage has the lines the hunk wants to update the part in img that corresponds to preimage with. and we need to compare if the last line of preimage (not postimage) matches the last line of img to see if the hunk applies at the end of the file. Signed-off-by: Junio C Hamano --- builtin-apply.c | 2 +- t/t4124-apply-ws-rule.sh | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/builtin-apply.c b/builtin-apply.c index 7a1ff041f1..5b5bde4f39 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2069,7 +2069,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, if (applied_pos >= 0) { if (ws_error_action == correct_ws_error && new_blank_lines_at_end && - postimage.nr + applied_pos == img->nr) { + preimage.nr + applied_pos == img->nr) { /* * If the patch application adds blank lines * at the end, and if the patch applies at the diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index f83322e513..6898722f2e 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -148,4 +148,33 @@ do done done + +test_expect_success 'blank at EOF with --whitespace=fix (1)' ' + : these can fail depending on what we did before + git config --unset core.whitespace + rm -f .gitattributes + + { echo a; echo b; echo c; } >one && + git add one && + { echo a; echo b; echo c; } >expect && + { cat expect; echo; } >one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + +test_expect_success 'blank at EOF with --whitespace=fix (2)' ' + { echo a; echo b; echo c; } >one && + git add one && + { echo a; echo c; } >expect && + { cat expect; echo; echo; } >one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + test_done From efa574438fca4ed328d2e47ecdb6363c50a905ec Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 14:08:20 -0700 Subject: [PATCH 040/884] apply --whitespace=fix: detect new blank lines at eof correctly The command tries to strip blank lines at the end of the file added by a patch. It is done by first detecting if a hunk in patch has additional blank lines at the end of itself, and if so checking if such a hunk applies at the end of file. This patch addresses a bug in the logic to implement the former (the previous one addressed a bug in the latter). If the original ends with blank lines, often the patch hunk ends like this: @@ -l,5 +m,7 @@$ _context$ _context$ -deleted$ +$ +$ +$ _$ _$ where _ stands for SP and $ shows a end-of-line. This example patch adds three trailing blank lines, but the code fails to notice it, because it only pays attention to added blank lines at the very end of the hunk. In this example, the three added blank lines do not appear textually at the end in the patch, even though you can see that they are indeed added at the end, if you rearrange the diff like this: @@ -l,5 +m,7 @@$ _context$ _context$ -deleted$ _$ _$ +$ +$ +$ The fix is not to reset the number of (candidate) added blank lines at the end when the loop sees a context line that is empty. Signed-off-by: Junio C Hamano --- builtin-apply.c | 6 ++++++ t/t4124-apply-ws-rule.sh | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/builtin-apply.c b/builtin-apply.c index 5b5bde4f39..c5e4048c61 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1913,6 +1913,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, int len = linelen(patch, size); int plen, added; int added_blank_line = 0; + int is_blank_context = 0; if (!len) break; @@ -1945,8 +1946,11 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, *new++ = '\n'; add_line_info(&preimage, "\n", 1, LINE_COMMON); add_line_info(&postimage, "\n", 1, LINE_COMMON); + is_blank_context = 1; break; case ' ': + if (plen && patch[1] == '\n') + is_blank_context = 1; case '-': memcpy(old, patch + 1, plen); add_line_info(&preimage, old, plen, @@ -1986,6 +1990,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, } if (added_blank_line) new_blank_lines_at_end++; + else if (is_blank_context) + ; else new_blank_lines_at_end = 0; patch += len; diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 6898722f2e..fedc8b9277 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -177,4 +177,28 @@ test_expect_success 'blank at EOF with --whitespace=fix (2)' ' test_cmp expect one ' +test_expect_success 'blank at EOF with --whitespace=fix (3)' ' + { echo a; echo b; echo; } >one && + git add one && + { echo a; echo c; echo; } >expect && + { cat expect; echo; echo; } >one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + +test_expect_success 'blank at end of hunk, not at EOF with --whitespace=fix' ' + { echo a; echo b; echo; echo; echo; echo; echo; echo d; } >one && + git add one && + { echo a; echo c; echo; echo; echo; echo; echo; echo; echo d; } >expect && + cp expect one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + test_done From 92a1747eeab6bc497db748e79ca4f029b1c1dc10 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 22:26:33 -0700 Subject: [PATCH 041/884] apply.c: split check_whitespace() into two This splits the logic to record the presence of whitespace errors out of the check_whitespace() function, which checks and then records. The new function, record_ws_error(), can be used by the blank-at-eof check that does not use ws_check() logic to report its findings in the same output format. Signed-off-by: Junio C Hamano --- builtin-apply.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index c5e4048c61..80ddf55ba5 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1055,23 +1055,29 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc return -1; } -static void check_whitespace(const char *line, int len, unsigned ws_rule) +static void record_ws_error(unsigned result, const char *line, int len, int linenr) { char *err; - unsigned result = ws_check(line + 1, len - 1, ws_rule); + if (!result) return; whitespace_error++; if (squelch_whitespace_errors && squelch_whitespace_errors < whitespace_error) - ; - else { - err = whitespace_error_string(result); - fprintf(stderr, "%s:%d: %s.\n%.*s\n", - patch_input_file, linenr, err, len - 2, line + 1); - free(err); - } + return; + + err = whitespace_error_string(result); + fprintf(stderr, "%s:%d: %s.\n%.*s\n", + patch_input_file, linenr, err, len, line); + free(err); +} + +static void check_whitespace(const char *line, int len, unsigned ws_rule) +{ + unsigned result = ws_check(line + 1, len - 1, ws_rule); + + record_ws_error(result, line + 1, len - 2, linenr); } /* From 77b15bbd88e2f48de093ff0e60de6dbc11e3329e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 16:02:32 -0700 Subject: [PATCH 042/884] apply --whitespace=warn/error: diagnose blank at EOF "git apply" strips new blank lines at EOF under --whitespace=fix option, but neigher --whitespace=warn nor --whitespace=error paid any attention to these errors. Introduce a new whitespace error class, blank-at-eof, to make the whitespace error handling more consistent. The patch adds a new "linenr" field to the struct fragment in order to record which line the hunk started in the input file, but this is needed solely for reporting purposes. The detection of this class of whitespace errors cannot be done while parsing a patch like we do for all the other classes of whitespace errors. It instead has to wait until we find where to apply the hunk, but at that point, we do not have an access to the original line number in the input file anymore, hence the new field. Depending on your point of view, this may be a bugfix that makes warn and error in line with fix. Or you could call it a new feature. The line between them is somewhat fuzzy in this case. Strictly speaking, triggering more errors than before is a change in behaviour that is not backward compatible, even though the reason for the change is because the code was not checking for an error that it should have. People who do not want added blank lines at EOF to trigger an error can disable the new error class. Signed-off-by: Junio C Hamano --- Documentation/config.txt | 2 ++ builtin-apply.c | 27 ++++++++++++++++++--------- cache.h | 3 ++- t/t4124-apply-ws-rule.sh | 26 ++++++++++++++++++++++++++ ws.c | 6 ++++++ 5 files changed, 54 insertions(+), 10 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 113d9d1438..871384eb73 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -389,6 +389,8 @@ core.whitespace:: error (enabled by default). * `indent-with-non-tab` treats a line that is indented with 8 or more space characters as an error (not enabled by default). +* `blank-at-eof` treats blank lines added at the end of file as an error + (enabled by default). * `cr-at-eol` treats a carriage-return at the end of line as part of the line terminator, i.e. with it, `trailing-space` does not trigger if the character before such a carriage-return diff --git a/builtin-apply.c b/builtin-apply.c index 80ddf55ba5..37d3bc069b 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -126,6 +126,7 @@ struct fragment { const char *patch; int size; int rejected; + int linenr; struct fragment *next; }; @@ -1193,6 +1194,7 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc int len; fragment = xcalloc(1, sizeof(*fragment)); + fragment->linenr = linenr; len = parse_fragment(line, size, patch, fragment); if (len <= 0) die("corrupt patch at line %d", linenr); @@ -2079,17 +2081,24 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, } if (applied_pos >= 0) { - if (ws_error_action == correct_ws_error && - new_blank_lines_at_end && - preimage.nr + applied_pos == img->nr) { + if (new_blank_lines_at_end && + preimage.nr + applied_pos == img->nr && + (ws_rule & WS_BLANK_AT_EOF) && + ws_error_action != nowarn_ws_error) { + record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr); + if (ws_error_action == correct_ws_error) { + while (new_blank_lines_at_end--) + remove_last_line(&postimage); + } /* - * If the patch application adds blank lines - * at the end, and if the patch applies at the - * end of the image, remove those added blank - * lines. + * We would want to prevent write_out_results() + * from taking place in apply_patch() that follows + * the callchain led us here, which is: + * apply_patch->check_patch_list->check_patch-> + * apply_data->apply_fragments->apply_one_fragment */ - while (new_blank_lines_at_end--) - remove_last_line(&postimage); + if (ws_error_action == die_on_ws_error) + apply = 0; } /* diff --git a/cache.h b/cache.h index 099a32e5fc..7152feaef2 100644 --- a/cache.h +++ b/cache.h @@ -845,7 +845,8 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i #define WS_SPACE_BEFORE_TAB 02 #define WS_INDENT_WITH_NON_TAB 04 #define WS_CR_AT_EOL 010 -#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) +#define WS_BLANK_AT_EOF 020 +#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|WS_BLANK_AT_EOF) extern unsigned whitespace_rule_cfg; extern unsigned whitespace_rule(const char *); extern unsigned parse_whitespace_rule(const char *); diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index fedc8b9277..3a77a9af87 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -201,4 +201,30 @@ test_expect_success 'blank at end of hunk, not at EOF with --whitespace=fix' ' test_cmp expect one ' +test_expect_success 'blank at EOF with --whitespace=warn' ' + { echo a; echo b; echo c; } >one && + git add one && + echo >>one && + cat one >expect && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=warn patch 2>error && + test_cmp expect one && + grep "new blank line at EOF" error +' + +test_expect_success 'blank at EOF with --whitespace=error' ' + { echo a; echo b; echo c; } >one && + git add one && + cat one >expect && + echo >>one && + git diff -- one >patch && + + git checkout one && + test_must_fail git apply --whitespace=error patch 2>error && + test_cmp expect one && + grep "new blank line at EOF" error +' + test_done diff --git a/ws.c b/ws.c index 7a7ff130a3..d56636be7b 100644 --- a/ws.c +++ b/ws.c @@ -15,6 +15,7 @@ static struct whitespace_rule { { "space-before-tab", WS_SPACE_BEFORE_TAB }, { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB }, { "cr-at-eol", WS_CR_AT_EOL }, + { "blank-at-eof", WS_BLANK_AT_EOF }, }; unsigned parse_whitespace_rule(const char *string) @@ -113,6 +114,11 @@ char *whitespace_error_string(unsigned ws) strbuf_addstr(&err, ", "); strbuf_addstr(&err, "indent with spaces"); } + if (ws & WS_BLANK_AT_EOF) { + if (err.len) + strbuf_addstr(&err, ", "); + strbuf_addstr(&err, "new blank line at EOF"); + } return strbuf_detach(&err, NULL); } From 94ea026b358f9fd5395d9344a80f2d5a50e93e1f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Sep 2009 02:25:57 -0700 Subject: [PATCH 043/884] apply --whitespace: warn blank but not necessarily empty lines at EOF The whitespace error of adding blank lines at the end of file should trigger if you added a non-empty line at the end, if the contents of the line is full of whitespaces. Signed-off-by: Junio C Hamano --- builtin-apply.c | 6 ++++-- t/t4124-apply-ws-rule.sh | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 37d3bc069b..6662cc438c 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1957,7 +1957,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, is_blank_context = 1; break; case ' ': - if (plen && patch[1] == '\n') + if (plen && (ws_rule & WS_BLANK_AT_EOF) && + ws_blank_line(patch + 1, plen, ws_rule)) is_blank_context = 1; case '-': memcpy(old, patch + 1, plen); @@ -1985,7 +1986,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, (first == '+' ? 0 : LINE_COMMON)); new += added; if (first == '+' && - added == 1 && new[-1] == '\n') + (ws_rule & WS_BLANK_AT_EOF) && + ws_blank_line(patch + 1, plen, ws_rule)) added_blank_line = 1; break; case '@': case '\\': diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 3a77a9af87..778d45bc07 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -227,4 +227,17 @@ test_expect_success 'blank at EOF with --whitespace=error' ' grep "new blank line at EOF" error ' +test_expect_success 'blank but not empty at EOF' ' + { echo a; echo b; echo c; } >one && + git add one && + echo " " >>one && + cat one >expect && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=warn patch 2>error && + test_cmp expect one && + grep "new blank line at EOF" error +' + test_done From b8d9c1a66b99ad3ca8069add010dafdd1bc6cab8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 23:59:25 -0700 Subject: [PATCH 044/884] diff.c: the builtin_diff() deals with only two-file comparison The combined diff is implemented in combine_diff() and fn_out_consume() codepath never has to deal with anything but two-file comparision. Drop nparents from the emit_callback structure and simplify the code. Signed-off-by: Junio C Hamano --- diff.c | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/diff.c b/diff.c index 6fea3c0347..1eddd590ec 100644 --- a/diff.c +++ b/diff.c @@ -489,7 +489,7 @@ typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); struct emit_callback { struct xdiff_emit_state xm; - int nparents, color_diff; + int color_diff; unsigned ws_rule; sane_truncate_fn truncate; const char **label_path; @@ -549,9 +549,8 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons emit_line(ecbdata->file, set, reset, line, len); else { /* Emit just the prefix, then the rest. */ - emit_line(ecbdata->file, set, reset, line, ecbdata->nparents); - ws_check_emit(line + ecbdata->nparents, - len - ecbdata->nparents, ecbdata->ws_rule, + emit_line(ecbdata->file, set, reset, line, 1); + ws_check_emit(line + 1, len - 1, ecbdata->ws_rule, ecbdata->file, set, reset, ws); } } @@ -576,7 +575,6 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u static void fn_out_consume(void *priv, char *line, unsigned long len) { - int i; int color; struct emit_callback *ecbdata = priv; const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); @@ -598,13 +596,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } - /* This is not really necessary for now because - * this codepath only deals with two-way diffs. - */ - for (i = 0; i < len && line[i] == '@'; i++) - ; - if (2 <= i && i < len && line[i] == ' ') { - ecbdata->nparents = i - 1; + if (line[0] == '@') { len = sane_truncate_line(ecbdata, line, len); emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), @@ -614,15 +606,12 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) return; } - if (len < ecbdata->nparents) { + if (len < 1) { emit_line(ecbdata->file, reset, reset, line, len); return; } color = DIFF_PLAIN; - if (ecbdata->diff_words && ecbdata->nparents != 1) - /* fall back to normal diff */ - free_diff_words_data(ecbdata); if (ecbdata->diff_words) { if (line[0] == '-') { diff_words_append(line, len, @@ -641,13 +630,10 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) emit_line(ecbdata->file, plain, reset, line, len); return; } - for (i = 0; i < ecbdata->nparents && len; i++) { - if (line[i] == '-') - color = DIFF_FILE_OLD; - else if (line[i] == '+') - color = DIFF_FILE_NEW; - } - + if (line[0] == '-') + color = DIFF_FILE_OLD; + else if (line[0] == '+') + color = DIFF_FILE_NEW; if (color != DIFF_FILE_NEW) { emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, color), From 5b5061efd88e1d113a4484369dfab654b43364de Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 22:30:27 -0700 Subject: [PATCH 045/884] diff --whitespace=warn/error: obey blank-at-eof The "diff --check" code used to conflate trailing-space whitespace error class with this, but now we have a proper separate error class, we should check it under blank-at-eof, not trailing-space. The whitespace error is not about _having_ blank lines at end, but about adding _new_ blank lines. To keep the message consistent with what is given by "git apply", call whitespace_error_string() to generate it, instead of using a hardcoded custom message. Signed-off-by: Junio C Hamano --- diff.c | 10 +++++++--- t/t4015-diff-whitespace.sh | 4 ++-- t/t4019-diff-wserror.sh | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/diff.c b/diff.c index 1eddd590ec..a693d184c9 100644 --- a/diff.c +++ b/diff.c @@ -1650,10 +1650,14 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, ecb.priv = &data; xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); - if ((data.ws_rule & WS_TRAILING_SPACE) && + if ((data.ws_rule & WS_BLANK_AT_EOF) && data.trailing_blanks_start) { - fprintf(o->file, "%s:%d: ends with blank lines.\n", - data.filename, data.trailing_blanks_start); + static char *err; + + if (!err) + err = whitespace_error_string(WS_BLANK_AT_EOF); + fprintf(o->file, "%s:%d: %s\n", + data.filename, data.trailing_blanks_start, err); data.status = 1; /* report errors */ } } diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index b1cbd36d17..a5d4461574 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -335,10 +335,10 @@ test_expect_success 'line numbers in --check output are correct' ' ' -test_expect_success 'checkdiff detects trailing blank lines' ' +test_expect_success 'checkdiff detects new trailing blank lines (1)' ' echo "foo();" >x && echo "" >>x && - git diff --check | grep "ends with blank" + git diff --check | grep "new blank line" ' test_expect_success 'checkdiff allows new blank lines' ' diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 84a1fe3115..1517fff9c6 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -165,7 +165,7 @@ test_expect_success 'trailing empty lines (1)' ' rm -f .gitattributes && test_must_fail git diff --check >output && - grep "ends with blank lines." output && + grep "new blank line at" output && grep "trailing whitespace" output ' From 467babf8d059caee9587567452fc8b46505b4e67 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 23:39:43 -0700 Subject: [PATCH 046/884] diff --whitespace=warn/error: fix blank-at-eof check The "diff --check" logic used to share the same issue as the one fixed for "git apply" earlier in this series, in that a patch that adds new blank lines at end could appear as @@ -l,5 +m,7 @@$ _context$ _context$ -deleted$ +$ +$ +$ _$ _$ where _ stands for SP and $ shows a end-of-line. Instead of looking at each line in the patch in the callback, simply count the blank lines from the end in two versions, and notice the presence of new ones. Signed-off-by: Junio C Hamano --- diff.c | 64 ++++++++++++++++++++++++++++---------- t/t4015-diff-whitespace.sh | 7 +++++ 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/diff.c b/diff.c index a693d184c9..c19c4760fe 100644 --- a/diff.c +++ b/diff.c @@ -1149,7 +1149,6 @@ struct checkdiff_t { struct diff_options *o; unsigned ws_rule; unsigned status; - int trailing_blanks_start; }; static int is_conflict_marker(const char *line, unsigned long len) @@ -1193,10 +1192,6 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) if (line[0] == '+') { unsigned bad; data->lineno++; - if (!ws_blank_line(line + 1, len - 1, data->ws_rule)) - data->trailing_blanks_start = 0; - else if (!data->trailing_blanks_start) - data->trailing_blanks_start = data->lineno; if (is_conflict_marker(line + 1, len - 1)) { data->status |= 1; fprintf(data->o->file, @@ -1216,14 +1211,12 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) data->o->file, set, reset, ws); } else if (line[0] == ' ') { data->lineno++; - data->trailing_blanks_start = 0; } else if (line[0] == '@') { char *plus = strchr(line, '+'); if (plus) data->lineno = strtol(plus, NULL, 10) - 1; else die("invalid diff"); - data->trailing_blanks_start = 0; } } @@ -1437,6 +1430,44 @@ static const struct funcname_pattern_entry *diff_funcname_pattern(struct diff_fi return NULL; } +static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) +{ + char *ptr = mf->ptr; + long size = mf->size; + int cnt = 0; + + if (!size) + return cnt; + ptr += size - 1; /* pointing at the very end */ + if (*ptr != '\n') + ; /* incomplete line */ + else + ptr--; /* skip the last LF */ + while (mf->ptr < ptr) { + char *prev_eol; + for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--) + if (*prev_eol == '\n') + break; + if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule)) + break; + cnt++; + ptr = prev_eol - 1; + } + return cnt; +} + +static int adds_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, unsigned ws_rule) +{ + int l1, l2, at; + l1 = count_trailing_blank(mf1, ws_rule); + l2 = count_trailing_blank(mf2, ws_rule); + if (l2 <= l1) + return 0; + /* starting where? */ + at = count_lines(mf1->ptr, mf1->size); + return (at - l1) + 1; /* the line number counts from 1 */ +} + static void builtin_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -1650,15 +1681,16 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, ecb.priv = &data; xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); - if ((data.ws_rule & WS_BLANK_AT_EOF) && - data.trailing_blanks_start) { - static char *err; - - if (!err) - err = whitespace_error_string(WS_BLANK_AT_EOF); - fprintf(o->file, "%s:%d: %s\n", - data.filename, data.trailing_blanks_start, err); - data.status = 1; /* report errors */ + if (data.ws_rule & WS_BLANK_AT_EOF) { + int blank_at_eof = adds_blank_at_eof(&mf1, &mf2, data.ws_rule); + if (blank_at_eof) { + static char *err; + if (!err) + err = whitespace_error_string(WS_BLANK_AT_EOF); + fprintf(o->file, "%s:%d: %s.\n", + data.filename, blank_at_eof, err); + data.status = 1; /* report errors */ + } } } free_and_return: diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index a5d4461574..e0b481d42b 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -341,6 +341,13 @@ test_expect_success 'checkdiff detects new trailing blank lines (1)' ' git diff --check | grep "new blank line" ' +test_expect_success 'checkdiff detects new trailing blank lines (2)' ' + { echo a; echo b; echo; echo; } >x && + git add x && + { echo a; echo; echo; echo; echo; } >x && + git diff --check | grep "new blank line" +' + test_expect_success 'checkdiff allows new blank lines' ' git checkout x && mv x y && From 690ed8436326484fe7e3f4deac4cffd780c7d630 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Sep 2009 00:41:15 -0700 Subject: [PATCH 047/884] diff --color: color blank-at-eof Since the coloring logic processed the patch output one line at a time, we couldn't easily color code the new blank lines at the end of file. Reuse the adds_blank_at_eof() function to find where the runs of such blank lines start, keep track of the line number in the preimage while processing the patch output one line at a time, and paint the new blank lines that appear after that line to implement this. Signed-off-by: Junio C Hamano --- diff.c | 37 +++++++++++++++++++++++++++---------- t/t4019-diff-wserror.sh | 9 +++++++++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/diff.c b/diff.c index c19c4760fe..2b285b8ce9 100644 --- a/diff.c +++ b/diff.c @@ -491,6 +491,8 @@ struct emit_callback { struct xdiff_emit_state xm; int color_diff; unsigned ws_rule; + int blank_at_eof; + int lno_in_preimage; sane_truncate_fn truncate; const char **label_path; struct diff_words_data *diff_words; @@ -547,6 +549,12 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons if (!*ws) emit_line(ecbdata->file, set, reset, line, len); + else if ((ecbdata->ws_rule & WS_BLANK_AT_EOF) && + ecbdata->blank_at_eof && + (ecbdata->blank_at_eof <= ecbdata->lno_in_preimage) && + ws_blank_line(line + 1, len - 1, ecbdata->ws_rule)) + /* Blank line at EOF */ + emit_line(ecbdata->file, ws, reset, line, len); else { /* Emit just the prefix, then the rest. */ emit_line(ecbdata->file, set, reset, line, 1); @@ -573,9 +581,16 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u return allot - l; } +static int find_preimage_lno(const char *line) +{ + char *p = strchr(line, '-'); + if (!p) + return 0; /* should not happen */ + return strtol(p+1, NULL, 10); +} + static void fn_out_consume(void *priv, char *line, unsigned long len) { - int color; struct emit_callback *ecbdata = priv; const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); @@ -598,6 +613,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) if (line[0] == '@') { len = sane_truncate_line(ecbdata, line, len); + ecbdata->lno_in_preimage = find_preimage_lno(line); emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), reset, line, len); @@ -611,7 +627,6 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) return; } - color = DIFF_PLAIN; if (ecbdata->diff_words) { if (line[0] == '-') { diff_words_append(line, len, @@ -630,14 +645,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) emit_line(ecbdata->file, plain, reset, line, len); return; } - if (line[0] == '-') - color = DIFF_FILE_OLD; - else if (line[0] == '+') - color = DIFF_FILE_NEW; - if (color != DIFF_FILE_NEW) { - emit_line(ecbdata->file, - diff_get_color(ecbdata->color_diff, color), - reset, line, len); + + if (line[0] != '+') { + const char *color = + diff_get_color(ecbdata->color_diff, + line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN); + ecbdata->lno_in_preimage++; + emit_line(ecbdata->file, color, reset, line, len); return; } emit_add_line(reset, ecbdata, line, len); @@ -1557,6 +1571,9 @@ static void builtin_diff(const char *name_a, ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + if (ecbdata.ws_rule & WS_BLANK_AT_EOF) + ecbdata.blank_at_eof = + adds_blank_at_eof(&mf1, &mf2, ecbdata.ws_rule); ecbdata.file = o->file; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 1517fff9c6..1e75f1a110 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -190,4 +190,13 @@ test_expect_success 'do not color trailing cr in context' ' ' +test_expect_success 'color new trailing blank lines' ' + { echo a; echo b; echo; echo; } >x && + git add x && + { echo a; echo; echo; echo; echo; } >x && + git diff --color x >output && + cnt=$(grep "${blue_grep}" output | wc -l) && + test $cnt = 2 +' + test_done From 0cc08ff7ddaf4360966bdedf006af041cdf6f7d9 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Sat, 5 Sep 2009 22:06:46 +1000 Subject: [PATCH 048/884] gitk: Add a user preference to enable/disable use of themed widgets Also move the hide-remotes option up into the commit display options in the Edit->Preferences panel, since it affects the commit display more than the diff display. Signed-off-by: Paul Mackerras --- gitk | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/gitk b/gitk index 9d8afd839c..4d427e23c6 100755 --- a/gitk +++ b/gitk @@ -2606,7 +2606,7 @@ proc savestuff {w} { global cmitmode wrapcomment datetimeformat limitdiffs global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor global autoselect extdifftool perfile_attrs markbgcolor use_ttk - global hideremotes + global hideremotes want_ttk if {$stuffsaved} return if {![winfo viewable .]} return @@ -2630,6 +2630,7 @@ proc savestuff {w} { puts $f [list set showlocalchanges $showlocalchanges] puts $f [list set datetimeformat $datetimeformat] puts $f [list set limitdiffs $limitdiffs] + puts $f [list set want_ttk $want_ttk] puts $f [list set bgcolor $bgcolor] puts $f [list set fgcolor $fgcolor] puts $f [list set colors $colors] @@ -10586,7 +10587,7 @@ proc doprefs {} { global oldprefs prefstop showneartags showlocalchanges global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor global tabstop limitdiffs autoselect extdifftool perfile_attrs - global hideremotes + global hideremotes want_ttk have_ttk set top .gitkprefs set prefstop $top @@ -10595,7 +10596,7 @@ proc doprefs {} { return } foreach v {maxwidth maxgraphpct showneartags showlocalchanges \ - limitdiffs tabstop perfile_attrs hideremotes} { + limitdiffs tabstop perfile_attrs hideremotes want_ttk} { set oldprefs($v) [set $v] } ttk_toplevel $top @@ -10616,6 +10617,9 @@ proc doprefs {} { ${NS}::checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \ -variable autoselect grid x $top.autoselect -sticky w + ${NS}::checkbutton $top.hideremotes -text [mc "Hide remote refs"] \ + -variable hideremotes + grid x $top.hideremotes -sticky w ${NS}::label $top.ddisp -text [mc "Diff display options"] grid $top.ddisp - -sticky w -pady 10 @@ -10625,9 +10629,6 @@ proc doprefs {} { ${NS}::checkbutton $top.ntag -text [mc "Display nearby tags"] \ -variable showneartags grid x $top.ntag -sticky w - ${NS}::checkbutton $top.hideremotes -text [mc "Hide remote refs"] \ - -variable hideremotes - grid x $top.hideremotes -sticky w ${NS}::checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \ -variable limitdiffs grid x $top.ldiff -sticky w @@ -10643,6 +10644,17 @@ proc doprefs {} { pack configure $top.extdifff.l -padx 10 grid x $top.extdifff $top.extdifft -sticky ew + ${NS}::label $top.lgen -text [mc "General options"] + grid $top.lgen - -sticky w -pady 10 + ${NS}::checkbutton $top.want_ttk -variable want_ttk \ + -text [mc "Use themed widgets"] + if {$have_ttk} { + ${NS}::label $top.ttk_note -text [mc "(change requires restart)"] + } else { + ${NS}::label $top.ttk_note -text [mc "(currently unavailable)"] + } + grid x $top.want_ttk $top.ttk_note -sticky w + ${NS}::label $top.cdisp -text [mc "Colors: press to choose"] grid $top.cdisp - -sticky w -pady 10 label $top.bg -padx 40 -relief sunk -background $bgcolor @@ -10689,7 +10701,8 @@ proc doprefs {} { if {!$use_ttk} { foreach w {maxpctl maxwidthl showlocal autoselect tabstopl ntag ldiff lattr extdifff.l extdifff.b bgbut fgbut - diffoldbut diffnewbut hunksepbut markbgbut selbgbut} { + diffoldbut diffnewbut hunksepbut markbgbut selbgbut + want_ttk ttk_note} { $top.$w configure -font optionfont } } @@ -10760,7 +10773,7 @@ proc prefscan {} { global oldprefs prefstop foreach v {maxwidth maxgraphpct showneartags showlocalchanges \ - limitdiffs tabstop perfile_attrs hideremotes} { + limitdiffs tabstop perfile_attrs hideremotes want_ttk} { global $v set $v $oldprefs($v) } @@ -11236,6 +11249,7 @@ set limitdiffs 1 set datetimeformat "%Y-%m-%d %H:%M:%S" set autoselect 1 set perfile_attrs 0 +set want_ttk 1 if {[tk windowingsystem] eq "aqua"} { set extdifftool "opendiff" @@ -11372,10 +11386,12 @@ set nullid2 "0000000000000000000000000000000000000001" set nullfile "/dev/null" set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}] -if {![info exists use_ttk]} { - set use_ttk [llength [info commands ::ttk::style]] +if {![info exists have_ttk]} { + set have_ttk [llength [info commands ::ttk::style]] } +set use_ttk [expr {$have_ttk && $want_ttk}] set NS [expr {$use_ttk ? "ttk" : ""}] + set git_version [join [lrange [split [lindex [exec git version] end] .] 0 2] .] set runq {} From aeb84b05ae448596c336807631d9633492b3049a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 5 Sep 2009 22:21:17 -0700 Subject: [PATCH 049/884] core.whitespace: split trailing-space into blank-at-{eol,eof} People who configured trailing-space depended on it to catch both extra white space at the end of line, and extra blank lines at the end of file. Earlier attempt to introduce only blank-at-eof gave them an escape hatch to keep the old behaviour, but it is a regression until they explicitly specify the new error class. This introduces a blank-at-eol that only catches extra white space at the end of line, and makes the traditional trailing-space a convenient synonym to catch both blank-at-eol and blank-at-eof. This way, people who used trailing-space continue to catch both classes of errors. Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 +++- cache.h | 5 +++-- ws.c | 24 +++++++++++++++--------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 871384eb73..2a2f7fcdbb 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -382,7 +382,7 @@ core.whitespace:: consider them as errors. You can prefix `-` to disable any of them (e.g. `-trailing-space`): + -* `trailing-space` treats trailing whitespaces at the end of the line +* `blank-at-eol` treats trailing whitespaces at the end of the line as an error (enabled by default). * `space-before-tab` treats a space character that appears immediately before a tab character in the initial indent part of the line as an @@ -391,6 +391,8 @@ core.whitespace:: space characters as an error (not enabled by default). * `blank-at-eof` treats blank lines added at the end of file as an error (enabled by default). +* `trailing-space` is a short-hand to cover both `blank-at-eol` and + `blank-at-eof`. * `cr-at-eol` treats a carriage-return at the end of line as part of the line terminator, i.e. with it, `trailing-space` does not trigger if the character before such a carriage-return diff --git a/cache.h b/cache.h index 7152feaef2..ee12e74134 100644 --- a/cache.h +++ b/cache.h @@ -841,12 +841,13 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i * whitespace rules. * used by both diff and apply */ -#define WS_TRAILING_SPACE 01 +#define WS_BLANK_AT_EOL 01 #define WS_SPACE_BEFORE_TAB 02 #define WS_INDENT_WITH_NON_TAB 04 #define WS_CR_AT_EOL 010 #define WS_BLANK_AT_EOF 020 -#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|WS_BLANK_AT_EOF) +#define WS_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF) +#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) extern unsigned whitespace_rule_cfg; extern unsigned whitespace_rule(const char *); extern unsigned parse_whitespace_rule(const char *); diff --git a/ws.c b/ws.c index d56636be7b..cd03bc032a 100644 --- a/ws.c +++ b/ws.c @@ -15,6 +15,7 @@ static struct whitespace_rule { { "space-before-tab", WS_SPACE_BEFORE_TAB }, { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB }, { "cr-at-eol", WS_CR_AT_EOL }, + { "blank-at-eol", WS_BLANK_AT_EOL }, { "blank-at-eof", WS_BLANK_AT_EOF }, }; @@ -101,9 +102,19 @@ unsigned whitespace_rule(const char *pathname) char *whitespace_error_string(unsigned ws) { struct strbuf err; + strbuf_init(&err, 0); - if (ws & WS_TRAILING_SPACE) + if ((ws & WS_TRAILING_SPACE) == WS_TRAILING_SPACE) strbuf_addstr(&err, "trailing whitespace"); + else { + if (ws & WS_BLANK_AT_EOL) + strbuf_addstr(&err, "trailing whitespace"); + if (ws & WS_BLANK_AT_EOF) { + if (err.len) + strbuf_addstr(&err, ", "); + strbuf_addstr(&err, "new blank line at EOF"); + } + } if (ws & WS_SPACE_BEFORE_TAB) { if (err.len) strbuf_addstr(&err, ", "); @@ -114,11 +125,6 @@ char *whitespace_error_string(unsigned ws) strbuf_addstr(&err, ", "); strbuf_addstr(&err, "indent with spaces"); } - if (ws & WS_BLANK_AT_EOF) { - if (err.len) - strbuf_addstr(&err, ", "); - strbuf_addstr(&err, "new blank line at EOF"); - } return strbuf_detach(&err, NULL); } @@ -146,11 +152,11 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, } /* Check for trailing whitespace. */ - if (ws_rule & WS_TRAILING_SPACE) { + if (ws_rule & WS_BLANK_AT_EOL) { for (i = len - 1; i >= 0; i--) { if (isspace(line[i])) { trailing_whitespace = i; - result |= WS_TRAILING_SPACE; + result |= WS_BLANK_AT_EOL; } else break; @@ -266,7 +272,7 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro /* * Strip trailing whitespace */ - if ((ws_rule & WS_TRAILING_SPACE) && + if ((ws_rule & WS_BLANK_AT_EOL) && (2 <= len && isspace(src[len-2]))) { if (src[len - 1] == '\n') { add_nl_to_tail = 1; From 9b4fe22990dd3fab9494927935c371b072ee7c8f Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 5 Sep 2009 04:50:26 -0400 Subject: [PATCH 050/884] status: typo fix in usage Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-commit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-commit.c b/builtin-commit.c index 6cb0e40484..812470e63d 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -975,7 +975,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), OPT_BOOLEAN('s', "short", &shortstatus, - "show status concicely"), + "show status concisely"), OPT_BOOLEAN('z', "null", &null_termination, "terminate entries with NUL"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, From 01d8ba187d6e8a6bfa908fbef16a36d1186981dd Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 5 Sep 2009 04:53:48 -0400 Subject: [PATCH 051/884] status: refactor short-mode printing to its own function We want to be able to call it from multiple places. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-commit.c | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 812470e63d..5b42179fe7 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -966,11 +966,32 @@ static void short_untracked(int null_termination, struct string_list_item *it, } } +static void short_print(struct wt_status *s, int null_termination) +{ + int i; + for (i = 0; i < s->change.nr; i++) { + struct wt_status_change_data *d; + struct string_list_item *it; + + it = &(s->change.items[i]); + d = it->util; + if (d->stagemask) + short_unmerged(null_termination, it, s); + else + short_status(null_termination, it, s); + } + for (i = 0; i < s->untracked.nr; i++) { + struct string_list_item *it; + + it = &(s->untracked.items[i]); + short_untracked(null_termination, it, s); + } +} + int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; static int null_termination, shortstatus; - int i; unsigned char sha1[20]; static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), @@ -1003,25 +1024,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; wt_status_collect(&s); - if (shortstatus) { - for (i = 0; i < s.change.nr; i++) { - struct wt_status_change_data *d; - struct string_list_item *it; - - it = &(s.change.items[i]); - d = it->util; - if (d->stagemask) - short_unmerged(null_termination, it, &s); - else - short_status(null_termination, it, &s); - } - for (i = 0; i < s.untracked.nr; i++) { - struct string_list_item *it; - - it = &(s.untracked.items[i]); - short_untracked(null_termination, it, &s); - } - } else { + if (shortstatus) + short_print(&s, null_termination); + else { s.verbose = verbose; if (s.relative_paths) s.prefix = prefix; From dd2be243d62260d4c825c22fdd2f61a7da12de22 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 5 Sep 2009 04:54:14 -0400 Subject: [PATCH 052/884] status: refactor format option parsing This makes it possible to have more than two formats. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-commit.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 5b42179fe7..aa4a358799 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -991,12 +991,16 @@ static void short_print(struct wt_status *s, int null_termination) int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; - static int null_termination, shortstatus; + static int null_termination; + static enum { + STATUS_FORMAT_LONG, + STATUS_FORMAT_SHORT, + } status_format = STATUS_FORMAT_LONG; unsigned char sha1[20]; static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), - OPT_BOOLEAN('s', "short", &shortstatus, - "show status concisely"), + OPT_SET_INT('s', "short", &status_format, + "show status concisely", STATUS_FORMAT_SHORT), OPT_BOOLEAN('z', "null", &null_termination, "terminate entries with NUL"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, @@ -1006,8 +1010,8 @@ int cmd_status(int argc, const char **argv, const char *prefix) OPT_END(), }; - if (null_termination) - shortstatus = 1; + if (null_termination && status_format == STATUS_FORMAT_LONG) + status_format = STATUS_FORMAT_SHORT; wt_status_prepare(&s); git_config(git_status_config, &s); @@ -1024,9 +1028,11 @@ int cmd_status(int argc, const char **argv, const char *prefix) s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; wt_status_collect(&s); - if (shortstatus) + switch (status_format) { + case STATUS_FORMAT_SHORT: short_print(&s, null_termination); - else { + break; + case STATUS_FORMAT_LONG: s.verbose = verbose; if (s.relative_paths) s.prefix = prefix; @@ -1035,6 +1041,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) if (diff_use_color_default == -1) diff_use_color_default = git_use_color_default; wt_status_print(&s); + break; } return 0; } From 6f15787181a163e158c6fee1d79085b97692ac2f Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 5 Sep 2009 04:55:37 -0400 Subject: [PATCH 053/884] status: add --porcelain output format The "short" format was added to "git status" recently to provide a less verbose way of looking at the same information. This has two practical uses: 1. Users who want a more dense display of the information. 2. Scripts which want to parse the information and need a stable, easy-to-parse interface. For now, the "--short" format covers both of those uses. However, as time goes on, users of (1) may want additional format tweaks, or for "git status" to change its behavior based on configuration variables. Those wishes will be at odds with (2), which wants to stability for scripts. This patch introduces a separate --porcelain option early to avoid problems later on. Right now the --short and --porcelain outputs are identical. However, as time goes on, we will have the freedom to customize --short for human consumption while keeping --porcelain stable. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-status.txt | 9 +++++++-- builtin-commit.c | 9 ++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index b5939d6b58..e9363d9978 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -27,6 +27,11 @@ OPTIONS --short:: Give the output in the short-format. +--porcelain:: + Give the output in a stable, easy-to-parse format for scripts. + Currently this is identical to --short output, but is guaranteed + not to change in the future, making it safe for scripts. + -u[]:: --untracked-files[=]:: Show untracked files (Default: 'all'). @@ -45,8 +50,8 @@ used to change the default for when the option is not specified. -z:: - Terminate entries with NUL, instead of LF. This implies `-s` - (short status) output format. + Terminate entries with NUL, instead of LF. This implies + the `--porcelain` output format if no other format is given. OUTPUT diff --git a/builtin-commit.c b/builtin-commit.c index aa4a358799..ffdee31bbf 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -995,12 +995,16 @@ int cmd_status(int argc, const char **argv, const char *prefix) static enum { STATUS_FORMAT_LONG, STATUS_FORMAT_SHORT, + STATUS_FORMAT_PORCELAIN, } status_format = STATUS_FORMAT_LONG; unsigned char sha1[20]; static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), OPT_SET_INT('s', "short", &status_format, "show status concisely", STATUS_FORMAT_SHORT), + OPT_SET_INT(0, "porcelain", &status_format, + "show porcelain output format", + STATUS_FORMAT_PORCELAIN), OPT_BOOLEAN('z', "null", &null_termination, "terminate entries with NUL"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, @@ -1011,7 +1015,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) }; if (null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_SHORT; + status_format = STATUS_FORMAT_PORCELAIN; wt_status_prepare(&s); git_config(git_status_config, &s); @@ -1032,6 +1036,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) case STATUS_FORMAT_SHORT: short_print(&s, null_termination); break; + case STATUS_FORMAT_PORCELAIN: + short_print(&s, null_termination); + break; case STATUS_FORMAT_LONG: s.verbose = verbose; if (s.relative_paths) From 7c9f7038e923e6eb135b27c6fca9a010b034bc27 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 5 Sep 2009 04:59:56 -0400 Subject: [PATCH 054/884] commit: support alternate status formats The status command recently grew "short" and "porcelain" options for alternate output formats. Since status is no longer "commit --dry-run", these formats are inaccessible to people who do want to see a dry-run in a parseable form. This patch makes those formats available to "git commit", implying the "dry-run" option when they are used. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-commit.txt | 14 +++++++++++++ builtin-commit.c | 39 +++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 64f94cfe12..c45fbe4f97 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -75,6 +75,20 @@ OPTIONS and paths that are untracked, similar to the one that is given in the commit log editor. +--short:: + When doing a dry-run, give the output in the short-format. See + linkgit:git-status[1] for details. Implies `--dry-run`. + +--porcelain:: + When doing a dry-run, give the output in a porcelain-ready + format. See linkgit:git-status[1] for details. Implies + `--dry-run`. + +-z:: + When showing `short` or `porcelain` status output, terminate + entries in the status output with NUL, instead of LF. If no + format is given, implies the `--porcelain` output format. + -F :: --file=:: Take the commit message from the given file. Use '-' to diff --git a/builtin-commit.c b/builtin-commit.c index ffdee31bbf..f2fd0a4580 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -72,6 +72,15 @@ static int use_editor = 1, initial_commit, in_merge; static const char *only_include_assumed; static struct strbuf message; +static int null_termination; +static enum { + STATUS_FORMAT_LONG, + STATUS_FORMAT_SHORT, + STATUS_FORMAT_PORCELAIN, +} status_format = STATUS_FORMAT_LONG; + +static void short_print(struct wt_status *s, int null_termination); + static int opt_parse_m(const struct option *opt, const char *arg, int unset) { struct strbuf *buf = opt->value; @@ -105,6 +114,12 @@ static struct option builtin_commit_options[] = { OPT_BOOLEAN('o', "only", &only, "commit only specified files"), OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"), + OPT_SET_INT(0, "short", &status_format, "show status concisely", + STATUS_FORMAT_SHORT), + OPT_SET_INT(0, "porcelain", &status_format, + "show porcelain output format", STATUS_FORMAT_PORCELAIN), + OPT_BOOLEAN('z', "null", &null_termination, + "terminate entries with NUL"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"), @@ -363,7 +378,18 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; wt_status_collect(s); - wt_status_print(s); + + switch (status_format) { + case STATUS_FORMAT_SHORT: + short_print(s, null_termination); + break; + case STATUS_FORMAT_PORCELAIN: + short_print(s, null_termination); + break; + case STATUS_FORMAT_LONG: + wt_status_print(s); + break; + } return s->commitable; } @@ -821,6 +847,11 @@ static int parse_and_validate_options(int argc, const char *argv[], else if (interactive && argc > 0) die("Paths with --interactive does not make sense."); + if (null_termination && status_format == STATUS_FORMAT_LONG) + status_format = STATUS_FORMAT_PORCELAIN; + if (status_format != STATUS_FORMAT_LONG) + dry_run = 1; + return argc; } @@ -991,12 +1022,6 @@ static void short_print(struct wt_status *s, int null_termination) int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; - static int null_termination; - static enum { - STATUS_FORMAT_LONG, - STATUS_FORMAT_SHORT, - STATUS_FORMAT_PORCELAIN, - } status_format = STATUS_FORMAT_LONG; unsigned char sha1[20]; static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), From 46b77a6b487fceeeb297a9473631939aefd7e6fd Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 5 Sep 2009 04:52:18 -0400 Subject: [PATCH 055/884] docs: note that status configuration affects only long format The short format does not respect any of the usual status.* configuration. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-status.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index e9363d9978..58d35fb3c2 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -114,13 +114,13 @@ compatibility) and `color.status.` configuration variables to colorize its output. If the config variable `status.relativePaths` is set to false, then all -paths shown are relative to the repository root, not to the current -directory. +paths shown in the long format are relative to the repository root, not +to the current directory. If `status.submodulesummary` is set to a non zero number or true (identical -to -1 or an unlimited number), the submodule summary will be enabled and a -summary of commits for modified submodules will be shown (see --summary-limit -option of linkgit:git-submodule[1]). +to -1 or an unlimited number), the submodule summary will be enabled for +the long format and a summary of commits for modified submodules will be +shown (see --summary-limit option of linkgit:git-submodule[1]). SEE ALSO -------- From 4a3da5d91a4768a8abe6a8c64f1c3a49125a5bcf Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Mon, 7 Sep 2009 10:08:21 +1000 Subject: [PATCH 056/884] gitk: Show diff of commits at end of compare-commits output When comparing a string of commits, when we find two non-merge commits that differ, we now write the two commits to files and diff the files. This pulls out the logic for creating a temporary directory from external_diff into a separate procedure so that the new diffcommits procedure can use it. Because the diff command returns an exit status of 1 when the files differ, and Tcl treats that as an error, this adds catch {} around the close statements in getblobdiffline. At present this only removes the temporary files when gitk exits. It should remove them when the diff is done. Signed-off-by: Paul Mackerras --- gitk | 81 +++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 23 deletions(-) diff --git a/gitk b/gitk index 4d427e23c6..34067b9ecc 100755 --- a/gitk +++ b/gitk @@ -3278,6 +3278,28 @@ proc flist_hl {only} { set gdttype [mc "touching paths:"] } +proc gitknewtmpdir {} { + global diffnum gitktmpdir gitdir + + if {![info exists gitktmpdir]} { + set gitktmpdir [file join [file dirname $gitdir] \ + [format ".gitk-tmp.%s" [pid]]] + if {[catch {file mkdir $gitktmpdir} err]} { + error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err" + unset gitktmpdir + return {} + } + set diffnum 0 + } + incr diffnum + set diffdir [file join $gitktmpdir $diffnum] + if {[catch {file mkdir $diffdir} err]} { + error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err" + return {} + } + return $diffdir +} + proc save_file_from_commit {filename output what} { global nullfile @@ -3312,11 +3334,10 @@ proc external_diff_get_one_file {diffid filename diffdir} { } proc external_diff {} { - global gitktmpdir nullid nullid2 + global nullid nullid2 global flist_menu_file global diffids - global diffnum - global gitdir extdifftool + global extdifftool if {[llength $diffids] == 1} { # no reference commit given @@ -3338,22 +3359,8 @@ proc external_diff {} { } # make sure that several diffs wont collide - if {![info exists gitktmpdir]} { - set gitktmpdir [file join [file dirname $gitdir] \ - [format ".gitk-tmp.%s" [pid]]] - if {[catch {file mkdir $gitktmpdir} err]} { - error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err" - unset gitktmpdir - return - } - set diffnum 0 - } - incr diffnum - set diffdir [file join $gitktmpdir $diffnum] - if {[catch {file mkdir $diffdir} err]} { - error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err" - return - } + set diffdir [gitknewtmpdir] + if {$diffdir eq {}} return # gather files to diff set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir] @@ -7573,7 +7580,7 @@ proc getblobdiffline {bdf ids} { $ctext conf -state normal while {[incr nr] <= 1000 && [gets $bdf line] >= 0} { if {$ids != $diffids || $bdf != $blobdifffd($ids)} { - close $bdf + catch {close $bdf} return 0 } if {![string compare -length 5 "diff " $line]} { @@ -7726,7 +7733,7 @@ proc getblobdiffline {bdf ids} { maybe_scroll_ctext [eof $bdf] $ctext conf -state disabled if {[eof $bdf]} { - close $bdf + catch {close $bdf} return 0 } return [expr {$nr >= 1000? 2: 1}] @@ -8448,8 +8455,11 @@ proc do_cmp_commits {a b} { appendshortlink $a [mc "Commit "] " $heada\n" appendshortlink $b [mc " differs from\n "] \ " $headb\n" - $ctext insert end [mc "- stopping\n"] - break + $ctext insert end [mc "Diff of commits:\n\n"] + $ctext conf -state disabled + update + diffcommits $a $b + return } } if {$skipa} { @@ -8475,6 +8485,31 @@ proc do_cmp_commits {a b} { $ctext conf -state disabled } +proc diffcommits {a b} { + global diffcontext diffids blobdifffd diffinhdr + + set tmpdir [gitknewtmpdir] + set fna [file join $tmpdir "commit-[string range $a 0 7]"] + set fnb [file join $tmpdir "commit-[string range $b 0 7]"] + if {[catch { + exec git diff-tree -p --pretty $a >$fna + exec git diff-tree -p --pretty $b >$fnb + } err]} { + error_popup [mc "Error writing commit to file: %s" $err] + return + } + if {[catch { + set fd [open "| diff -U$diffcontext $fna $fnb" r] + } err]} { + error_popup [mc "Error diffing commits: %s" $err] + return + } + set diffids [list commits $a $b] + set blobdifffd($diffids) $fd + set diffinhdr 0 + filerun $fd [list getblobdiffline $fd $diffids] +} + proc diffvssel {dirn} { global rowmenuid selectedline From e4b48eaab724d9fd5941c6a683a34ee38a864897 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 7 Sep 2009 14:40:00 +0200 Subject: [PATCH 057/884] gitweb: Add 'show-sizes' feature to show blob sizes in tree view Add support for 'show-sizes' feature to show (in separate column, between mode and filename) the size of blobs (files) in the 'tree' view. It passes '-l' option to "git ls-tree" invocation. For the 'tree' and 'commit' (submodule) entries, '-' is shown in place of size; for generated '..' "up directory" entry nothing is shown. The 'show-sizes' feature is enabled by default. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.css | 6 ++++ gitweb/gitweb.perl | 69 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 8f68fe3091..d60bfc1f64 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -341,6 +341,12 @@ td.mode { font-family: monospace; } +/* format of (optional) objects size in 'tree' view */ +td.size { + font-family: monospace; + text-align: right; +} + /* styling of diffs (patchsets): commitdiff and blobdiff views */ div.diff.header, div.diff.extended_header { diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 24b219310a..7b1c60e2c1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -297,6 +297,19 @@ our %feature = ( 'override' => 0, 'default' => [1]}, + # Enable showing size of blobs in a 'tree' view, in a separate + # column, similar to what 'ls -l' does. This cost a bit of IO. + + # To disable system wide have in $GITWEB_CONFIG + # $feature{'show-sizes'}{'default'} = [0]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'show-sizes'}{'override'} = 1; + # and in project config gitweb.showsizes = 0|1; + 'show-sizes' => { + 'sub' => sub { feature_bool('showsizes', @_) }, + 'override' => 0, + 'default' => [1]}, + # Make gitweb use an alternative format of the URLs which can be # more readable and natural-looking: project name is embedded # directly in the path and the query string contains other @@ -2764,16 +2777,31 @@ sub parse_ls_tree_line { my %opts = @_; my %res; - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s; + if ($opts{'-l'}) { + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa 16717 panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s; - $res{'mode'} = $1; - $res{'type'} = $2; - $res{'hash'} = $3; - if ($opts{'-z'}) { - $res{'name'} = $4; + $res{'mode'} = $1; + $res{'type'} = $2; + $res{'hash'} = $3; + $res{'size'} = $4; + if ($opts{'-z'}) { + $res{'name'} = $5; + } else { + $res{'name'} = unquote($5); + } } else { - $res{'name'} = unquote($4); + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s; + + $res{'mode'} = $1; + $res{'type'} = $2; + $res{'hash'} = $3; + if ($opts{'-z'}) { + $res{'name'} = $4; + } else { + $res{'name'} = unquote($4); + } } return wantarray ? %res : \%res; @@ -3564,6 +3592,9 @@ sub git_print_tree_entry { # and link is the action links of the entry. print "" . mode_str($t->{'mode'}) . "\n"; + if (exists $t->{'size'}) { + print "$t->{'size'}\n"; + } if ($t->{'type'} eq "blob") { print "" . $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, @@ -3609,12 +3640,14 @@ sub git_print_tree_entry { } elsif ($t->{'type'} eq "tree") { print ""; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, + file_name=>"$basedir$t->{'name'}", + %base_key)}, esc_path($t->{'name'})); print "\n"; print ""; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, + file_name=>"$basedir$t->{'name'}", + %base_key)}, "tree"); if (defined $hash_base) { print " | " . @@ -5088,10 +5121,14 @@ sub git_tree { } die_error(404, "No such tree") unless defined($hash); + my $show_sizes = gitweb_check_feature('show-sizes'); + my $have_blame = gitweb_check_feature('blame'); + my @entries = (); { local $/ = "\0"; - open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash + open my $fd, "-|", git_cmd(), "ls-tree", '-z', + ($show_sizes ? '-l' : ()), @extra_options, $hash or die_error(500, "Open git-ls-tree failed"); @entries = map { chomp; $_ } <$fd>; close $fd @@ -5102,7 +5139,6 @@ sub git_tree { my $ref = format_ref_marker($refs, $hash_base); git_header_html(); my $basedir = ''; - my $have_blame = gitweb_check_feature('blame'); if (defined $hash_base && (my %co = parse_commit($hash_base))) { my @views_nav = (); if (defined $file_name) { @@ -5118,7 +5154,8 @@ sub git_tree { # FIXME: Should be available when we have no hash base as well. push @views_nav, $snapshot_links; } - git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); + git_print_page_nav('tree','', $hash_base, undef, undef, + join(' | ', @views_nav)); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { undef $hash_base; @@ -5151,8 +5188,10 @@ sub git_tree { undef $up unless $up; # based on git_print_tree_entry print '' . mode_str('040000') . "\n"; + print ' '."\n" if $show_sizes; print ''; - print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base, + print $cgi->a({-href => href(action=>"tree", + hash_base=>$hash_base, file_name=>$up)}, ".."); print "\n"; @@ -5161,7 +5200,7 @@ sub git_tree { print "\n"; } foreach my $line (@entries) { - my %t = parse_ls_tree_line($line, -z => 1); + my %t = parse_ls_tree_line($line, -z => 1, -l => $show_sizes); if ($alternate) { print "\n"; From aa43561ac0c18dd6d200afe3a02e4e6c7e4beeae Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Thu, 10 Sep 2009 21:58:40 +1000 Subject: [PATCH 058/884] gitk: Don't compare fake children when comparing commits This fixes a bug where the compare-commits function would advance to a fake node (one representing local changes, either checked in but not committed, or not checked in) and then get an error when trying to get the patch-id. This fixes it by only considering the real children of each commit. Reported-by: Stephen Rothwell Signed-off-by: Paul Mackerras --- gitk | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/gitk b/gitk index 34067b9ecc..c0f38adb9b 100755 --- a/gitk +++ b/gitk @@ -2,7 +2,7 @@ # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" -# Copyright © 2005-2008 Paul Mackerras. All rights reserved. +# Copyright © 2005-2009 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. @@ -989,6 +989,18 @@ proc removefakerow {id} { drawvisible } +proc real_children {vp} { + global children nullid nullid2 + + set kids {} + foreach id $children($vp) { + if {$id ne $nullid && $id ne $nullid2} { + lappend kids $id + } + } + return $kids +} + proc first_real_child {vp} { global children nullid nullid2 @@ -8463,23 +8475,23 @@ proc do_cmp_commits {a b} { } } if {$skipa} { - if {[llength $children($curview,$a)] != 1} { + set kids [real_children $curview,$a] + if {[llength $kids] != 1} { $ctext insert end "\n" appendshortlink $a [mc "Commit "] \ - [mc " has %s children - stopping\n" \ - [llength $children($curview,$a)]] + [mc " has %s children - stopping\n" [llength $kids]] break } - set a [lindex $children($curview,$a) 0] + set a [lindex $kids 0] } if {$skipb} { - if {[llength $children($curview,$b)] != 1} { + set kids [real_children $curview,$b] + if {[llength $kids] != 1} { appendshortlink $b [mc "Commit "] \ - [mc " has %s children - stopping\n" \ - [llength $children($curview,$b)]] + [mc " has %s children - stopping\n" [llength $kids]] break } - set b [lindex $children($curview,$b) 0] + set b [lindex $kids 0] } } $ctext conf -state disabled From d68fe26f3e03b230ac9bbbcf002a9acdc4bebde9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 22:05:57 -0700 Subject: [PATCH 059/884] diff --whitespace: fix blank lines at end The earlier logic tried to colour any and all blank lines that were added beyond the last blank line in the original, but this was very wrong. If you added 96 blank lines, a non-blank line, and then 3 blank lines at the end, only the last 3 lines should trigger the error, not the earlier 96 blank lines. We need to also make sure that the lines are after the last non-blank line in the postimage as well before deciding to paint them. Signed-off-by: Junio C Hamano --- diff.c | 74 +++++++++++++++++++++++++++++------------ t/t4019-diff-wserror.sh | 2 +- 2 files changed, 54 insertions(+), 22 deletions(-) diff --git a/diff.c b/diff.c index 2b285b8ce9..63a3bfc333 100644 --- a/diff.c +++ b/diff.c @@ -491,8 +491,10 @@ struct emit_callback { struct xdiff_emit_state xm; int color_diff; unsigned ws_rule; - int blank_at_eof; + int blank_at_eof_in_preimage; + int blank_at_eof_in_postimage; int lno_in_preimage; + int lno_in_postimage; sane_truncate_fn truncate; const char **label_path; struct diff_words_data *diff_words; @@ -542,6 +544,17 @@ static void emit_line(FILE *file, const char *set, const char *reset, const char fputc('\n', file); } +static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) +{ + if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && + ecbdata->blank_at_eof_in_preimage && + ecbdata->blank_at_eof_in_postimage && + ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && + ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) + return 0; + return ws_blank_line(line + 1, len - 1, ecbdata->ws_rule); +} + static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) { const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); @@ -549,11 +562,8 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons if (!*ws) emit_line(ecbdata->file, set, reset, line, len); - else if ((ecbdata->ws_rule & WS_BLANK_AT_EOF) && - ecbdata->blank_at_eof && - (ecbdata->blank_at_eof <= ecbdata->lno_in_preimage) && - ws_blank_line(line + 1, len - 1, ecbdata->ws_rule)) - /* Blank line at EOF */ + else if (new_blank_line_at_eof(ecbdata, line, len)) + /* Blank line at EOF - paint '+' as well */ emit_line(ecbdata->file, ws, reset, line, len); else { /* Emit just the prefix, then the rest. */ @@ -581,12 +591,19 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u return allot - l; } -static int find_preimage_lno(const char *line) +static void find_lno(const char *line, struct emit_callback *ecbdata) { - char *p = strchr(line, '-'); + const char *p; + ecbdata->lno_in_preimage = 0; + ecbdata->lno_in_postimage = 0; + p = strchr(line, '-'); if (!p) - return 0; /* should not happen */ - return strtol(p+1, NULL, 10); + return; /* cannot happen */ + ecbdata->lno_in_preimage = strtol(p + 1, NULL, 10); + p = strchr(p, '+'); + if (!p) + return; /* cannot happen */ + ecbdata->lno_in_postimage = strtol(p + 1, NULL, 10); } static void fn_out_consume(void *priv, char *line, unsigned long len) @@ -613,7 +630,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) if (line[0] == '@') { len = sane_truncate_line(ecbdata, line, len); - ecbdata->lno_in_preimage = find_preimage_lno(line); + find_lno(line, ecbdata); emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), reset, line, len); @@ -651,10 +668,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) diff_get_color(ecbdata->color_diff, line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN); ecbdata->lno_in_preimage++; + if (line[0] == ' ') + ecbdata->lno_in_postimage++; emit_line(ecbdata->file, color, reset, line, len); - return; + } else { + ecbdata->lno_in_postimage++; + emit_add_line(reset, ecbdata, line, len); } - emit_add_line(reset, ecbdata, line, len); } static char *pprint_rename(const char *a, const char *b) @@ -1470,16 +1490,23 @@ static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) return cnt; } -static int adds_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, unsigned ws_rule) +static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, + struct emit_callback *ecbdata) { int l1, l2, at; + unsigned ws_rule = ecbdata->ws_rule; l1 = count_trailing_blank(mf1, ws_rule); l2 = count_trailing_blank(mf2, ws_rule); - if (l2 <= l1) - return 0; - /* starting where? */ + if (l2 <= l1) { + ecbdata->blank_at_eof_in_preimage = 0; + ecbdata->blank_at_eof_in_postimage = 0; + return; + } at = count_lines(mf1->ptr, mf1->size); - return (at - l1) + 1; /* the line number counts from 1 */ + ecbdata->blank_at_eof_in_preimage = (at - l1) + 1; + + at = count_lines(mf2->ptr, mf2->size); + ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; } static void builtin_diff(const char *name_a, @@ -1572,8 +1599,7 @@ static void builtin_diff(const char *name_a, ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); if (ecbdata.ws_rule & WS_BLANK_AT_EOF) - ecbdata.blank_at_eof = - adds_blank_at_eof(&mf1, &mf2, ecbdata.ws_rule); + check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.file = o->file; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; @@ -1699,7 +1725,13 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); if (data.ws_rule & WS_BLANK_AT_EOF) { - int blank_at_eof = adds_blank_at_eof(&mf1, &mf2, data.ws_rule); + struct emit_callback ecbdata; + int blank_at_eof; + + ecbdata.ws_rule = data.ws_rule; + check_blank_at_eof(&mf1, &mf2, &ecbdata); + blank_at_eof = ecbdata.blank_at_eof_in_preimage; + if (blank_at_eof) { static char *err; if (!err) diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 1e75f1a110..3a3663fbcb 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -193,7 +193,7 @@ test_expect_success 'do not color trailing cr in context' ' test_expect_success 'color new trailing blank lines' ' { echo a; echo b; echo; echo; } >x && git add x && - { echo a; echo; echo; echo; echo; } >x && + { echo a; echo; echo; echo; echo c; echo; echo; echo; echo; } >x && git diff --color x >output && cnt=$(grep "${blue_grep}" output | wc -l) && test $cnt = 2 From 6957eb9a39cc765862e125edeef0dd70f359cff1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 18:44:01 -0700 Subject: [PATCH 060/884] diff.c: shuffling code around Move function, type, and structure definitions for fill_mmfile(), count_trailing_blank(), check_blank_at_eof(), emit_line(), new_blank_line_at_eof(), emit_add_line(), sane_truncate_fn, and emit_callback up in the file, so that they can be refactored into helper functions and reused by codepath for emitting rewrite patches. This only moves the lines around to make the next two patches easier to read. Signed-off-by: Junio C Hamano --- diff.c | 250 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 125 insertions(+), 125 deletions(-) diff --git a/diff.c b/diff.c index 63a3bfc333..75489665ba 100644 --- a/diff.c +++ b/diff.c @@ -241,6 +241,23 @@ static struct diff_tempfile { char tmp_path[PATH_MAX]; } diff_temp[2]; +typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); + +struct emit_callback { + struct xdiff_emit_state xm; + int color_diff; + unsigned ws_rule; + int blank_at_eof_in_preimage; + int blank_at_eof_in_postimage; + int lno_in_preimage; + int lno_in_postimage; + sane_truncate_fn truncate; + const char **label_path; + struct diff_words_data *diff_words; + int *found_changesp; + FILE *file; +}; + static int count_lines(const char *data, int size) { int count, ch, completely_empty = 1, nl_just_seen = 0; @@ -301,6 +318,114 @@ static void copy_file_with_prefix(FILE *file, fprintf(file, "%s\n\\ No newline at end of file\n", reset); } +static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) +{ + if (!DIFF_FILE_VALID(one)) { + mf->ptr = (char *)""; /* does not matter */ + mf->size = 0; + return 0; + } + else if (diff_populate_filespec(one, 0)) + return -1; + mf->ptr = one->data; + mf->size = one->size; + return 0; +} + +static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) +{ + char *ptr = mf->ptr; + long size = mf->size; + int cnt = 0; + + if (!size) + return cnt; + ptr += size - 1; /* pointing at the very end */ + if (*ptr != '\n') + ; /* incomplete line */ + else + ptr--; /* skip the last LF */ + while (mf->ptr < ptr) { + char *prev_eol; + for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--) + if (*prev_eol == '\n') + break; + if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule)) + break; + cnt++; + ptr = prev_eol - 1; + } + return cnt; +} + +static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, + struct emit_callback *ecbdata) +{ + int l1, l2, at; + unsigned ws_rule = ecbdata->ws_rule; + l1 = count_trailing_blank(mf1, ws_rule); + l2 = count_trailing_blank(mf2, ws_rule); + if (l2 <= l1) { + ecbdata->blank_at_eof_in_preimage = 0; + ecbdata->blank_at_eof_in_postimage = 0; + return; + } + at = count_lines(mf1->ptr, mf1->size); + ecbdata->blank_at_eof_in_preimage = (at - l1) + 1; + + at = count_lines(mf2->ptr, mf2->size); + ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; +} + +static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) +{ + int has_trailing_newline, has_trailing_carriage_return; + + has_trailing_newline = (len > 0 && line[len-1] == '\n'); + if (has_trailing_newline) + len--; + has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); + if (has_trailing_carriage_return) + len--; + + fputs(set, file); + fwrite(line, len, 1, file); + fputs(reset, file); + if (has_trailing_carriage_return) + fputc('\r', file); + if (has_trailing_newline) + fputc('\n', file); +} + +static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) +{ + if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && + ecbdata->blank_at_eof_in_preimage && + ecbdata->blank_at_eof_in_postimage && + ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && + ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) + return 0; + return ws_blank_line(line + 1, len - 1, ecbdata->ws_rule); +} + +static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) +{ + const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); + const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); + + if (!*ws) + emit_line(ecbdata->file, set, reset, line, len); + else if (new_blank_line_at_eof(ecbdata, line, len)) + /* Blank line at EOF - paint '+' as well */ + emit_line(ecbdata->file, ws, reset, line, len); + else { + /* Emit just the prefix, then the rest. */ + emit_line(ecbdata->file, set, reset, line, 1); + ws_check_emit(line + 1, len - 1, ecbdata->ws_rule, + ecbdata->file, set, reset, ws); + } +} + static void emit_rewrite_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -345,20 +470,6 @@ static void emit_rewrite_diff(const char *name_a, copy_file_with_prefix(o->file, '+', two->data, two->size, new, reset); } -static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) -{ - if (!DIFF_FILE_VALID(one)) { - mf->ptr = (char *)""; /* does not matter */ - mf->size = 0; - return 0; - } - else if (diff_populate_filespec(one, 0)) - return -1; - mf->ptr = one->data; - mf->size = one->size; - return 0; -} - struct diff_words_buffer { mmfile_t text; long alloc; @@ -485,23 +596,6 @@ static void diff_words_show(struct diff_words_data *diff_words) } } -typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); - -struct emit_callback { - struct xdiff_emit_state xm; - int color_diff; - unsigned ws_rule; - int blank_at_eof_in_preimage; - int blank_at_eof_in_postimage; - int lno_in_preimage; - int lno_in_postimage; - sane_truncate_fn truncate; - const char **label_path; - struct diff_words_data *diff_words; - int *found_changesp; - FILE *file; -}; - static void free_diff_words_data(struct emit_callback *ecbdata) { if (ecbdata->diff_words) { @@ -524,55 +618,6 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) return ""; } -static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) -{ - int has_trailing_newline, has_trailing_carriage_return; - - has_trailing_newline = (len > 0 && line[len-1] == '\n'); - if (has_trailing_newline) - len--; - has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); - if (has_trailing_carriage_return) - len--; - - fputs(set, file); - fwrite(line, len, 1, file); - fputs(reset, file); - if (has_trailing_carriage_return) - fputc('\r', file); - if (has_trailing_newline) - fputc('\n', file); -} - -static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) -{ - if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && - ecbdata->blank_at_eof_in_preimage && - ecbdata->blank_at_eof_in_postimage && - ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && - ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) - return 0; - return ws_blank_line(line + 1, len - 1, ecbdata->ws_rule); -} - -static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) -{ - const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); - const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); - - if (!*ws) - emit_line(ecbdata->file, set, reset, line, len); - else if (new_blank_line_at_eof(ecbdata, line, len)) - /* Blank line at EOF - paint '+' as well */ - emit_line(ecbdata->file, ws, reset, line, len); - else { - /* Emit just the prefix, then the rest. */ - emit_line(ecbdata->file, set, reset, line, 1); - ws_check_emit(line + 1, len - 1, ecbdata->ws_rule, - ecbdata->file, set, reset, ws); - } -} - static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len) { const char *cp; @@ -1464,51 +1509,6 @@ static const struct funcname_pattern_entry *diff_funcname_pattern(struct diff_fi return NULL; } -static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) -{ - char *ptr = mf->ptr; - long size = mf->size; - int cnt = 0; - - if (!size) - return cnt; - ptr += size - 1; /* pointing at the very end */ - if (*ptr != '\n') - ; /* incomplete line */ - else - ptr--; /* skip the last LF */ - while (mf->ptr < ptr) { - char *prev_eol; - for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--) - if (*prev_eol == '\n') - break; - if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule)) - break; - cnt++; - ptr = prev_eol - 1; - } - return cnt; -} - -static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, - struct emit_callback *ecbdata) -{ - int l1, l2, at; - unsigned ws_rule = ecbdata->ws_rule; - l1 = count_trailing_blank(mf1, ws_rule); - l2 = count_trailing_blank(mf2, ws_rule); - if (l2 <= l1) { - ecbdata->blank_at_eof_in_preimage = 0; - ecbdata->blank_at_eof_in_postimage = 0; - return; - } - at = count_lines(mf1->ptr, mf1->size); - ecbdata->blank_at_eof_in_preimage = (at - l1) + 1; - - at = count_lines(mf2->ptr, mf2->size); - ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; -} - static void builtin_diff(const char *name_a, const char *name_b, struct diff_filespec *one, From 250f79930d84af54dce15320914ea911d58dd8ca Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 18:44:01 -0700 Subject: [PATCH 061/884] diff.c: split emit_line() from the first char and the rest of the line A new helper function emit_line_0() takes the first line of diff output (typically "-", " ", or "+") separately from the remainder of the line. No other functional changes. This change will make it easier to reuse the logic when emitting the rewrite diff, as we do not want to copy a line only to add "+"/"-"/" " immediately before its first character when we produce rewrite diff output. Signed-off-by: Junio C Hamano --- diff.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/diff.c b/diff.c index 75489665ba..e4aaebf0d0 100644 --- a/diff.c +++ b/diff.c @@ -377,18 +377,31 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; } -static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) +static void emit_line_0(FILE *file, const char *set, const char *reset, + int first, const char *line, int len) { int has_trailing_newline, has_trailing_carriage_return; + int nofirst; - has_trailing_newline = (len > 0 && line[len-1] == '\n'); - if (has_trailing_newline) - len--; - has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); - if (has_trailing_carriage_return) - len--; + if (len == 0) { + has_trailing_newline = (first == '\n'); + has_trailing_carriage_return = (!has_trailing_newline && + (first == '\r')); + nofirst = has_trailing_newline || has_trailing_carriage_return; + } else { + has_trailing_newline = (len > 0 && line[len-1] == '\n'); + if (has_trailing_newline) + len--; + has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); + if (has_trailing_carriage_return) + len--; + nofirst = 0; + } fputs(set, file); + + if (!nofirst) + fputc(first, file); fwrite(line, len, 1, file); fputs(reset, file); if (has_trailing_carriage_return) @@ -397,6 +410,12 @@ static void emit_line(FILE *file, const char *set, const char *reset, const char fputc('\n', file); } +static void emit_line(FILE *file, const char *set, const char *reset, + const char *line, int len) +{ + emit_line_0(file, set, reset, line[0], line+1, len-1); +} + static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) { if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && From 018cff70462eb59779c96a383531c4440fb35b9c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 18:44:01 -0700 Subject: [PATCH 062/884] diff.c: emit_add_line() takes only the rest of the line As the first character on the line that is fed to this function is always "+", it is pointless to send that along with the rest of the line. This change will make it easier to reuse the logic when emitting the rewrite diff, as we do not want to copy a line only to add "+"/"-"/" " immediately before its first character when we produce rewrite diff output. Signed-off-by: Junio C Hamano --- diff.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/diff.c b/diff.c index e4aaebf0d0..07cf04365c 100644 --- a/diff.c +++ b/diff.c @@ -424,23 +424,25 @@ static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) return 0; - return ws_blank_line(line + 1, len - 1, ecbdata->ws_rule); + return ws_blank_line(line, len, ecbdata->ws_rule); } -static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) +static void emit_add_line(const char *reset, + struct emit_callback *ecbdata, + const char *line, int len) { const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); if (!*ws) - emit_line(ecbdata->file, set, reset, line, len); + emit_line_0(ecbdata->file, set, reset, '+', line, len); else if (new_blank_line_at_eof(ecbdata, line, len)) /* Blank line at EOF - paint '+' as well */ - emit_line(ecbdata->file, ws, reset, line, len); + emit_line_0(ecbdata->file, ws, reset, '+', line, len); else { /* Emit just the prefix, then the rest. */ - emit_line(ecbdata->file, set, reset, line, 1); - ws_check_emit(line + 1, len - 1, ecbdata->ws_rule, + emit_line_0(ecbdata->file, set, reset, '+', "", 0); + ws_check_emit(line, len, ecbdata->ws_rule, ecbdata->file, set, reset, ws); } } @@ -737,7 +739,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) emit_line(ecbdata->file, color, reset, line, len); } else { ecbdata->lno_in_postimage++; - emit_add_line(reset, ecbdata, line, len); + emit_add_line(reset, ecbdata, line + 1, len - 1); } } From 7f7ee2ff2dfbb8435a4b46750f573ef0f7d0b853 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 18:44:01 -0700 Subject: [PATCH 063/884] diff -B: colour whitespace errors We used to send the old and new contents more or less straight out to the output with only the original "old is red, new is green" colouring. Now all the necessary support routines have been prepared, call them with a line of data at a time from the output code and have them check and color whitespace errors in exactly the same way as they are called from the low level diff callback routines. Signed-off-by: Junio C Hamano --- diff.c | 75 ++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/diff.c b/diff.c index 07cf04365c..8414478032 100644 --- a/diff.c +++ b/diff.c @@ -296,28 +296,6 @@ static void print_line_count(FILE *file, int count) } } -static void copy_file_with_prefix(FILE *file, - int prefix, const char *data, int size, - const char *set, const char *reset) -{ - int ch, nl_just_seen = 1; - while (0 < size--) { - ch = *data++; - if (nl_just_seen) { - fputs(set, file); - putc(prefix, file); - } - if (ch == '\n') { - nl_just_seen = 1; - fputs(reset, file); - } else - nl_just_seen = 0; - putc(ch, file); - } - if (!nl_just_seen) - fprintf(file, "%s\n\\ No newline at end of file\n", reset); -} - static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) { if (!DIFF_FILE_VALID(one)) { @@ -447,6 +425,38 @@ static void emit_add_line(const char *reset, } } +static void emit_rewrite_lines(struct emit_callback *ecb, + int prefix, const char *data, int size) +{ + const char *endp = NULL; + static const char *nneof = " No newline at end of file\n"; + const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD); + const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET); + + while (0 < size) { + int len; + + endp = memchr(data, '\n', size); + len = endp ? (endp - data + 1) : size; + if (prefix != '+') { + ecb->lno_in_preimage++; + emit_line_0(ecb->file, old, reset, '-', + data, len); + } else { + ecb->lno_in_postimage++; + emit_add_line(reset, ecb, data, len); + } + size -= len; + data += len; + } + if (!endp) { + const char *plain = diff_get_color(ecb->color_diff, + DIFF_PLAIN); + emit_line_0(ecb->file, plain, reset, '\\', + nneof, strlen(nneof)); + } +} + static void emit_rewrite_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -458,10 +468,23 @@ static void emit_rewrite_diff(const char *name_a, const char *name_a_tab, *name_b_tab; const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO); const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO); - const char *old = diff_get_color(color_diff, DIFF_FILE_OLD); - const char *new = diff_get_color(color_diff, DIFF_FILE_NEW); const char *reset = diff_get_color(color_diff, DIFF_RESET); static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT; + struct emit_callback ecbdata; + + memset(&ecbdata, 0, sizeof(ecbdata)); + ecbdata.color_diff = color_diff; + ecbdata.found_changesp = &o->found_changes; + ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + ecbdata.file = o->file; + if (ecbdata.ws_rule & WS_BLANK_AT_EOF) { + mmfile_t mf1, mf2; + fill_mmfile(&mf1, one); + fill_mmfile(&mf2, two); + check_blank_at_eof(&mf1, &mf2, &ecbdata); + } + ecbdata.lno_in_preimage = 1; + ecbdata.lno_in_postimage = 1; name_a += (*name_a == '/'); name_b += (*name_b == '/'); @@ -486,9 +509,9 @@ static void emit_rewrite_diff(const char *name_a, print_line_count(o->file, lc_b); fprintf(o->file, " @@%s\n", reset); if (lc_a) - copy_file_with_prefix(o->file, '-', one->data, one->size, old, reset); + emit_rewrite_lines(&ecbdata, '-', one->data, one->size); if (lc_b) - copy_file_with_prefix(o->file, '+', two->data, two->size, new, reset); + emit_rewrite_lines(&ecbdata, '+', two->data, two->size); } struct diff_words_buffer { From e984c54ada086e7676419b6b1b8a2d7a2429da48 Mon Sep 17 00:00:00 2001 From: Julian Phillips Date: Thu, 17 Sep 2009 08:33:19 +0100 Subject: [PATCH 064/884] fetch: Speed up fetch by rewriting find_non_local_tags When trying to get a list of remote tags to see if we need to fetch any we were doing a linear search for the matching tag ref for the tag^{} commit entries. This proves to be incredibly slow for large numbers of tags. Rewrite the function so that we build up a string_list of refs to fetch and then process that instead. As an extreme example, for a repository with 50000 tags (and just a single commit on a single branch), a fetch that does nothing goes from ~1m50s to ~4.1s. Signed-off-by: Julian Phillips Signed-off-by: Junio C Hamano --- builtin-fetch.c | 107 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 33 deletions(-) diff --git a/builtin-fetch.c b/builtin-fetch.c index cb48c57ca3..a35a6f8cb8 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -504,57 +504,98 @@ static int will_fetch(struct ref **head, const unsigned char *sha1) return 0; } +struct tag_data { + struct ref **head; + struct ref ***tail; +}; + +static int add_to_tail(struct string_list_item *item, void *cb_data) +{ + struct tag_data *data = (struct tag_data *)cb_data; + struct ref *rm = NULL; + + /* We have already decided to ignore this item */ + if (!item->util) + return 0; + + rm = alloc_ref(item->string); + rm->peer_ref = alloc_ref(item->string); + hashcpy(rm->old_sha1, item->util); + + **data->tail = rm; + *data->tail = &rm->next; + + return 0; +} + static void find_non_local_tags(struct transport *transport, struct ref **head, struct ref ***tail) { struct string_list existing_refs = { NULL, 0, 0, 0 }; - struct string_list new_refs = { NULL, 0, 0, 1 }; - char *ref_name; - int ref_name_len; - const unsigned char *ref_sha1; - const struct ref *tag_ref; - struct ref *rm = NULL; + struct string_list remote_refs = { NULL, 0, 0, 0 }; + struct tag_data data = {head, tail}; const struct ref *ref; + struct string_list_item *item = NULL; for_each_ref(add_existing, &existing_refs); for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) { if (prefixcmp(ref->name, "refs/tags")) continue; - ref_name = xstrdup(ref->name); - ref_name_len = strlen(ref_name); - ref_sha1 = ref->old_sha1; - - if (!strcmp(ref_name + ref_name_len - 3, "^{}")) { - ref_name[ref_name_len - 3] = 0; - tag_ref = transport_get_remote_refs(transport); - while (tag_ref) { - if (!strcmp(tag_ref->name, ref_name)) { - ref_sha1 = tag_ref->old_sha1; - break; - } - tag_ref = tag_ref->next; - } + /* + * The peeled ref always follows the matching base + * ref, so if we see a peeled ref that we don't want + * to fetch then we can mark the ref entry in the list + * as one to ignore by setting util to NULL. + */ + if (!strcmp(ref->name + strlen(ref->name) - 3, "^{}")) { + if (item && !has_sha1_file(ref->old_sha1) && + !will_fetch(head, ref->old_sha1) && + !has_sha1_file(item->util) && + !will_fetch(head, item->util)) + item->util = NULL; + item = NULL; + continue; } - if (!string_list_has_string(&existing_refs, ref_name) && - !string_list_has_string(&new_refs, ref_name) && - (has_sha1_file(ref->old_sha1) || - will_fetch(head, ref->old_sha1))) { - string_list_insert(ref_name, &new_refs); + /* + * If item is non-NULL here, then we previously saw a + * ref not followed by a peeled reference, so we need + * to check if it is a lightweight tag that we want to + * fetch. + */ + if (item && !has_sha1_file(item->util) && + !will_fetch(head, item->util)) + item->util = NULL; - rm = alloc_ref(ref_name); - rm->peer_ref = alloc_ref(ref_name); - hashcpy(rm->old_sha1, ref_sha1); + item = NULL; - **tail = rm; - *tail = &rm->next; - } - free(ref_name); + /* skip duplicates and refs that we already have */ + if (string_list_has_string(&remote_refs, ref->name) || + string_list_has_string(&existing_refs, ref->name)) + continue; + + item = string_list_insert(ref->name, &remote_refs); + item->util = (void *)ref->old_sha1; } string_list_clear(&existing_refs, 0); - string_list_clear(&new_refs, 0); + + /* + * We may have a final lightweight tag that needs to be + * checked to see if it needs fetching. + */ + if (item && !has_sha1_file(item->util) && + !will_fetch(head, item->util)) + item->util = NULL; + + /* + * For all the tags in the remote_refs string list, call + * add_to_tail to add them to the list of refs to be fetched + */ + for_each_string_list(add_to_tail, &remote_refs, &data); + + string_list_clear(&remote_refs, 0); } static void check_not_current_branch(struct ref *ref_map) From 1414e5788b85787a712a30977b388200f1bc04da Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Tue, 22 Sep 2009 17:10:12 +0200 Subject: [PATCH 065/884] git submodule add: make the parameter optional When is not given, use the "humanish" part of the source repository instead. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- Documentation/git-submodule.txt | 8 ++++++-- git-submodule.sh | 7 ++++++- t/t7400-submodule-basic.sh | 16 ++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 5ccdd18c89..4ef70c42eb 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git submodule' [--quiet] add [-b branch] - [--reference ] [--] + [--reference ] [--] [] 'git submodule' [--quiet] status [--cached] [--recursive] [--] [...] 'git submodule' [--quiet] init [--] [...] 'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase] @@ -69,7 +69,11 @@ add:: to the changeset to be committed next to the current project: the current project is termed the "superproject". + -This requires two arguments: and . +This requires at least one argument: . The optional +argument is the relative location for the cloned submodule +to exist in the superproject. If is not given, the +"humanish" part of the source repository is used ("repo" for +"/path/to/repo.git" and "foo" for "host.xz:foo/.git"). + is the URL of the new submodule's origin repository. This may be either an absolute URL, or (if it begins with ./ diff --git a/git-submodule.sh b/git-submodule.sh index bfbd36b6f4..0c617eb40d 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -5,7 +5,7 @@ # Copyright (c) 2007 Lars Hjemli dashless=$(basename "$0" | sed -e 's/-/ /') -USAGE="[--quiet] add [-b branch] [--reference ] [--] +USAGE="[--quiet] add [-b branch] [--reference ] [--] [] or: $dashless [--quiet] status [--cached] [--recursive] [--] [...] or: $dashless [--quiet] init [--] [...] or: $dashless [--quiet] update [--init] [-N|--no-fetch] [--rebase] [--reference ] [--merge] [--recursive] [--] [...] @@ -160,6 +160,11 @@ cmd_add() repo=$1 path=$2 + if test -z "$path"; then + path=$(echo "$repo" | + sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g') + fi + if test -z "$repo" -o -z "$path"; then usage fi diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 0f2ccc6cf0..a0cc99ab9f 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -306,4 +306,20 @@ test_expect_success 'submodule warns' ' ' +test_expect_success 'add submodules without specifying an explicit path' ' + mkdir repo && + cd repo && + git init && + echo r >r && + git add r && + git commit -m "repo commit 1" && + cd .. && + git clone --bare repo/ bare.git && + cd addtest && + git submodule add "$submodurl/repo" && + git config -f .gitmodules submodule.repo.path repo && + git submodule add "$submodurl/bare.git" && + git config -f .gitmodules submodule.bare.path bare +' + test_done From fdb0c36e903d13c184f9a465035c75565c5c072a Mon Sep 17 00:00:00 2001 From: Mark Rada Date: Sat, 26 Sep 2009 13:46:08 -0400 Subject: [PATCH 066/884] gitweb: check given hash before trying to create snapshot Makes things nicer in cases when you hand craft the snapshot URL but make a typo in defining the hash variable (e.g. netx instead of next); you will now get an error message instead of a broken tarball. Tests for t9501 are included to demonstrate added functionality. Signed-off-by: Mark Rada Signed-off-by: Shawn O. Pearce --- gitweb/gitweb.perl | 7 +++-- t/t9501-gitweb-standalone-http-status.sh | 39 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 24b219310a..8d4a2ae600 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -5196,8 +5196,11 @@ sub git_snapshot { die_error(403, "Unsupported snapshot format"); } - if (!defined $hash) { - $hash = git_get_head_hash($project); + my $type = git_get_type("$hash^{}"); + if (!$type) { + die_error(404, 'Object does not exist'); + } elsif ($type eq 'blob') { + die_error(400, 'Object is not a tree-ish'); } my $name = $project; diff --git a/t/t9501-gitweb-standalone-http-status.sh b/t/t9501-gitweb-standalone-http-status.sh index d0ff21d426..0688a57e1d 100644 --- a/t/t9501-gitweb-standalone-http-status.sh +++ b/t/t9501-gitweb-standalone-http-status.sh @@ -75,4 +75,43 @@ test_expect_success \ test_debug 'cat gitweb.output' +# ---------------------------------------------------------------------- +# snapshot hash ids + +test_expect_success 'snapshots: good tree-ish id' ' + gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" && + grep "Status: 200 OK" gitweb.output +' +test_debug 'cat gitweb.output' + +test_expect_success 'snapshots: bad tree-ish id' ' + gitweb_run "p=.git;a=snapshot;h=frizzumFrazzum;sf=tgz" && + grep "404 - Object does not exist" gitweb.output +' +test_debug 'cat gitweb.output' + +test_expect_success 'snapshots: bad tree-ish id (tagged object)' ' + echo object > tag-object && + git add tag-object && + git commit -m "Object to be tagged" && + git tag tagged-object `git hash-object tag-object` && + gitweb_run "p=.git;a=snapshot;h=tagged-object;sf=tgz" && + grep "400 - Object is not a tree-ish" gitweb.output +' +test_debug 'cat gitweb.output' + +test_expect_success 'snapshots: good object id' ' + ID=`git rev-parse --verify HEAD` && + gitweb_run "p=.git;a=snapshot;h=$ID;sf=tgz" && + grep "Status: 200 OK" gitweb.output +' +test_debug 'cat gitweb.output' + +test_expect_success 'snapshots: bad object id' ' + gitweb_run "p=.git;a=snapshot;h=abcdef01234;sf=tgz" && + grep "404 - Object does not exist" gitweb.output +' +test_debug 'cat gitweb.output' + + test_done From 10d1432aece21ac65a89fe962b1c3019ec1f46e0 Mon Sep 17 00:00:00 2001 From: Mark Rada Date: Sat, 26 Sep 2009 14:12:32 -0400 Subject: [PATCH 067/884] instaweb: support mod_cgid for apache2 Some people have mod_cgid instead of mod_cgi, most likely as a result of choosing a threaded MPM. In cases where the user has both modules, mod_cgi will be preferred in order to maintain a simpler setup. This patch also causes instaweb to print a message and die in cases where there is no module that instaweb knows how to handle. Signed-off-by: Mark Rada Signed-off-by: Shawn O. Pearce --- git-instaweb.sh | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/git-instaweb.sh b/git-instaweb.sh index d96eddbe56..622a5f0eb2 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -317,7 +317,21 @@ EOF resolve_full_httpd list_mods=$(echo "$full_httpd" | sed "s/-f$/-l/") $list_mods | grep 'mod_cgi\.c' >/dev/null 2>&1 || \ - echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf" + if test -f "$module_path/mod_cgi.so" + then + echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf" + else + $list_mods | grep 'mod_cgid\.c' >/dev/null 2>&1 || \ + if test -f "$module_path/mod_cgid.so" + then + echo "LoadModule cgid_module $module_path/mod_cgid.so" \ + >> "$conf" + else + echo "You have no CGI support!" + exit 2 + fi + echo "ScriptSock logs/gitweb.sock" >> "$conf" + fi cat >> "$conf" < From c5022f576aa583429c245054d8600564b788ff33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20K=C3=A5gedal?= Date: Tue, 29 Sep 2009 17:12:57 +0200 Subject: [PATCH 068/884] git-blame.el: Change how blame information is shown. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is more customizable, and uses a line prefix to show the commit. Signed-off-by: David Kågedal Signed-off-by: Shawn O. Pearce --- contrib/emacs/git-blame.el | 154 ++++++++++++++++++++++++------------- 1 file changed, 101 insertions(+), 53 deletions(-) diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el index 4fa70c5ad4..7f4c792978 100644 --- a/contrib/emacs/git-blame.el +++ b/contrib/emacs/git-blame.el @@ -80,6 +80,57 @@ (eval-when-compile (require 'cl)) ; to use `push', `pop' +(defface git-blame-prefix-face + '((((background dark)) (:foreground "gray" + :background "black")) + (((background light)) (:foreground "gray" + :background "white")) + (t (:weight bold))) + "The face used for the hash prefix." + :group 'git-blame) + +(defgroup git-blame nil + "A minor mode showing Git blame information." + :group 'git + :link '(function-link git-blame-mode)) + + +(defcustom git-blame-use-colors t + "Use colors to indicate commits in `git-blame-mode'." + :type 'boolean + :group 'git-blame) + +(defcustom git-blame-prefix-format + "%h %20A:" + "The format of the prefix added to each line in `git-blame' +mode. The format is passed to `format-spec' with the following format keys: + + %h - the abbreviated hash + %H - the full hash + %a - the author name + %A - the author email + %c - the committer name + %C - the committer email + %s - the commit summary +" + :group 'git-blame) + +(defcustom git-blame-mouseover-format + "%h %a %A: %s" + "The format of the description shown when pointing at a line in +`git-blame' mode. The format string is passed to `format-spec' +with the following format keys: + + %h - the abbreviated hash + %H - the full hash + %a - the author name + %A - the author email + %c - the committer name + %C - the committer email + %s - the commit summary +" + :group 'git-blame) + (defun git-blame-color-scale (&rest elements) "Given a list, returns a list of triples formed with each @@ -302,72 +353,69 @@ See also function `git-blame-mode'." (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 - (if (string= hash "0000000000000000000000000000000000000000") - nil - (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)) + (delete-region (point) (match-end 0)) + (setq git-blame-current (list (git-blame-new-commit hash) + src-line res-line num-lines))) 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)) + (delete-region (point) (match-end 0)) + (git-blame-add-info (car git-blame-current) key value) + (when (string= key "filename") + (git-blame-create-overlay (car git-blame-current) + (caddr git-blame-current) + (cadddr git-blame-current)) + (setq git-blame-current nil))) t) (t nil))) -(defun git-blame-new-commit (hash src-line res-line num-lines) +(defun git-blame-new-commit (hash) + (with-current-buffer git-blame-file + (or (gethash hash git-blame-cache) + ;; Assign a random color to each new commit info + ;; Take care not to select the same color multiple times + (let* ((color (if git-blame-colors + (git-blame-random-pop git-blame-colors) + git-blame-ancient-color)) + (info `(,hash (color . ,color)))) + (puthash hash info git-blame-cache) + info)))) + +(defun git-blame-create-overlay (info start-line num-lines) (save-excursion (set-buffer git-blame-file) - (let ((info (gethash hash git-blame-cache)) - (inhibit-point-motion-hooks t) + (let ((inhibit-point-motion-hooks t) (inhibit-modification-hooks t)) - (when (not info) - ;; Assign a random color to each new commit info - ;; Take care not to select the same color multiple times - (let ((color (if git-blame-colors - (git-blame-random-pop git-blame-colors) - git-blame-ancient-color))) - (setq info (list hash src-line res-line num-lines - (git-describe-commit hash) - (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) + (goto-line start-line) + (let* ((start (point)) + (end (progn (forward-line num-lines) (point))) + (ovl (make-overlay start end)) + (hash (car info)) + (spec `((?h . ,(substring hash 0 6)) + (?H . ,hash) + (?a . ,(git-blame-get-info info 'author)) + (?A . ,(git-blame-get-info info 'author-mail)) + (?c . ,(git-blame-get-info info 'committer)) + (?C . ,(git-blame-get-info info 'committer-mail)) + (?s . ,(git-blame-get-info info 'summary))))) + (push ovl git-blame-overlays) + (overlay-put ovl 'git-blame info) + (overlay-put ovl 'help-echo + (format-spec git-blame-mouseover-format spec)) + (if git-blame-use-colors (overlay-put ovl 'face (list :background - (cdr (assq 'color (nthcdr 5 info))))) - ;; the point-entered property doesn't seem to work in overlays - ;;(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)))))) + (cdr (assq 'color (cdr info)))))) + (overlay-put ovl 'line-prefix + (propertize (format-spec git-blame-prefix-format spec) + 'face 'git-blame-prefix-face)))))) -(defun git-blame-add-info (key value) - (if git-blame-current - (nconc git-blame-current (list (cons (intern key) value))))) +(defun git-blame-add-info (info key value) + (nconc info (list (cons (intern key) value)))) + +(defun git-blame-get-info (info key) + (cdr (assq key (cdr info)))) (defun git-blame-current-commit () (let ((info (get-char-property (point) 'git-blame))) From 6962b6b02c93c35163c25e07201f6219e5558f45 Mon Sep 17 00:00:00 2001 From: Thiago Farina Date: Wed, 23 Sep 2009 14:25:39 -0400 Subject: [PATCH 069/884] Documentation: update pt-BR Translate some english words to portuguese and fix some typos on translation. Signed-off-by: Thiago Farina Signed-off-by: Jeff King --- Documentation/pt_BR/gittutorial.txt | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Documentation/pt_BR/gittutorial.txt b/Documentation/pt_BR/gittutorial.txt index 81e7ad7df4..beba065252 100644 --- a/Documentation/pt_BR/gittutorial.txt +++ b/Documentation/pt_BR/gittutorial.txt @@ -1,15 +1,15 @@ gittutorial(7) ============== -NAME +NOME ---- gittutorial - Um tutorial de introdução ao git (para versão 1.5.1 ou mais nova) -SYNOPSIS +SINOPSE -------- git * -DESCRIPTION +DESCRIÇÃO ----------- Este tutorial explica como importar um novo projeto para o git, @@ -64,11 +64,11 @@ Git irá responder Initialized empty Git repository in .git/ ------------------------------------------------ -Você agora iniciou seu diretório de trabalho--você deve ter notado um -novo diretório criado, com o nome de ".git". +Agora que você iniciou seu diretório de trabalho, você deve ter notado que um +novo diretório foi criado com o nome de ".git". A seguir, diga ao git para gravar um instantâneo do conteúdo de todos os -arquivos sob o diretório corrente (note o '.'), com 'git-add': +arquivos sob o diretório atual (note o '.'), com 'git-add': ------------------------------------------------ $ git add . @@ -126,8 +126,8 @@ mudanças com: $ git commit ------------------------------------------------ -Isto irá novamente te pedir por uma mensagem descrevendo a mudança, e, -então, gravar a nova versão do projeto. +Ao executar esse comando, ele irá te pedir uma mensagem descrevendo a mudança, +e, então, irá gravar a nova versão do projeto. Alternativamente, ao invés de executar 'git-add' antes, você pode usar @@ -143,7 +143,7 @@ idéia começar a mensagem com uma simples e curta (menos de 50 caracteres) linha sumarizando a mudança, seguida de uma linha em branco e, então, uma descrição mais detalhada. Ferramentas que transformam commits em email, por exemplo, usam a primeira linha no campo de -cabeçalho Subject: e o resto no corpo. +cabeçalho "Subject:" e o resto no corpo. Git rastreia conteúdo, não arquivos ---------------------------- @@ -155,7 +155,7 @@ usado tanto para arquivos novos e arquivos recentemente modificados, e em ambos os casos, ele tira o instantâneo dos arquivos dados e armazena o conteúdo no índice, pronto para inclusão do próximo commit. -Visualizando história do projeto +Visualizando a história do projeto ----------------------- Em qualquer ponto você pode visualizar a história das suas mudanças @@ -165,7 +165,7 @@ usando $ git log ------------------------------------------------ -Se você também quer ver a diferença completa a cada passo, use +Se você também quiser ver a diferença completa a cada passo, use ------------------------------------------------ $ git log -p From 9fa708dab1ccf8be69a606ca4eb58e62f3ef334a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 4 Oct 2009 23:43:32 -0700 Subject: [PATCH 070/884] Pretty-format: %[+-]x to tweak inter-item newlines This teaches the "pretty" machinery to expand '%+x' to a LF followed by the expansion of '%x' if and only if '%x' expands to a non-empty string, and to remove LFs before '%-x' if '%x' expands to an empty string. This works for any supported expansion placeholder 'x'. This is expected to be immediately useful to reproduce the commit log message with "%s%+b%n"; "%s%n%b%n" adds one extra LF if the log message is a one-liner. Signed-off-by: Junio C Hamano --- Documentation/pretty-formats.txt | 8 ++++++ pretty.c | 42 ++++++++++++++++++++++++++++++-- t/t6006-rev-list-format.sh | 22 +++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 2a845b1e57..ca9c6d1f80 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -132,6 +132,14 @@ The placeholders are: - '%n': newline - '%x00': print a byte from a hex code +If you add a `{plus}` (plus sign) after '%' of a placeholder, a line-feed +is inserted immediately before the expansion if and only if the +placeholder expands to a non-empty string. + +If you add a `-` (minus sign) after '%' of a placeholder, line-feeds that +immediately precede the expansion are deleted if and only if the +placeholder expands to an empty string. + * 'tformat:' + The 'tformat:' format works exactly like 'format:', except that it diff --git a/pretty.c b/pretty.c index f5983f8baa..081feb6602 100644 --- a/pretty.c +++ b/pretty.c @@ -595,8 +595,8 @@ static void format_decoration(struct strbuf *sb, const struct commit *commit) strbuf_addch(sb, ')'); } -static size_t format_commit_item(struct strbuf *sb, const char *placeholder, - void *context) +static size_t format_commit_one(struct strbuf *sb, const char *placeholder, + void *context) { struct format_commit_context *c = context; const struct commit *commit = c->commit; @@ -739,6 +739,44 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, return 0; /* unknown placeholder */ } +static size_t format_commit_item(struct strbuf *sb, const char *placeholder, + void *context) +{ + int consumed; + size_t orig_len; + enum { + NO_MAGIC, + ADD_LF_BEFORE_NON_EMPTY, + DEL_LF_BEFORE_EMPTY, + } magic = NO_MAGIC; + + switch (placeholder[0]) { + case '-': + magic = DEL_LF_BEFORE_EMPTY; + break; + case '+': + magic = ADD_LF_BEFORE_NON_EMPTY; + break; + default: + break; + } + if (magic != NO_MAGIC) + placeholder++; + + orig_len = sb->len; + consumed = format_commit_one(sb, placeholder, context); + if (magic == NO_MAGIC) + return consumed; + + if ((orig_len == sb->len) && magic == DEL_LF_BEFORE_EMPTY) { + while (sb->len && sb->buf[sb->len - 1] == '\n') + strbuf_setlen(sb, sb->len - 1); + } else if ((orig_len != sb->len) && magic == ADD_LF_BEFORE_NON_EMPTY) { + strbuf_insert(sb, orig_len, "\n", 1); + } + return consumed + 1; +} + void format_commit_message(const struct commit *commit, const void *format, struct strbuf *sb, enum date_mode dmode) diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 59d1f6283b..18a77a739e 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -162,4 +162,26 @@ test_expect_success 'empty email' ' } ' +test_expect_success 'del LF before empty (1)' ' + git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD^^ >actual && + test $(wc -l actual && + test $(wc -l actual && + test $(wc -l actual && + test $(wc -l Date: Wed, 7 Oct 2009 08:13:23 +0200 Subject: [PATCH 071/884] Teach 'rebase -i' the command "reword" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make it easier to edit just the commit message for a commit using 'git rebase -i' by introducing the "reword" command. Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano --- Documentation/git-rebase.txt | 9 ++++++--- git-rebase--interactive.sh | 9 +++++++++ t/lib-rebase.sh | 6 +++--- t/t3404-rebase-interactive.sh | 14 ++++++++++++++ 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 0aefc34d0d..33e0ef1f6d 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -368,14 +368,17 @@ By replacing the command "pick" with the command "edit", you can tell the files and/or the commit message, amend the commit, and continue rebasing. +If you just want to edit the commit message for a commit, replace the +command "pick" with the command "reword". + If you want to fold two or more commits into one, replace the command "pick" with "squash" for the second and subsequent commit. If the commits had different authors, it will attribute the squashed commit to the author of the first commit. -In both cases, or when a "pick" does not succeed (because of merge -errors), the loop will stop to let you fix things, and you can continue -the loop with `git rebase --continue`. +'git-rebase' will stop when "pick" has been replaced with "edit" or +when a command fails due to merge errors. When you are done editing +and/or resolving conflicts you can continue with `git rebase --continue`. For example, if you want to reorder the last 5 commits, such that what was HEAD~4 becomes the new HEAD. To achieve that, you would call diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 23ded48322..a43ee22c64 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -340,6 +340,14 @@ do_next () { pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" ;; + reword|r) + comment_for_reflog reword + + mark_action_done + pick_one $sha1 || + die_with_patch $sha1 "Could not apply $sha1... $rest" + output git commit --amend + ;; edit|e) comment_for_reflog edit @@ -752,6 +760,7 @@ first and then run 'git rebase --continue' again." # # Commands: # p, pick = use commit +# r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 260a231933..62f452c8ea 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -9,8 +9,8 @@ # # "[] []..." # -# If a line number is prefixed with "squash" or "edit", the respective line's -# command will be replaced with the specified one. +# If a line number is prefixed with "squash", "edit", or "reword", the +# respective line's command will be replaced with the specified one. set_fake_editor () { echo "#!$SHELL_PATH" >fake-editor.sh @@ -32,7 +32,7 @@ cat "$1".tmp action=pick for line in $FAKE_LINES; do case $line in - squash|edit) + squash|edit|reword) action="$line";; *) echo sed -n "${line}s/^pick/$action/p" diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 4cae019521..3a37793c0d 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -470,4 +470,18 @@ test_expect_success 'avoid unnecessary reset' ' test 123456789 = $MTIME ' +test_expect_success 'reword' ' + git checkout -b reword-branch master && + FAKE_LINES="1 2 3 reword 4" FAKE_COMMIT_MESSAGE="E changed" git rebase -i A && + git show HEAD | grep "E changed" && + test $(git rev-parse master) != $(git rev-parse HEAD) && + test $(git rev-parse master^) = $(git rev-parse HEAD^) && + FAKE_LINES="1 2 reword 3 4" FAKE_COMMIT_MESSAGE="D changed" git rebase -i A && + git show HEAD^ | grep "D changed" && + FAKE_LINES="reword 1 2 3 4" FAKE_COMMIT_MESSAGE="B changed" git rebase -i A && + git show HEAD~3 | grep "B changed" && + FAKE_LINES="1 reword 2 3 4" FAKE_COMMIT_MESSAGE="C changed" git rebase -i A && + git show HEAD~2 | grep "C changed" +' + test_done From 1655c98790682aed8892eb8a9eb6d44e00d5f69f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 9 Oct 2009 14:26:44 +0200 Subject: [PATCH 072/884] gitweb: Do not show 'patch' link for merge commits The 'patch' view is about generating text/plain patch that can be given to "git am", and "git am" doesn't understand merges anyway. Therefore link to 'patch' view should not be shown for merge commits. Also call to git-format-patch inside the 'patch' action would fail when 'patch' action is called for a merge commit, with "Reading git-format-patch failed" text as 'patch' view body. Signed-off-by: Jakub Narebski 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 24b219310a..c939e2434d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -5328,7 +5328,7 @@ sub git_commit { } @$parents ) . ')'; } - if (gitweb_check_feature('patches')) { + if (gitweb_check_feature('patches') && @$parents <= 1) { $formats_nav .= " | " . $cgi->a({-href => href(action=>"patch", -replay=>1)}, "patch"); @@ -5616,7 +5616,7 @@ sub git_commitdiff { $formats_nav = $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)}, "raw"); - if ($patch_max) { + if ($patch_max && @{$co{'parents'}} <= 1) { $formats_nav .= " | " . $cgi->a({-href => href(action=>"patch", -replay=>1)}, "patch"); @@ -5824,7 +5824,7 @@ sub git_commitdiff_plain { # format-patch-style patches sub git_patch { - git_commitdiff(-format => 'patch', -single=> 1); + git_commitdiff(-format => 'patch', -single => 1); } sub git_patches { From 02461e0e2833753151ed71899f075c94e5fc495b Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Thu, 8 Oct 2009 10:03:26 -0700 Subject: [PATCH 073/884] git-send-email.perl: fold multiple entry "Cc:" and multiple single line "RCPT TO:"s Some MTAs reject Cc: lines longer than 78 chars. Avoid this by using the same join as "To:" ",\n\t" so each subsequent Cc entry is on a new line. RCPT TO: should have a single entry per line. see: http://www.ietf.org/rfc/rfc2821.txt Signed-off-by: Joe Perches Signed-off-by: Junio C Hamano --- git-send-email.perl | 6 ++- t/t9001-send-email.sh | 94 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 78 insertions(+), 22 deletions(-) diff --git a/git-send-email.perl b/git-send-email.perl index 0700d80afc..e188a89495 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -835,7 +835,7 @@ sub send_message $gitversion = Git::version(); } - my $cc = join(", ", unique_email_list(@cc)); + my $cc = join(",\n\t", unique_email_list(@cc)); my $ccline = ""; if ($cc ne '') { $ccline = "\nCc: $cc"; @@ -976,7 +976,9 @@ X-Mailer: git-send-email $gitversion if ($smtp_server !~ m#^/#) { print "Server: $smtp_server\n"; print "MAIL FROM:<$raw_from>\n"; - print "RCPT TO:".join(',',(map { "<$_>" } @recipients))."\n"; + foreach my $entry (@recipients) { + print "RCPT TO:<$entry>\n"; + } } else { print "Sendmail: $smtp_server ".join(' ',@sendmail_parameters)."\n"; } diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index fb606a9f05..84a7f03d46 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -103,10 +103,18 @@ cat >expected-show-all-headers <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: cc@example.com, A , One , two@example.com +Cc: cc@example.com, + A , + One , + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -164,7 +172,7 @@ test_expect_success 'cccmd works' ' --smtp-server="$(pwd)/fake.sendmail" \ cccmd.patch \ && - grep ^Cc:.*cccmd@example.com msgtxt1 + grep "^ cccmd@example.com" msgtxt1 ' z8=zzzzzzzz @@ -278,10 +286,17 @@ cat >expected-suppress-sob <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: cc@example.com, A , One , two@example.com +Cc: cc@example.com, + A , + One , + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -318,10 +333,15 @@ cat >expected-suppress-sob <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , One , two@example.com +Cc: A , + One , + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -344,10 +364,17 @@ cat >expected-suppress-cccmd <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , One , two@example.com, C O Mitter +Cc: A , + One , + two@example.com, + C O Mitter Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -392,10 +419,17 @@ cat >expected-suppress-body <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , One , two@example.com, cc-cmd@example.com +Cc: A , + One , + two@example.com, + cc-cmd@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -416,10 +450,15 @@ cat >expected-suppress-body-cccmd <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , One , two@example.com +Cc: A , + One , + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -440,10 +479,15 @@ cat >expected-suppress-sob <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , One , two@example.com +Cc: A , + One , + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -466,10 +510,17 @@ cat >expected-suppress-bodycc <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , One , two@example.com, C O Mitter +Cc: A , + One , + two@example.com, + C O Mitter Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -489,10 +540,13 @@ cat >expected-suppress-cc <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,, +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , C O Mitter +Cc: A , + C O Mitter Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -605,7 +659,7 @@ test_expect_success 'utf8 Cc is rfc2047 encoded' ' --to=nobody@example.com \ --smtp-server="$(pwd)/fake.sendmail" \ outdir/*.patch && - grep "^Cc:" msgtxt1 | + grep "^ " msgtxt1 | grep "=?UTF-8?q?=C3=A0=C3=A9=C3=AC=C3=B6=C3=BA?= " ' From b145b211baa4129a827cc1b1b1a7984523b8f903 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 10 Oct 2009 18:51:45 +0300 Subject: [PATCH 074/884] git-add--interactive: never skip files included in index Make "git add -p" to not skip files that are in index even if they are excluded (by .gitignore etc.). This fixes the contradictory behavior that "git status" and "git commit -a" listed such files as modified, but "git add -p FILENAME" ignored them. Signed-off-by: Pauli Virtanen Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 2 +- t/t3701-add-interactive.sh | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 06f70602cc..3e90d716fa 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -186,7 +186,7 @@ sub list_modified { @tracked = map { chomp $_; unquote_path($_); - } run_cmd_pipe(qw(git ls-files --exclude-standard --), @ARGV); + } run_cmd_pipe(qw(git ls-files --), @ARGV); return if (!@tracked); } diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 62fd65e18d..687bd7ab53 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -138,6 +138,20 @@ test_expect_success 'real edit works' ' test_cmp expected output ' +test_expect_success 'skip files similarly as commit -a' ' + git reset && + echo file >.gitignore && + echo changed >file && + echo y | git add -p file && + git diff >output && + git reset && + git commit -am commit && + git diff >expected && + test_cmp expected output && + git reset --hard HEAD^ +' +rm -f .gitignore + if test "$(git config --bool core.filemode)" = false then say 'skipping filemode tests (filesystem does not properly support modes)' From da8ba5e7da01be597aa9417c563dbd516ea5f204 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 17 Sep 2009 22:12:17 -0700 Subject: [PATCH 075/884] diff-lib.c: fix misleading comments on oneway_diff() 20a16eb (unpack_trees(): fix diff-index regression., 2008-03-10) adjusted diff-index to the new world order since 34110cd (Make 'unpack_trees()' have a separate source and destination index, 2008-03-06). Callbacks are expected to return anything non-negative as "success", and instead of reporting how many index entries they have processed, they are expected to advance o->pos themselves. The code did so, but a stale comment was left behind. Signed-off-by: Junio C Hamano --- diff-lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-lib.c b/diff-lib.c index 0c74ef5cbe..adf1c5fdee 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -383,7 +383,7 @@ static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_op * For diffing, the index is more important, and we only have a * single tree. * - * We're supposed to return how many index entries we want to skip. + * We're supposed to advance o->pos to skip what we have already processed. * * This wrapper makes it all more readable, and takes care of all * the fairly complex unpack_trees() semantic requirements, including From 6caa7b555336dcb6e52bc7dfce1dd953bf53c3d5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 19 Sep 2009 17:26:16 -0700 Subject: [PATCH 076/884] unpack-trees: typofix I am not good at subject-verb concordance. Signed-off-by: Junio C Hamano --- unpack-trees.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unpack-trees.c b/unpack-trees.c index 720f7a1616..d174fe05db 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -617,7 +617,7 @@ static int verify_absent(struct cache_entry *ce, const char *action, * found "foo/." in the working tree. * This is tricky -- if we have modified * files that are in "foo/" we would lose - * it. + * them. */ ret = verify_clean_subdirectory(ce, action, o); if (ret < 0) From 353c5eeb5cad63218ca796352cce9986df7b9c62 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 19 Sep 2009 16:36:45 -0700 Subject: [PATCH 077/884] unpack_callback(): use unpack_failed() consistently When unpack_index_entry() failed, consistently call unpack_failed(), instead of silently returning -1. Signed-off-by: Junio C Hamano --- unpack-trees.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index d174fe05db..c424bab409 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -277,6 +277,17 @@ static int unpack_nondirectories(int n, unsigned long mask, return 0; } +static int unpack_failed(struct unpack_trees_options *o, const char *message) +{ + discard_index(&o->result); + if (!o->gently) { + if (message) + return error("%s", message); + return -1; + } + return -1; +} + static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info) { struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, }; @@ -294,7 +305,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str int cmp = compare_entry(ce, info, p); if (cmp < 0) { if (unpack_index_entry(ce, o) < 0) - return -1; + return unpack_failed(o, NULL); continue; } if (!cmp) { @@ -352,17 +363,6 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str return mask; } -static int unpack_failed(struct unpack_trees_options *o, const char *message) -{ - discard_index(&o->result); - if (!o->gently) { - if (message) - return error("%s", message); - return -1; - } - return -1; -} - /* * N-way merge "len" trees. Returns 0 on success, -1 on failure to manipulate the * resulting index, -2 on failure to reflect the changes to the work tree. From 3240269dd9038ea12eb2f19a4be1218d6320a548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Sun, 11 Oct 2009 23:08:25 +0200 Subject: [PATCH 078/884] Documentation: add 'git replace' to main git manpage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- command-list.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/command-list.txt b/command-list.txt index fb03a2ebb5..59b0adc39b 100644 --- a/command-list.txt +++ b/command-list.txt @@ -92,6 +92,7 @@ git-reflog ancillarymanipulators git-relink ancillarymanipulators git-remote ancillarymanipulators git-repack ancillarymanipulators +git-replace ancillarymanipulators git-repo-config ancillarymanipulators deprecated git-request-pull foreignscminterface git-rerere ancillaryinterrogators From 2775d92c53d7b00758fa98e4ad8bce1e59445b05 Mon Sep 17 00:00:00 2001 From: Felipe Contreras Date: Sun, 11 Oct 2009 23:46:11 +0300 Subject: [PATCH 079/884] diff.c: stylefix Essentially; s/type* /type */ as per the coding guidelines. Signed-off-by: Felipe Contreras Signed-off-by: Junio C Hamano --- diff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff.c b/diff.c index e1be189742..b39c1b6f7f 100644 --- a/diff.c +++ b/diff.c @@ -999,7 +999,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) total_files, adds, dels); } -static void show_shortstats(struct diffstat_t* data, struct diff_options *options) +static void show_shortstats(struct diffstat_t *data, struct diff_options *options) { int i, adds = 0, dels = 0, total_files = data->nr; From b5227d80aee5173bfda6aa43a890d03110b0df26 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 12 Oct 2009 01:11:57 -0400 Subject: [PATCH 080/884] ls-files: excludes should not impact tracked files In all parts of git, .gitignore and other exclude files impact only how we treat untracked files; they should have no effect on files listed in the index. This behavior was originally implemented very early on in 9ff768e, but only for --exclude-from. Later, commit 63d285c accidentally caused us to trigger the behavior for --exclude-per-directory. This patch totally ignores excludes for files found in the index. This means we are reversing the original intent of 9ff768e, while at the same time fixing the accidental behavior of 63d285c. This is a good thing, though, as the way that 9ff768e behaved does not really make sense with the way exclusions are used in modern git. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-ls-files.c | 8 -------- t/t3003-ls-files-exclude.sh | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) create mode 100755 t/t3003-ls-files-exclude.sh diff --git a/builtin-ls-files.c b/builtin-ls-files.c index da2daf45ac..16a1370647 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -175,10 +175,6 @@ static void show_files(struct dir_struct *dir, const char *prefix) if (show_cached | show_stage) { for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - int dtype = ce_to_dtype(ce); - if (excluded(dir, ce->name, &dtype) != - !!(dir->flags & DIR_SHOW_IGNORED)) - continue; if (show_unmerged && !ce_stage(ce)) continue; if (ce->ce_flags & CE_UPDATE) @@ -191,10 +187,6 @@ static void show_files(struct dir_struct *dir, const char *prefix) struct cache_entry *ce = active_cache[i]; struct stat st; int err; - int dtype = ce_to_dtype(ce); - if (excluded(dir, ce->name, &dtype) != - !!(dir->flags & DIR_SHOW_IGNORED)) - continue; if (ce->ce_flags & CE_UPDATE) continue; err = lstat(ce->name, &st); diff --git a/t/t3003-ls-files-exclude.sh b/t/t3003-ls-files-exclude.sh new file mode 100755 index 0000000000..fc1e379321 --- /dev/null +++ b/t/t3003-ls-files-exclude.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +test_description='ls-files --exclude does not affect index files' +. ./test-lib.sh + +test_expect_success 'create repo with file' ' + echo content >file && + git add file && + git commit -m file && + echo modification >file +' + +check_output() { +test_expect_success "ls-files output contains file ($1)" " + echo '$2' >expect && + git ls-files --exclude-standard --$1 >output && + test_cmp expect output +" +} + +check_all_output() { + check_output 'cached' 'file' + check_output 'modified' 'file' +} + +check_all_output +test_expect_success 'add file to gitignore' ' + echo file >.gitignore +' +check_all_output + +test_done From 17225c49d501a304a0ae9ff370ca5cc3ae4afe4d Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 12 Oct 2009 11:00:09 +0200 Subject: [PATCH 081/884] bash completion: complete refs for git-grep Before the --, always attempt ref completion. This helps with entering the arguments to git-grep. As a bonus, you can work around git-grep's current lack of --all by hitting M-*, ugly as the resulting command line may be. Strictly speaking, completing the regular expression argument (or option argument) makes no sense. However, we cannot prevent _all_ completion (it will fall back to filenames), so we dispense with any additional complication to detect whether the user still has to enter a regular expression. Signed-off-by: Thomas Rast Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 7cf8557468..d3fec32997 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1069,7 +1069,8 @@ _git_grep () return ;; esac - COMPREPLY=() + + __gitcomp "$(__git_refs)" } _git_help () From 77abcbdc268d048b7a19af405607fb8d14e630ed Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 12 Oct 2009 22:34:12 +0200 Subject: [PATCH 082/884] Let --decorate show HEAD position 'git log --graph --oneline --decorate --all' is a useful way to get a general overview of the repository state, similar to 'gitk --all'. Let it indicate the position of HEAD by loading that ref too, so that the --decorate code can see it. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- log-tree.c | 1 + t/t4013/diff.log_--decorate=full_--all | 2 +- t/t4013/diff.log_--decorate_--all | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/log-tree.c b/log-tree.c index 1618f3c79a..f7d54f2f1b 100644 --- a/log-tree.c +++ b/log-tree.c @@ -43,6 +43,7 @@ void load_ref_decorations(int flags) if (!loaded) { loaded = 1; for_each_ref(add_ref_decoration, &flags); + head_ref(add_ref_decoration, &flags); } } diff --git a/t/t4013/diff.log_--decorate=full_--all b/t/t4013/diff.log_--decorate=full_--all index 903d9d9659..d155e0bab2 100644 --- a/t/t4013/diff.log_--decorate=full_--all +++ b/t/t4013/diff.log_--decorate=full_--all @@ -1,5 +1,5 @@ $ git log --decorate=full --all -commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (refs/heads/master) +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, refs/heads/master) Merge: 9a6d494 c7a2ab9 Author: A U Thor Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--decorate_--all b/t/t4013/diff.log_--decorate_--all index 954210ea90..fd7c3e6439 100644 --- a/t/t4013/diff.log_--decorate_--all +++ b/t/t4013/diff.log_--decorate_--all @@ -1,5 +1,5 @@ $ git log --decorate --all -commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (master) +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, master) Merge: 9a6d494 c7a2ab9 Author: A U Thor Date: Mon Jun 26 00:04:00 2006 +0000 From 58a05c74e7a9341af80eb98731d6b0dafe1b5c29 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 12 Oct 2009 00:27:04 -0500 Subject: [PATCH 083/884] Add tests for git check-ref-format The "git check-ref-format" command is a basic command various porcelains rely on. Test its functionality to make sure it does not unintentionally change. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- t/t1402-check-ref-format.sh | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 t/t1402-check-ref-format.sh diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh new file mode 100644 index 0000000000..382bc6e823 --- /dev/null +++ b/t/t1402-check-ref-format.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +test_description='Test git check-ref-format' + +. ./test-lib.sh + +valid_ref() { + test_expect_success "ref name '$1' is valid" \ + "git check-ref-format '$1'" +} +invalid_ref() { + test_expect_success "ref name '$1' is not valid" \ + "test_must_fail git check-ref-format '$1'" +} + +valid_ref 'heads/foo' +invalid_ref 'foo' +valid_ref 'foo/bar/baz' +valid_ref 'refs///heads/foo' +invalid_ref 'heads/foo/' +invalid_ref './foo' +invalid_ref '.refs/foo' +invalid_ref 'heads/foo..bar' +invalid_ref 'heads/foo?bar' +valid_ref 'foo./bar' +invalid_ref 'heads/foo.lock' +valid_ref 'heads/foo@bar' +invalid_ref 'heads/v@{ation' +invalid_ref 'heads/foo\bar' + +test_expect_success "check-ref-format --branch @{-1}" ' + T=$(git write-tree) && + sha1=$(echo A | git commit-tree $T) && + git update-ref refs/heads/master $sha1 && + git update-ref refs/remotes/origin/master $sha1 + git checkout master && + git checkout origin/master && + git checkout master && + refname=$(git check-ref-format --branch @{-1}) && + test "$refname" = "$sha1" && + refname2=$(git check-ref-format --branch @{-2}) && + test "$refname2" = master' + +test_done From 604e0cb5cbd2ea2e37273e13f99b6572cd82b4c1 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 12 Oct 2009 00:28:23 -0500 Subject: [PATCH 084/884] Documentation: describe check-ref-format --branch Unless one already knew, it was not obvious what sort of shorthand "git check-ref-format --branch" expands. Explain it. The --branch argument is not optional. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- Documentation/git-check-ref-format.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index c1ce26884e..664da8a988 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git check-ref-format' -'git check-ref-format' [--branch] +'git check-ref-format' --branch DESCRIPTION ----------- @@ -57,8 +57,11 @@ reference name expressions (see linkgit:git-rev-parse[1]): . at-open-brace `@{` is used as a notation to access a reflog entry. -With the `--branch` option, it expands a branch name shorthand and -prints the name of the branch the shorthand refers to. +With the `--branch` option, it expands the ``previous branch syntax'' +`@{-n}`. For example, `@{-1}` is a way to refer the last branch you +were on. This option should be used by porcelains to accept this +syntax anywhere a branch name is expected, so they can act as if you +typed the branch name. EXAMPLE ------- From 38eedc634bc5d30e8a7a2356d9eb3ae95d9b1d75 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 12 Oct 2009 16:39:43 -0700 Subject: [PATCH 085/884] git check-ref-format --print Tolerating empty path components in ref names means each ref does not have a unique name. This creates difficulty for porcelains that want to see if two branches are equal. Add a helper associating to each ref a canonical name. If a user asks a porcelain to create a ref "refs/heads//master", the porcelain can run "git check-ref-format --print refs/heads//master" and only deal with "refs/heads/master" from then on. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- Documentation/git-check-ref-format.txt | 23 ++++++++++++++++++----- builtin-check-ref-format.c | 10 ++++++++++ t/t1402-check-ref-format.sh | 17 +++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index 0b7982ea76..211ae1c3fa 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -9,6 +9,7 @@ SYNOPSIS -------- [verse] 'git check-ref-format' +'git check-ref-format' --print 'git check-ref-format' [--branch] DESCRIPTION @@ -63,16 +64,28 @@ reference name expressions (see linkgit:git-rev-parse[1]): . at-open-brace `@{` is used as a notation to access a reflog entry. +With the `--print` option, if 'refname' is acceptable, it prints the +canonicalized name of a hypothetical reference with that name. That is, +it prints 'refname' with any extra `/` characters removed. + With the `--branch` option, it expands a branch name shorthand and prints the name of the branch the shorthand refers to. -EXAMPLE -------- +EXAMPLES +-------- -git check-ref-format --branch @{-1}:: - -Print the name of the previous branch. +* Print the name of the previous branch: ++ +------------ +$ git check-ref-format --branch @{-1} +------------ +* Determine the reference name to use for a new branch: ++ +------------ +$ ref=$(git check-ref-format --print "refs/heads/$newbranch") || +die "we do not like '$newbranch' as a branch name." +------------ GIT --- diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c index f9381e07ea..b97b61a0a4 100644 --- a/builtin-check-ref-format.c +++ b/builtin-check-ref-format.c @@ -17,6 +17,16 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) printf("%s\n", sb.buf + 11); exit(0); } + if (argc == 3 && !strcmp(argv[1], "--print")) { + char *refname = xmalloc(strlen(argv[2]) + 1); + + if (check_ref_format(argv[2])) + exit(1); + if (normalize_path_copy(refname, argv[2])) + die("Could not normalize ref name '%s'", argv[2]); + printf("%s\n", refname); + exit(0); + } if (argc != 2) usage("git check-ref-format refname"); return !!check_ref_format(argv[1]); diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh index 382bc6e823..eb45afb018 100644 --- a/t/t1402-check-ref-format.sh +++ b/t/t1402-check-ref-format.sh @@ -41,4 +41,21 @@ test_expect_success "check-ref-format --branch @{-1}" ' refname2=$(git check-ref-format --branch @{-2}) && test "$refname2" = master' +valid_ref_normalized() { + test_expect_success "ref name '$1' simplifies to '$2'" " + refname=\$(git check-ref-format --print '$1') && + test \"\$refname\" = '$2'" +} +invalid_ref_normalized() { + test_expect_success "check-ref-format --print rejects '$1'" " + test_must_fail git check-ref-format --print '$1'" +} + +valid_ref_normalized 'heads/foo' 'heads/foo' +valid_ref_normalized 'refs///heads/foo' 'refs/heads/foo' +invalid_ref_normalized 'foo' +invalid_ref_normalized 'heads/foo/../bar' +invalid_ref_normalized 'heads/./foo' +invalid_ref_normalized 'heads\foo' + test_done From 1ba447b8dc2ec4e6c8ebbbdaa449f38edc29ad3f Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 12 Oct 2009 00:33:01 -0500 Subject: [PATCH 086/884] check-ref-format: simplify --print implementation normalize_path_copy() is a complicated function, but most of its functionality will never apply to a ref name that has been checked with check_ref_format(). Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-check-ref-format.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c index b97b61a0a4..e3e7bdf52f 100644 --- a/builtin-check-ref-format.c +++ b/builtin-check-ref-format.c @@ -7,6 +7,28 @@ #include "builtin.h" #include "strbuf.h" +/* + * Replace each run of adjacent slashes in src with a single slash, + * and write the result to dst. + * + * This function is similar to normalize_path_copy(), but stripped down + * to meet check_ref_format's simpler needs. + */ +static void collapse_slashes(char *dst, const char *src) +{ + char ch; + char prev = '\0'; + + while ((ch = *src++) != '\0') { + if (prev == '/' && ch == prev) + continue; + + *dst++ = ch; + prev = ch; + } + *dst = '\0'; +} + int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { if (argc == 3 && !strcmp(argv[1], "--branch")) { @@ -22,8 +44,7 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) if (check_ref_format(argv[2])) exit(1); - if (normalize_path_copy(refname, argv[2])) - die("Could not normalize ref name '%s'", argv[2]); + collapse_slashes(refname, argv[2]); printf("%s\n", refname); exit(0); } From b0fa7ab51b29d34579d8f6bb4443dfbcb8278c7a Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 12 Oct 2009 22:30:32 +0200 Subject: [PATCH 087/884] git: add --no-replace-objects option to disable replacing Commit dae556b (environment: add global variable to disable replacement) adds a variable to enable/disable replacement, and it is enabled by default for most commands. So there is no way to disable it for some commands, which is annoying when we want to get information about a commit that has been replaced. For example: $ git cat-file -p N would output information about the replacement commit if commit N is replaced. With the "--no-replace-objects" option that this patch adds it is possible to get information about the original commit using: $ git --no-replace-objects cat-file -p N While at it, let's add some documentation about this new option in the "git replace" man page too. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- Documentation/git-replace.txt | 21 +++++++++++++++++++++ Documentation/git.txt | 6 +++++- git.c | 4 +++- t/t6050-replace.sh | 7 +++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt index 915cb77b29..8adc1ef55c 100644 --- a/Documentation/git-replace.txt +++ b/Documentation/git-replace.txt @@ -23,6 +23,26 @@ replacement object. Unless `-f` is given, the replace reference must not yet exist in `.git/refs/replace/` directory. +Replace references will be used by default by all git commands except +those doing reachability traversal (prune, pack transfer and fsck). + +It is possible to disable use of replacement refs for any command +using the --no-replace-objects option just after "git". + +For example if commit "foo" has been replaced by commit "bar": + +------------------------------------------------ +$ git --no-replace-object cat-file commit foo +------------------------------------------------ + +show information about commit "foo", while: + +------------------------------------------------ +$ git cat-file commit foo +------------------------------------------------ + +show information about commit "bar". + OPTIONS ------- -f:: @@ -54,6 +74,7 @@ SEE ALSO -------- linkgit:git-tag[1] linkgit:git-branch[1] +linkgit:git[1] Author ------ diff --git a/Documentation/git.txt b/Documentation/git.txt index d97aaf5bf8..2f4541780a 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] - [-p|--paginate|--no-pager] + [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS] @@ -237,6 +237,10 @@ help ...`. environment is not set, it is set to the current working directory. +--no-replace-objects:: + Do not use replacement refs to replace git objects. See + linkgit:git-replace[1] for more information. + FURTHER DOCUMENTATION --------------------- diff --git a/git.c b/git.c index 9883009b5d..bd2c5fe77b 100644 --- a/git.c +++ b/git.c @@ -6,7 +6,7 @@ const char git_usage_string[] = "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n" - " [-p|--paginate|--no-pager]\n" + " [-p|--paginate|--no-pager] [--no-replace-objects]\n" " [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n" " [--help] COMMAND [ARGS]"; @@ -87,6 +87,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) use_pager = 0; if (envchanged) *envchanged = 1; + } else if (!strcmp(cmd, "--no-replace-objects")) { + read_replace_refs = 0; } else if (!strcmp(cmd, "--git-dir")) { if (*argc < 2) { fprintf(stderr, "No directory given for --git-dir.\n" ); diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 8b8bd81c09..d4818b430a 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -70,6 +70,13 @@ test_expect_success 'replace the author' ' git show $HASH2 | grep "O Thor" ' +test_expect_success 'test --no-replace-objects option' ' + git cat-file commit $HASH2 | grep "author O Thor" && + git --no-replace-objects cat-file commit $HASH2 | grep "author A U Thor" && + git show $HASH2 | grep "O Thor" && + git --no-replace-objects show $HASH2 | grep "A U Thor" +' + cat >tag.sig < Date: Tue, 13 Oct 2009 17:02:24 -0400 Subject: [PATCH 088/884] bisect reset: Allow resetting to any commit, not just a branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ‘git bisect reset’ accepts an optional argument specifying a branch to check out after cleaning up the bisection state. This lets you specify an arbitrary commit. In particular, this provides a way to clean the bisection state without moving HEAD: ‘git bisect reset HEAD’. This may be useful if you are not interested in the state before you began a bisect, especially if checking out the old commit would be expensive and invalidate most of your compiled tree. Clarify the ‘git bisect reset’ documentation to explain this optional argument, which was previously mentioned only in the usage message. Signed-off-by: Anders Kaseorg Signed-off-by: Junio C Hamano --- Documentation/git-bisect.txt | 23 +++++++++++++++++------ git-bisect.sh | 8 ++++---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 63e7a42cb3..d2ffae0c10 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -20,7 +20,7 @@ on the subcommand: git bisect bad [] git bisect good [...] git bisect skip [(|)...] - git bisect reset [] + git bisect reset [] git bisect visualize git bisect replay git bisect log @@ -81,16 +81,27 @@ will have been left with the first bad kernel revision in "refs/bisect/bad". Bisect reset ~~~~~~~~~~~~ -To return to the original head after a bisect session, issue the -following command: +After a bisect session, to clean up the bisection state and return to +the original HEAD, issue the following command: ------------------------------------------------ $ git bisect reset ------------------------------------------------ -This resets the tree to the original branch instead of being on the -bisection commit ("git bisect start" will also do that, as it resets -the bisection state). +By default, this will return your tree to the commit that was checked +out before `git bisect start`. (A new `git bisect start` will also do +that, as it cleans up the old bisection state.) + +With an optional argument, you can return to a different commit +instead: + +------------------------------------------------ +$ git bisect reset +------------------------------------------------ + +For example, `git bisect reset HEAD` will leave you on the current +bisection commit and avoid switching commits at all, while `git bisect +reset bisect/bad` will check out the first bad revision. Bisect visualize ~~~~~~~~~~~~~~~~ diff --git a/git-bisect.sh b/git-bisect.sh index 6f6f03966f..8b3c5858a9 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -13,8 +13,8 @@ git bisect skip [(|)...] mark ... untestable revisions. git bisect next find next bisection to test and check it out. -git bisect reset [] - finish bisection search and go back to branch. +git bisect reset [] + finish bisection search and go back to commit. git bisect visualize show bisect status in gitk. git bisect replay @@ -311,8 +311,8 @@ bisect_reset() { } case "$#" in 0) branch=$(cat "$GIT_DIR/BISECT_START") ;; - 1) git show-ref --verify --quiet -- "refs/heads/$1" || - die "$1 does not seem to be a valid branch" + 1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || + die "'$1' is not a valid commit" branch="$1" ;; *) usage ;; From 452e2256d2d7cb5494ca10fcbbb6bdf29570f2c0 Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Tue, 13 Oct 2009 21:51:36 +0200 Subject: [PATCH 089/884] gitweb: fix esc_param The custom CGI escaping done in esc_param failed to escape UTF-8 properly. Fix by using CGI::escape on each sequence of matched characters instead of sprintf()ing a custom escaping for each byte. Additionally, the space -> + escape was being escaped due to greedy matching on the first substitution. Fix by adding space to the list of characters not handled on the first substitution. Finally, remove an unnecessary escaping of the + sign. Signed-off-by: Giuseppe Bilotta Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 24b219310a..4b21ad25df 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1083,8 +1083,7 @@ sub to_utf8 { # correct, but quoted slashes look too horrible in bookmarks sub esc_param { my $str = shift; - $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg; - $str =~ s/\+/%2B/g; + $str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg; $str =~ s/ /\+/g; return $str; } From fe0a3cb23c7930be419937825591388465ceb47f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 14 Oct 2009 11:51:03 -0700 Subject: [PATCH 090/884] info/grafts: allow trailing whitespaces at the end of line When creating an info/grafts under windows, one typically gets a CRLF file. There is no good reason to forbid trailing CR at the end of the line (for that matter, any trailing whitespaces); the code allowed only LF simply because that was good enough for the platforms with LF line endings. Signed-off-by: Junio C Hamano --- commit.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commit.c b/commit.c index aa3b35b6a8..eee5ab803b 100644 --- a/commit.c +++ b/commit.c @@ -136,8 +136,8 @@ struct commit_graft *read_graft_line(char *buf, int len) int i; struct commit_graft *graft = NULL; - if (buf[len-1] == '\n') - buf[--len] = 0; + while (len && isspace(buf[len-1])) + buf[--len] = '\0'; if (buf[0] == '#' || buf[0] == '\0') return NULL; if ((len + 1) % 41) { From ad12b81271d76f3e4a8b1f527e252ac8452732fd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 17 Oct 2009 00:11:43 -0700 Subject: [PATCH 091/884] Start 1.6.6 cycle Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.6.txt | 60 ++++++++++++++++++++++++++++++++ GIT-VERSION-GEN | 2 +- RelNotes | 2 +- 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 Documentation/RelNotes-1.6.6.txt diff --git a/Documentation/RelNotes-1.6.6.txt b/Documentation/RelNotes-1.6.6.txt new file mode 100644 index 0000000000..5f1fecb5ec --- /dev/null +++ b/Documentation/RelNotes-1.6.6.txt @@ -0,0 +1,60 @@ +GIT v1.6.6 Release Notes +======================== + +In git 1.7.0, which is planned to be the release after 1.6.6, "git +push" into a branch that is currently checked out will be refused by +default. + +You can choose what should happen upon such a push by setting the +configuration variable receive.denyCurrentBranch in the receiving +repository. + +Also, "git push $there :$killed" to delete the branch $killed in a remote +repository $there, when $killed branch is the current branch pointed at by +its HEAD, will be refused by default. + +You can choose what should happen upon such a push by setting the +configuration variable receive.denyDeleteCurrent in the receiving +repository. + +To ease the transition plan, the receiving repository of such a +push running this release will issue a big warning when the +configuration variable is missing. Please refer to: + + http://git.or.cz/gitwiki/GitFaq#non-bare + http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007 + +for more details on the reason why this change is needed and the +transition plan. + +Updates since v1.6.5 +-------------------- + +(subsystems) + +(portability) + +(performance) + +(usability, bells and whistles) + + * "git log --decorate" shows the location of HEAD as well. + +(developers) + +Fixes since v1.6.5 +------------------ + +All of the fixes in v1.6.5.X maintenance series are included in this +release, unless otherwise noted. + + * "git apply" and "git diff" (including patch output from "git log -p") + now flags trailing blank lines as whitespace errors correctly (only + "apply --whitespace=fix" stripped them but "apply --whitespace=warn" + did not even warn). + + * Two whitespace error classes, 'blank-at-eof' and 'blank-at-eol', have + been introduced (settable by core.whitespace configuration variable and + whitespace attribute). The 'trailing-space' whitespace error class has + become a short-hand to cover both of these and there is no behaviour + change for existing set-ups. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 08e6073f33..710d361233 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.5 +DEF_VER=v1.6.5.GIT LF=' ' diff --git a/RelNotes b/RelNotes index b62449d2e2..5274ceed4e 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.6.5.txt \ No newline at end of file +Documentation/RelNotes-1.6.6.txt \ No newline at end of file From c6e8c8005a2b1fc4cff72d279f29178767bd1a47 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Oct 2009 00:27:24 -0700 Subject: [PATCH 092/884] check_filename(): make verify_filename() callable without dying Make it possible to invole the logic of verify_filename() to make sure the pathname arguments are unambiguous without actually dying. The caller may want to do something different. --- cache.h | 1 + setup.c | 38 ++++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/cache.h b/cache.h index 96840c7af7..71a731dbc9 100644 --- a/cache.h +++ b/cache.h @@ -396,6 +396,7 @@ extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); extern const char *prefix_path(const char *prefix, int len, const char *path); extern const char *prefix_filename(const char *prefix, int len, const char *path); +extern int check_filename(const char *prefix, const char *name); extern void verify_filename(const char *prefix, const char *name); extern void verify_non_filename(const char *prefix, const char *name); diff --git a/setup.c b/setup.c index 029371e584..f67250b7c1 100644 --- a/setup.c +++ b/setup.c @@ -61,6 +61,19 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) return path; } +int check_filename(const char *prefix, const char *arg) +{ + const char *name; + struct stat st; + + name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; + if (!lstat(name, &st)) + return 1; /* file exists */ + if (errno == ENOENT || errno == ENOTDIR) + return 0; /* file does not exist */ + die_errno("failed to stat '%s'", arg); +} + /* * Verify a filename that we got as an argument for a pathspec * entry. Note that a filename that begins with "-" never verifies @@ -70,18 +83,12 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) */ void verify_filename(const char *prefix, const char *arg) { - const char *name; - struct stat st; - if (*arg == '-') die("bad flag '%s' used after filename", arg); - name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; - if (!lstat(name, &st)) + if (check_filename(prefix, arg)) return; - if (errno == ENOENT) - die("ambiguous argument '%s': unknown revision or path not in the working tree.\n" - "Use '--' to separate paths from revisions", arg); - die_errno("failed to stat '%s'", arg); + die("ambiguous argument '%s': unknown revision or path not in the working tree.\n" + "Use '--' to separate paths from revisions", arg); } /* @@ -91,19 +98,14 @@ void verify_filename(const char *prefix, const char *arg) */ void verify_non_filename(const char *prefix, const char *arg) { - const char *name; - struct stat st; - if (!is_inside_work_tree() || is_inside_git_dir()) return; if (*arg == '-') return; /* flag */ - name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; - if (!lstat(name, &st)) - die("ambiguous argument '%s': both revision and filename\n" - "Use '--' to separate filenames from revisions", arg); - if (errno != ENOENT && errno != ENOTDIR) - die_errno("failed to stat '%s'", arg); + if (!check_filename(prefix, arg)) + return; + die("ambiguous argument '%s': both revision and filename\n" + "Use '--' to separate filenames from revisions", arg); } const char **get_pathspec(const char *prefix, const char **pathspec) From 70c9ac2f1999b7e0c17a864235537cffe29dfea4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Oct 2009 00:13:47 -0700 Subject: [PATCH 093/884] DWIM "git checkout frotz" to "git checkout -b frotz origin/frotz" When 'frotz' is not a valid object name and not a tracked filename, we used to complain and failed this command. When there is only one remote that has 'frotz' as one of its tracking branches, we can DWIM it as a request to create a local branch 'frotz' forking from the matching remote tracking branch. Signed-off-by: Junio C Hamano --- builtin-checkout.c | 60 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index d050c3789f..fb7e68ac5f 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -572,6 +572,40 @@ static int interactive_checkout(const char *revision, const char **pathspec, return run_add_interactive(revision, "--patch=checkout", pathspec); } +struct tracking_name_data { + const char *name; + char *remote; + int unique; +}; + +static int check_tracking_name(const char *refname, const unsigned char *sha1, + int flags, void *cb_data) +{ + struct tracking_name_data *cb = cb_data; + const char *slash; + + if (prefixcmp(refname, "refs/remotes/")) + return 0; + slash = strchr(refname + 13, '/'); + if (!slash || strcmp(slash + 1, cb->name)) + return 0; + if (cb->remote) { + cb->unique = 0; + return 0; + } + cb->remote = xstrdup(refname); + return 0; +} + +static const char *unique_tracking_name(const char *name) +{ + struct tracking_name_data cb_data = { name, NULL, 1 }; + for_each_ref(check_tracking_name, &cb_data); + if (cb_data.unique) + return cb_data.remote; + free(cb_data.remote); + return NULL; +} int cmd_checkout(int argc, const char **argv, const char *prefix) { @@ -630,8 +664,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.new_branch = argv0 + 1; } - if (opts.track == BRANCH_TRACK_UNSPECIFIED) - opts.track = git_branch_track; if (conflict_style) { opts.merge = 1; /* implied */ git_xmerge_config("merge.conflictstyle", conflict_style, NULL); @@ -655,6 +687,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) * With no paths, if is a commit, that is to * switch to the branch or detach HEAD at it. * + * With no paths, if is _not_ a commit, no -t nor -b + * was given, and there is a tracking branch whose name is + * in one and only one remote, then this is a short-hand + * to fork local from that remote tracking branch. + * * Otherwise shall not be ambiguous. * - If it's *only* a reference, treat it like case (1). * - If it's only a path, treat it like case (2). @@ -677,7 +714,20 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (get_sha1(arg, rev)) { if (has_dash_dash) /* case (1) */ die("invalid reference: %s", arg); - goto no_reference; /* case (3 -> 2) */ + if (!patch_mode && + opts.track == BRANCH_TRACK_UNSPECIFIED && + !opts.new_branch && + !check_filename(NULL, arg) && + argc == 1) { + const char *remote = unique_tracking_name(arg); + if (!remote || get_sha1(remote, rev)) + goto no_reference; + opts.new_branch = arg; + arg = remote; + /* DWIMmed to create local branch */ + } + else + goto no_reference; } /* we can't end up being in (2) anymore, eat the argument */ @@ -715,6 +765,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) } no_reference: + + if (opts.track == BRANCH_TRACK_UNSPECIFIED) + opts.track = git_branch_track; + if (argc) { const char **pathspec = get_pathspec(prefix, argv); From 619a644d6daef56d70aeca85514e2d281eb483a5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Oct 2009 12:34:56 -0700 Subject: [PATCH 094/884] "checkout A...B" switches to the merge base between A and B When flipping commits around on topic branches, I often end up doing this sequence: * Run "log --oneline next..jc/frotz" to find out the first commit on 'jc/frotz' branch not yet merged to 'next'; * Run "checkout $that_commit^" to detach HEAD to the parent of it; * Rebuild the series on top of that commit; and * "show-branch jc/frotz HEAD" and "diff jc/frotz HEAD" to verify. Introduce a new syntax to "git checkout" to name the commit to switch to, to make the first two steps easier. When the branch to switch to is specified as A...B (you can omit either A or B but not both, and HEAD is used instead of the omitted side), the merge base between these two commits are computed, and if there is one unique one, we detach the HEAD at that commit. With this, I can say "checkout next...jc/frotz". Signed-off-by: Junio C Hamano --- builtin-checkout.c | 7 +++++-- cache.h | 1 + sha1_name.c | 42 ++++++++++++++++++++++++++++++++++++++++ t/t2012-checkout-last.sh | 27 +++++++++++++++++++++++++- 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index da04eed391..fe7c8584ca 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -689,7 +689,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) * case 3: git checkout [] * * With no paths, if is a commit, that is to - * switch to the branch or detach HEAD at it. + * switch to the branch or detach HEAD at it. As a special case, + * if is A...B (missing A or B means HEAD but you can + * omit at most one side), and if there is a unique merge base + * between A and B, A...B names that merge base. * * With no paths, if is _not_ a commit, no -t nor -b * was given, and there is a tracking branch whose name is @@ -715,7 +718,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "-")) arg = "@{-1}"; - if (get_sha1(arg, rev)) { + if (get_sha1_mb(arg, rev)) { if (has_dash_dash) /* case (1) */ die("invalid reference: %s", arg); if (!patch_mode && diff --git a/cache.h b/cache.h index 71a731dbc9..f8df15a060 100644 --- a/cache.h +++ b/cache.h @@ -703,6 +703,7 @@ extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int * extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); extern int interpret_branch_name(const char *str, struct strbuf *); +extern int get_sha1_mb(const char *str, unsigned char *sha1); extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules); extern const char *ref_rev_parse_rules[]; diff --git a/sha1_name.c b/sha1_name.c index 44bb62d270..d5a240cf07 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -794,6 +794,48 @@ release_return: return retval; } +int get_sha1_mb(const char *name, unsigned char *sha1) +{ + struct commit *one, *two; + struct commit_list *mbs; + unsigned char sha1_tmp[20]; + const char *dots; + int st; + + dots = strstr(name, "..."); + if (!dots) + return get_sha1(name, sha1); + if (dots == name) + st = get_sha1("HEAD", sha1_tmp); + else { + struct strbuf sb; + strbuf_init(&sb, dots - name); + strbuf_add(&sb, name, dots - name); + st = get_sha1(sb.buf, sha1_tmp); + strbuf_release(&sb); + } + if (st) + return st; + one = lookup_commit_reference_gently(sha1_tmp, 0); + if (!one) + return -1; + + if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp)) + return -1; + two = lookup_commit_reference_gently(sha1_tmp, 0); + if (!two) + return -1; + mbs = get_merge_bases(one, two, 1); + if (!mbs || mbs->next) + st = -1; + else { + st = 0; + hashcpy(sha1, mbs->item->object.sha1); + } + free_commit_list(mbs); + return st; +} + /* * This is like "get_sha1_basic()", except it allows "sha1 expressions", * notably "xyz^" for "parent of xyz" diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh index 87b30a268c..b44de9dc62 100755 --- a/t/t2012-checkout-last.sh +++ b/t/t2012-checkout-last.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='checkout can switch to last branch' +test_description='checkout can switch to last branch and merge base' . ./test-lib.sh @@ -91,4 +91,29 @@ test_expect_success 'switch to twelfth from the last' ' test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13" ' +test_expect_success 'merge base test setup' ' + git checkout -b another other && + echo "hello again" >>world && + git add world && + git commit -m third +' + +test_expect_success 'another...master' ' + git checkout another && + git checkout another...master && + test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)" +' + +test_expect_success '...master' ' + git checkout another && + git checkout ...master && + test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)" +' + +test_expect_success 'master...' ' + git checkout another && + git checkout master... && + test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)" +' + test_done From 3edd98ac6569252acd669c2b9cbe39d15eeb7e56 Mon Sep 17 00:00:00 2001 From: "Carlos R. Mafra" Date: Sun, 11 Oct 2009 15:32:19 +0200 Subject: [PATCH 095/884] Makefile: clean block-sha1/ directory instead of mozilla-sha1/ 'make clean' should remove the object files from block-sha1/ instead of the non-existent mozilla-sha1/ directory. Signed-off-by: Carlos R. Mafra Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index fea237bc80..42b7d60e19 100644 --- a/Makefile +++ b/Makefile @@ -1827,7 +1827,7 @@ distclean: clean $(RM) configure clean: - $(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ + $(RM) *.o block-sha1/*.o arm/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ $(LIB_FILE) $(XDIFF_LIB) $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) From 989119d96e8d06c69b16d2021856309ec8e0a0c8 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 19 Oct 2009 00:01:19 -0400 Subject: [PATCH 096/884] document push's new quiet option Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-push.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index ba6a8a2fb2..37c88953d1 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -138,6 +138,11 @@ useful if you write an alias or script around 'git-push'. --verbose:: Run verbosely. +-q:: +--quiet:: + Suppress all output, including the listing of updated refs, + unless an error occurs. + include::urls-remotes.txt[] OUTPUT From fcb044ee5754ad2df2d217748a976ec82970eb29 Mon Sep 17 00:00:00 2001 From: Nanako Shiraishi Date: Mon, 19 Oct 2009 11:54:12 +0900 Subject: [PATCH 097/884] git push: remove incomplete options list from help text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'git push -h' shows usage text with incomplete list of options and then has a separate list of options that are supported. Imitate the way other commands (I looked at 'git diff' for an example) show their options. Signed-off-by: しらいし ななこ Signed-off-by: Junio C Hamano --- builtin-push.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-push.c b/builtin-push.c index 3cb1ee46d1..ab49b7c765 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -10,7 +10,7 @@ #include "parse-options.h" static const char * const push_usage[] = { - "git push [--all | --mirror] [-n | --dry-run] [--porcelain] [--tags] [--receive-pack=] [--repo=] [-f | --force] [-v] [ ...]", + "git push [] [ ...]", NULL, }; From 8ef4c28b8d391ee053049d2dc9a3568b86c0dd38 Mon Sep 17 00:00:00 2001 From: Nanako Shiraishi Date: Mon, 19 Oct 2009 12:57:01 +0900 Subject: [PATCH 098/884] git push: say that --tag can't be used with --all or --mirror in help text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This replaces an earlier patch by Björn Gustavsson, Message-ID: <4AD75029.1010109@gmail.com> Signed-off-by: しらいし ななこ Signed-off-by: Junio C Hamano --- builtin-push.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-push.c b/builtin-push.c index ab49b7c765..b5cd2cdad0 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -181,7 +181,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL), OPT_BIT( 0 , "mirror", &flags, "mirror all refs", (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)), - OPT_BOOLEAN( 0 , "tags", &tags, "push tags"), + OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror"), OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE), From fb423da0e5668f3945c5a3e34fe5953cde6985a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 17 Oct 2009 18:30:48 +0200 Subject: [PATCH 099/884] describe: load refnames before calling describe() Get rid of the static variable that was used to prevent loading all the refnames multiple times by moving that code out of describe(), simply making sure it is only run once that way. Also change the error message that is shown in case no refnames are found to not include a hash any more, as the error condition is not specific to any particular revision. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- builtin-describe.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/builtin-describe.c b/builtin-describe.c index df67a733ae..2dcfd3dfeb 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -180,7 +180,6 @@ static void describe(const char *arg, int last_one) unsigned char sha1[20]; struct commit *cmit, *gave_up_on = NULL; struct commit_list *list; - static int initialized = 0; struct commit_name *n; struct possible_tag all_matches[MAX_TAGS]; unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; @@ -192,14 +191,6 @@ static void describe(const char *arg, int last_one) if (!cmit) die("%s is not a valid '%s' object", arg, commit_type); - if (!initialized) { - initialized = 1; - for_each_ref(get_name, NULL); - } - - if (!found_names) - die("cannot describe '%s'", sha1_to_hex(sha1)); - n = cmit->util; if (n) { /* @@ -359,6 +350,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix) return cmd_name_rev(i + argc, args, prefix); } + for_each_ref(get_name, NULL); + if (!found_names) + die("No names found, cannot describe anything."); + if (argc == 0) { describe("HEAD", 1); } else { From f6fdbb6804eac72eb0ccff191162a5911bf2b014 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 19 Oct 2009 02:49:55 -0400 Subject: [PATCH 100/884] cvsimport: fix relative argument filenames One of the first things that cvsimport does is chdir to the newly created git repo. This means that any filenames given to us on the command line will be looked up relative to the git repo directory. This is probably not what the user expects, so let's remember and prepend the original directory for relative filenames. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git-cvsimport.perl | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 1ad20ac964..a7d215c8aa 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -579,10 +579,21 @@ sub get_headref ($) { return $r; } +my $user_filename_prepend = ''; +sub munge_user_filename { + my $name = shift; + return File::Spec->file_name_is_absolute($name) ? + $name : + $user_filename_prepend . $name; +} + -d $git_tree or mkdir($git_tree,0777) or die "Could not create $git_tree: $!"; -chdir($git_tree); +if ($git_tree ne '.') { + $user_filename_prepend = getwd() . '/'; + chdir($git_tree); +} my $last_branch = ""; my $orig_branch = ""; @@ -644,7 +655,7 @@ unless (-d $git_dir) { -f "$git_dir/cvs-authors" and read_author_info("$git_dir/cvs-authors"); if ($opt_A) { - read_author_info($opt_A); + read_author_info(munge_user_filename($opt_A)); write_author_info("$git_dir/cvs-authors"); } @@ -679,7 +690,7 @@ unless ($opt_P) { $? == 0 or die "git-cvsimport: fatal: cvsps reported error\n"; close $cvspsfh; } else { - $cvspsfile = $opt_P; + $cvspsfile = munge_user_filename($opt_P); } open(CVS, "<$cvspsfile") or die $!; From 7f98ebc8fd34107c6234cbad7b776054810b6fe1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 15 Oct 2009 22:59:41 -0700 Subject: [PATCH 101/884] format_commit_message(): fix function signature The format template string was declared as "const void *" for some unknown reason, even though it obviously is meant to be passed a string. Make it "const char *". Signed-off-by: Junio C Hamano --- commit.h | 2 +- pretty.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/commit.h b/commit.h index f4fc5c5589..95f981a1a9 100644 --- a/commit.h +++ b/commit.h @@ -70,7 +70,7 @@ extern char *reencode_commit_message(const struct commit *commit, const char **encoding_p); extern void get_commit_format(const char *arg, struct rev_info *); extern void format_commit_message(const struct commit *commit, - const void *format, struct strbuf *sb, + const char *format, struct strbuf *sb, enum date_mode dmode); extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*, struct strbuf *, diff --git a/pretty.c b/pretty.c index f5983f8baa..587101f846 100644 --- a/pretty.c +++ b/pretty.c @@ -740,7 +740,7 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, } void format_commit_message(const struct commit *commit, - const void *format, struct strbuf *sb, + const char *format, struct strbuf *sb, enum date_mode dmode) { struct format_commit_context context; From 7725cb5e8bfd27887ee0b984b83708eff655ec9e Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 15 Oct 2009 23:32:33 -0700 Subject: [PATCH 102/884] rebase -i: fix reword when using a terminal editor We don't want to use output() on git-commit --amend when rewording the commit message. This leads to confusion as the editor is run in a subshell with it's output saved away, leaving the user with a seemingly frozen terminal. Fix by removing the output part. Signed-off-by: Stephen Boyd Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index a43ee22c64..a1879e3ace 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -346,7 +346,7 @@ do_next () { mark_action_done pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" - output git commit --amend + git commit --amend ;; edit|e) comment_for_reflog edit From ae0b27023018416c0bfe54823466dee67c20705a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 11 Nov 2007 14:14:15 +0000 Subject: [PATCH 103/884] print_wrapped_text(): allow hard newlines print_wrapped_text() will insert its own newlines. Up until now, if the text passed to it contained newlines, they would not be handled properly (the wrapping got confused after that). The strategy is to replace a single new-line with a space, but keep double new-lines so that already-wrapped text with empty lines between paragraphs will be handled properly. However, single new-line characters are only handled this way if the character after it is an alphanumeric character, as per Linus' suggestion. Signed-off-by: Johannes Schindelin --- utf8.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/utf8.c b/utf8.c index db706ac4ed..2aace1d42c 100644 --- a/utf8.c +++ b/utf8.c @@ -310,6 +310,8 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width) if (!c || isspace(c)) { if (w < width || !space) { const char *start = bol; + if (!c && text == start) + return w; if (space) start = space; else @@ -317,13 +319,25 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width) fwrite(start, text - start, 1, stdout); if (!c) return w; - else if (c == '\t') - w |= 0x07; space = text; + if (c == '\t') + w |= 0x07; + else if (c == '\n') { + space++; + if (*space == '\n') { + putchar('\n'); + goto new_line; + } + else if (!isalnum(*space)) + goto new_line; + else + putchar(' '); + } w++; text++; } else { +new_line: putchar('\n'); text = bol = space + isspace(*space); space = NULL; From a94410c8134581f2f11a7db838da4d8725911a3c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 10 Nov 2008 18:47:00 +0100 Subject: [PATCH 104/884] Add strbuf_add_wrapped_text() to utf8.[ch] The newly added function can rewrap text according to a given first-line indent, other-indent and text width. Signed-off-by: Johannes Schindelin --- utf8.c | 33 ++++++++++++++++++++++++--------- utf8.h | 2 ++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/utf8.c b/utf8.c index 2aace1d42c..da996695cc 100644 --- a/utf8.c +++ b/utf8.c @@ -1,4 +1,5 @@ #include "git-compat-util.h" +#include "strbuf.h" #include "utf8.h" /* This code is originally from http://www.cl.cam.ac.uk/~mgk25/ucs/ */ @@ -279,14 +280,22 @@ int is_utf8(const char *text) return 1; } -static void print_spaces(int count) +static inline void strbuf_write(struct strbuf *sb, const char *buf, int len) +{ + if (sb) + strbuf_insert(sb, sb->len, buf, len); + else + fwrite(buf, len, 1, stdout); +} + +static void print_spaces(struct strbuf *buf, int count) { static const char s[] = " "; while (count >= sizeof(s)) { - fwrite(s, sizeof(s) - 1, 1, stdout); + strbuf_write(buf, s, sizeof(s) - 1); count -= sizeof(s) - 1; } - fwrite(s, count, 1, stdout); + strbuf_write(buf, s, count); } /* @@ -295,7 +304,8 @@ static void print_spaces(int count) * If indent is negative, assume that already -indent columns have been * consumed (and no extra indent is necessary for the first line). */ -int print_wrapped_text(const char *text, int indent, int indent2, int width) +int strbuf_add_wrapped_text(struct strbuf *buf, + const char *text, int indent, int indent2, int width) { int w = indent, assume_utf8 = is_utf8(text); const char *bol = text, *space = NULL; @@ -315,8 +325,8 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width) if (space) start = space; else - print_spaces(indent); - fwrite(start, text - start, 1, stdout); + print_spaces(buf, indent); + strbuf_write(buf, start, text - start); if (!c) return w; space = text; @@ -325,20 +335,20 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width) else if (c == '\n') { space++; if (*space == '\n') { - putchar('\n'); + strbuf_write(buf, "\n", 1); goto new_line; } else if (!isalnum(*space)) goto new_line; else - putchar(' '); + strbuf_write(buf, " ", 1); } w++; text++; } else { new_line: - putchar('\n'); + strbuf_write(buf, "\n", 1); text = bol = space + isspace(*space); space = NULL; w = indent = indent2; @@ -354,6 +364,11 @@ new_line: } } +int print_wrapped_text(const char *text, int indent, int indent2, int width) +{ + return strbuf_add_wrapped_text(NULL, text, indent, indent2, width); +} + int is_encoding_utf8(const char *name) { if (!name) diff --git a/utf8.h b/utf8.h index 2f1b14ff49..ae30ae4c6e 100644 --- a/utf8.h +++ b/utf8.h @@ -10,6 +10,8 @@ int is_utf8(const char *text); int is_encoding_utf8(const char *name); int print_wrapped_text(const char *text, int indent, int indent2, int len); +int strbuf_add_wrapped_text(struct strbuf *buf, + const char *text, int indent, int indent2, int width); #ifndef NO_ICONV char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding); From a97a74686d70a318cd802003498054cc1e8b0ae2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Oct 2009 12:21:57 +0200 Subject: [PATCH 105/884] Introduce commit notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit notes are blobs which are shown together with the commit message. These blobs are taken from the notes ref, which you can configure by the config variable core.notesRef, which in turn can be overridden by the environment variable GIT_NOTES_REF. The notes ref is a branch which contains "files" whose names are the names of the corresponding commits (i.e. the SHA-1). The rationale for putting this information into a ref is this: we want to be able to fetch and possibly union-merge the notes, maybe even look at the date when a note was introduced, and we want to store them efficiently together with the other objects. This patch has been improved by the following contributions: - Thomas Rast: fix core.notesRef documentation - Tor Arne Vestbø: fix printing of multi-line notes - Alex Riesen: Using char array instead of char pointer costs less BSS - Johan Herland: Plug leak when msg is good, but msglen or type causes return Signed-off-by: Johannes Schindelin Signed-off-by: Thomas Rast Signed-off-by: Tor Arne Vestbø Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano get_commit_notes(): Plug memory leak when 'if' triggers, but not because of read_sha1_file() failure --- Documentation/config.txt | 13 ++++++++ Makefile | 2 ++ cache.h | 4 +++ commit.c | 1 + config.c | 5 +++ environment.c | 1 + notes.c | 70 ++++++++++++++++++++++++++++++++++++++++ notes.h | 7 ++++ pretty.c | 5 +++ 9 files changed, 108 insertions(+) create mode 100644 notes.c create mode 100644 notes.h diff --git a/Documentation/config.txt b/Documentation/config.txt index cd1781498e..57d64e4d99 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -454,6 +454,19 @@ On some file system/operating system combinations, this is unreliable. Set this config setting to 'rename' there; However, This will remove the check that makes sure that existing object files will not get overwritten. +core.notesRef:: + When showing commit messages, also show notes which are stored in + the given ref. This ref is expected to contain files named + after the full SHA-1 of the commit they annotate. ++ +If such a file exists in the given ref, the referenced blob is read, and +appended to the commit message, separated by a "Notes:" line. If the +given ref itself does not exist, it is not an error, but means that no +notes should be printed. ++ +This setting defaults to "refs/notes/commits", and can be overridden by +the `GIT_NOTES_REF` environment variable. + add.ignore-errors:: Tells 'git-add' to continue adding files when some files cannot be added due to indexing errors. Equivalent to the '--ignore-errors' diff --git a/Makefile b/Makefile index fea237bc80..9a6a729868 100644 --- a/Makefile +++ b/Makefile @@ -432,6 +432,7 @@ LIB_H += ll-merge.h LIB_H += log-tree.h LIB_H += mailmap.h LIB_H += merge-recursive.h +LIB_H += notes.h LIB_H += object.h LIB_H += pack.h LIB_H += pack-refs.h @@ -516,6 +517,7 @@ LIB_OBJS += match-trees.o LIB_OBJS += merge-file.o LIB_OBJS += merge-recursive.o LIB_OBJS += name-hash.o +LIB_OBJS += notes.o LIB_OBJS += object.o LIB_OBJS += pack-check.o LIB_OBJS += pack-refs.o diff --git a/cache.h b/cache.h index a5eeead1e2..4f0ddeca11 100644 --- a/cache.h +++ b/cache.h @@ -372,6 +372,8 @@ static inline enum object_type object_type(unsigned int mode) #define GITATTRIBUTES_FILE ".gitattributes" #define INFOATTRIBUTES_FILE "info/attributes" #define ATTRIBUTE_MACRO_PREFIX "[attr]" +#define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF" +#define GIT_NOTES_DEFAULT_REF "refs/notes/commits" extern int is_bare_repository_cfg; extern int is_bare_repository(void); @@ -567,6 +569,8 @@ enum object_creation_mode { extern enum object_creation_mode object_creation_mode; +extern char *notes_ref_name; + extern int grafts_replace_parents; #define GIT_REPO_VERSION 0 diff --git a/commit.c b/commit.c index fedbd5e526..5ade8edf81 100644 --- a/commit.c +++ b/commit.c @@ -5,6 +5,7 @@ #include "utf8.h" #include "diff.h" #include "revision.h" +#include "notes.h" int save_commit_buffer = 1; diff --git a/config.c b/config.c index c644061136..51f22088e7 100644 --- a/config.c +++ b/config.c @@ -467,6 +467,11 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.notesref")) { + notes_ref_name = xstrdup(value); + return 0; + } + if (!strcmp(var, "core.pager")) return git_config_string(&pager_program, var, value); diff --git a/environment.c b/environment.c index 5de6837840..571ab56b7e 100644 --- a/environment.c +++ b/environment.c @@ -49,6 +49,7 @@ enum push_default_type push_default = PUSH_DEFAULT_MATCHING; #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS #endif enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; +char *notes_ref_name; int grafts_replace_parents = 1; /* Parallel index stat data preload? */ diff --git a/notes.c b/notes.c new file mode 100644 index 0000000000..66379ffd22 --- /dev/null +++ b/notes.c @@ -0,0 +1,70 @@ +#include "cache.h" +#include "commit.h" +#include "notes.h" +#include "refs.h" +#include "utf8.h" +#include "strbuf.h" + +static int initialized; + +void get_commit_notes(const struct commit *commit, struct strbuf *sb, + const char *output_encoding) +{ + static const char utf8[] = "utf-8"; + struct strbuf name = STRBUF_INIT; + unsigned char sha1[20]; + char *msg, *msg_p; + unsigned long linelen, msglen; + enum object_type type; + + if (!initialized) { + const char *env = getenv(GIT_NOTES_REF_ENVIRONMENT); + if (env) + notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT); + else if (!notes_ref_name) + notes_ref_name = GIT_NOTES_DEFAULT_REF; + if (notes_ref_name && read_ref(notes_ref_name, sha1)) + notes_ref_name = NULL; + initialized = 1; + } + + if (!notes_ref_name) + return; + + strbuf_addf(&name, "%s:%s", notes_ref_name, + sha1_to_hex(commit->object.sha1)); + if (get_sha1(name.buf, sha1)) + return; + + if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen || + type != OBJ_BLOB) { + free(msg); + return; + } + + if (output_encoding && *output_encoding && + strcmp(utf8, output_encoding)) { + char *reencoded = reencode_string(msg, output_encoding, utf8); + if (reencoded) { + free(msg); + msg = reencoded; + msglen = strlen(msg); + } + } + + /* we will end the annotation by a newline anyway */ + if (msglen && msg[msglen - 1] == '\n') + msglen--; + + strbuf_addstr(sb, "\nNotes:\n"); + + for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) { + linelen = strchrnul(msg_p, '\n') - msg_p; + + strbuf_addstr(sb, " "); + strbuf_add(sb, msg_p, linelen); + strbuf_addch(sb, '\n'); + } + + free(msg); +} diff --git a/notes.h b/notes.h new file mode 100644 index 0000000000..79d21b65f5 --- /dev/null +++ b/notes.h @@ -0,0 +1,7 @@ +#ifndef NOTES_H +#define NOTES_H + +void get_commit_notes(const struct commit *commit, struct strbuf *sb, + const char *output_encoding); + +#endif diff --git a/pretty.c b/pretty.c index f5983f8baa..e25db81eaa 100644 --- a/pretty.c +++ b/pretty.c @@ -6,6 +6,7 @@ #include "string-list.h" #include "mailmap.h" #include "log-tree.h" +#include "notes.h" #include "color.h" static char *user_format; @@ -975,5 +976,9 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, */ if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body) strbuf_addch(sb, '\n'); + + if (fmt != CMIT_FMT_ONELINE) + get_commit_notes(commit, sb, encoding); + free(reencoded); } From 65d9fb487f36d4a12a169dc18cbbb5225337c085 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Oct 2009 12:21:58 +0200 Subject: [PATCH 106/884] Add a script to edit/inspect notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The script 'git notes' allows you to edit and show commit notes, by calling either git notes show or git notes edit This patch has been improved by the following contributions: - Tor Arne Vestbø: fix printing of multi-line notes - Michael J Gruber: test and handle empty notes gracefully - Thomas Rast: - only clean up message file when editing - use GIT_EDITOR and core.editor over VISUAL/EDITOR - t3301: fix confusing quoting in test for valid notes ref - t3301: use test_must_fail instead of ! - refuse to edit notes outside refs/notes/ - Junio C Hamano: tests: fix "export var=val" - Christian Couder: documentation: fix 'linkgit' macro in "git-notes.txt" - Johan Herland: minor cleanup and bugfixing in git-notes.sh (v2) Signed-off-by: Johannes Schindelin Signed-off-by: Tor Arne Vestbø Signed-off-by: Michael J Gruber Signed-off-by: Thomas Rast Signed-off-by: Christian Couder Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- .gitignore | 1 + Documentation/git-notes.txt | 46 +++++++++++++++ Makefile | 1 + command-list.txt | 1 + git-notes.sh | 73 +++++++++++++++++++++++ t/t3301-notes.sh | 114 ++++++++++++++++++++++++++++++++++++ 6 files changed, 236 insertions(+) create mode 100644 Documentation/git-notes.txt create mode 100755 git-notes.sh create mode 100755 t/t3301-notes.sh diff --git a/.gitignore b/.gitignore index 51a37b1af7..cbafa64b44 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ git-mktag git-mktree git-name-rev git-mv +git-notes git-pack-redundant git-pack-objects git-pack-refs diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt new file mode 100644 index 0000000000..7136016c74 --- /dev/null +++ b/Documentation/git-notes.txt @@ -0,0 +1,46 @@ +git-notes(1) +============ + +NAME +---- +git-notes - Add/inspect commit notes + +SYNOPSIS +-------- +[verse] +'git-notes' (edit | show) [commit] + +DESCRIPTION +----------- +This command allows you to add notes to commit messages, without +changing the commit. To discern these notes from the message stored +in the commit object, the notes are indented like the message, after +an unindented line saying "Notes:". + +To disable commit notes, you have to set the config variable +core.notesRef to the empty string. Alternatively, you can set it +to a different ref, something like "refs/notes/bugzilla". This setting +can be overridden by the environment variable "GIT_NOTES_REF". + + +SUBCOMMANDS +----------- + +edit:: + Edit the notes for a given commit (defaults to HEAD). + +show:: + Show the notes for a given commit (defaults to HEAD). + + +Author +------ +Written by Johannes Schindelin + +Documentation +------------- +Documentation by Johannes Schindelin + +GIT +--- +Part of the linkgit:git[7] suite diff --git a/Makefile b/Makefile index 9a6a729868..8d7cec79d9 100644 --- a/Makefile +++ b/Makefile @@ -321,6 +321,7 @@ SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-mergetool--lib.sh +SCRIPT_SH += git-notes.sh SCRIPT_SH += git-parse-remote.sh SCRIPT_SH += git-pull.sh SCRIPT_SH += git-quiltimport.sh diff --git a/command-list.txt b/command-list.txt index fb03a2ebb5..4296941b68 100644 --- a/command-list.txt +++ b/command-list.txt @@ -74,6 +74,7 @@ git-mktag plumbingmanipulators git-mktree plumbingmanipulators git-mv mainporcelain common git-name-rev plumbinginterrogators +git-notes mainporcelain git-pack-objects plumbingmanipulators git-pack-redundant plumbinginterrogators git-pack-refs ancillarymanipulators diff --git a/git-notes.sh b/git-notes.sh new file mode 100755 index 0000000000..f06c2549c4 --- /dev/null +++ b/git-notes.sh @@ -0,0 +1,73 @@ +#!/bin/sh + +USAGE="(edit | show) [commit]" +. git-sh-setup + +test -n "$3" && usage + +test -z "$1" && usage +ACTION="$1"; shift + +test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="$(git config core.notesref)" +test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="refs/notes/commits" + +COMMIT=$(git rev-parse --verify --default HEAD "$@") || +die "Invalid commit: $@" + +case "$ACTION" in +edit) + if [ "${GIT_NOTES_REF#refs/notes/}" = "$GIT_NOTES_REF" ]; then + die "Refusing to edit notes in $GIT_NOTES_REF (outside of refs/notes/)" + fi + + MSG_FILE="$GIT_DIR/new-notes-$COMMIT" + GIT_INDEX_FILE="$MSG_FILE.idx" + export GIT_INDEX_FILE + + trap ' + test -f "$MSG_FILE" && rm "$MSG_FILE" + test -f "$GIT_INDEX_FILE" && rm "$GIT_INDEX_FILE" + ' 0 + + GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MSG_FILE" + + CURRENT_HEAD=$(git show-ref "$GIT_NOTES_REF" | cut -f 1 -d ' ') + if [ -z "$CURRENT_HEAD" ]; then + PARENT= + else + PARENT="-p $CURRENT_HEAD" + git read-tree "$GIT_NOTES_REF" || die "Could not read index" + git cat-file blob :$COMMIT >> "$MSG_FILE" 2> /dev/null + fi + + core_editor="$(git config core.editor)" + ${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} "$MSG_FILE" + + grep -v ^# < "$MSG_FILE" | git stripspace > "$MSG_FILE".processed + mv "$MSG_FILE".processed "$MSG_FILE" + if [ -s "$MSG_FILE" ]; then + BLOB=$(git hash-object -w "$MSG_FILE") || + die "Could not write into object database" + git update-index --add --cacheinfo 0644 $BLOB $COMMIT || + die "Could not write index" + else + test -z "$CURRENT_HEAD" && + die "Will not initialise with empty tree" + git update-index --force-remove $COMMIT || + die "Could not update index" + fi + + TREE=$(git write-tree) || die "Could not write tree" + NEW_HEAD=$(echo Annotate $COMMIT | git commit-tree $TREE $PARENT) || + die "Could not annotate" + git update-ref -m "Annotate $COMMIT" \ + "$GIT_NOTES_REF" $NEW_HEAD $CURRENT_HEAD +;; +show) + git rev-parse -q --verify "$GIT_NOTES_REF":$COMMIT > /dev/null || + die "No note for commit $COMMIT." + git show "$GIT_NOTES_REF":$COMMIT +;; +*) + usage +esac diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh new file mode 100755 index 0000000000..73e53be40b --- /dev/null +++ b/t/t3301-notes.sh @@ -0,0 +1,114 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='Test commit notes' + +. ./test-lib.sh + +cat > fake_editor.sh << \EOF +echo "$MSG" > "$1" +echo "$MSG" >& 2 +EOF +chmod a+x fake_editor.sh +VISUAL=./fake_editor.sh +export VISUAL + +test_expect_success 'cannot annotate non-existing HEAD' ' + (MSG=3 && export MSG && test_must_fail git notes edit) +' + +test_expect_success setup ' + : > a1 && + git add a1 && + test_tick && + git commit -m 1st && + : > a2 && + git add a2 && + test_tick && + git commit -m 2nd +' + +test_expect_success 'need valid notes ref' ' + (MSG=1 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF && + test_must_fail git notes edit) && + (MSG=2 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF && + test_must_fail git notes show) +' + +test_expect_success 'refusing to edit in refs/heads/' ' + (MSG=1 GIT_NOTES_REF=refs/heads/bogus && + export MSG GIT_NOTES_REF && + test_must_fail git notes edit) +' + +test_expect_success 'refusing to edit in refs/remotes/' ' + (MSG=1 GIT_NOTES_REF=refs/remotes/bogus && + export MSG GIT_NOTES_REF && + test_must_fail git notes edit) +' + +# 1 indicates caught gracefully by die, 128 means git-show barked +test_expect_success 'handle empty notes gracefully' ' + git notes show ; test 1 = $? +' + +test_expect_success 'create notes' ' + git config core.notesRef refs/notes/commits && + MSG=b1 git notes edit && + test ! -f .git/new-notes && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b1 = $(git notes show) && + git show HEAD^ && + test_must_fail git notes show HEAD^ +' + +cat > expect << EOF +commit 268048bfb8a1fb38e703baceb8ab235421bf80c5 +Author: A U Thor +Date: Thu Apr 7 15:14:13 2005 -0700 + + 2nd + +Notes: + b1 +EOF + +test_expect_success 'show notes' ' + ! (git cat-file commit HEAD | grep b1) && + git log -1 > output && + test_cmp expect output +' +test_expect_success 'create multi-line notes (setup)' ' + : > a3 && + git add a3 && + test_tick && + git commit -m 3rd && + MSG="b3 +c3c3c3c3 +d3d3d3" git notes edit +' + +cat > expect-multiline << EOF +commit 1584215f1d29c65e99c6c6848626553fdd07fd75 +Author: A U Thor +Date: Thu Apr 7 15:15:13 2005 -0700 + + 3rd + +Notes: + b3 + c3c3c3c3 + d3d3d3 +EOF + +printf "\n" >> expect-multiline +cat expect >> expect-multiline + +test_expect_success 'show multi-line notes' ' + git log -2 > output && + test_cmp expect-multiline output +' + +test_done From fd53c9eb445815696bf84c4701b9af73b5d7f50d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Oct 2009 12:21:59 +0200 Subject: [PATCH 107/884] Speed up git notes lookup To avoid looking up each and every commit in the notes ref's tree object, which is very expensive, speed things up by slurping the tree object's contents into a hash_map. The idea for the hashmap singleton is from David Reiss, initial benchmarking by Jeff King. Note: the implementation allows for arbitrary entries in the notes tree object, ignoring those that do not reference a valid object. This allows you to annotate arbitrary branches, or objects. This patch has been improved by the following contributions: - Junio C Hamano: fixed an obvious error in initialize_hash_map() Signed-off-by: Johannes Schindelin Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 102 insertions(+), 10 deletions(-) diff --git a/notes.c b/notes.c index 66379ffd22..2b66723f5f 100644 --- a/notes.c +++ b/notes.c @@ -4,15 +4,112 @@ #include "refs.h" #include "utf8.h" #include "strbuf.h" +#include "tree-walk.h" + +struct entry { + unsigned char commit_sha1[20]; + unsigned char notes_sha1[20]; +}; + +struct hash_map { + struct entry *entries; + off_t count, size; +}; static int initialized; +static struct hash_map hash_map; + +static int hash_index(struct hash_map *map, const unsigned char *sha1) +{ + int i = ((*(unsigned int *)sha1) % map->size); + + for (;;) { + unsigned char *current = map->entries[i].commit_sha1; + + if (!hashcmp(sha1, current)) + return i; + + if (is_null_sha1(current)) + return -1 - i; + + if (++i == map->size) + i = 0; + } +} + +static void add_entry(const unsigned char *commit_sha1, + const unsigned char *notes_sha1) +{ + int index; + + if (hash_map.count + 1 > hash_map.size >> 1) { + int i, old_size = hash_map.size; + struct entry *old = hash_map.entries; + + hash_map.size = old_size ? old_size << 1 : 64; + hash_map.entries = (struct entry *) + xcalloc(sizeof(struct entry), hash_map.size); + + for (i = 0; i < old_size; i++) + if (!is_null_sha1(old[i].commit_sha1)) { + index = -1 - hash_index(&hash_map, + old[i].commit_sha1); + memcpy(hash_map.entries + index, old + i, + sizeof(struct entry)); + } + free(old); + } + + index = hash_index(&hash_map, commit_sha1); + if (index < 0) { + index = -1 - index; + hash_map.count++; + } + + hashcpy(hash_map.entries[index].commit_sha1, commit_sha1); + hashcpy(hash_map.entries[index].notes_sha1, notes_sha1); +} + +static void initialize_hash_map(const char *notes_ref_name) +{ + unsigned char sha1[20], commit_sha1[20]; + unsigned mode; + struct tree_desc desc; + struct name_entry entry; + void *buf; + + if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) || + get_tree_entry(commit_sha1, "", sha1, &mode)) + return; + + buf = fill_tree_descriptor(&desc, sha1); + if (!buf) + die("Could not read %s for notes-index", sha1_to_hex(sha1)); + + while (tree_entry(&desc, &entry)) + if (!get_sha1(entry.path, commit_sha1)) + add_entry(commit_sha1, entry.sha1); + free(buf); +} + +static unsigned char *lookup_notes(const unsigned char *commit_sha1) +{ + int index; + + if (!hash_map.size) + return NULL; + + index = hash_index(&hash_map, commit_sha1); + if (index < 0) + return NULL; + return hash_map.entries[index].notes_sha1; +} void get_commit_notes(const struct commit *commit, struct strbuf *sb, const char *output_encoding) { static const char utf8[] = "utf-8"; - struct strbuf name = STRBUF_INIT; - unsigned char sha1[20]; + unsigned char *sha1; char *msg, *msg_p; unsigned long linelen, msglen; enum object_type type; @@ -23,17 +120,12 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb, notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT); else if (!notes_ref_name) notes_ref_name = GIT_NOTES_DEFAULT_REF; - if (notes_ref_name && read_ref(notes_ref_name, sha1)) - notes_ref_name = NULL; + initialize_hash_map(notes_ref_name); initialized = 1; } - if (!notes_ref_name) - return; - - strbuf_addf(&name, "%s:%s", notes_ref_name, - sha1_to_hex(commit->object.sha1)); - if (get_sha1(name.buf, sha1)) + sha1 = lookup_notes(commit->object.sha1); + if (!sha1) return; if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen || From a5b0c24f3e9a29f8fe496f49db82f2e3f1b687ce Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Oct 2009 12:22:00 +0200 Subject: [PATCH 108/884] Add an expensive test for git-notes git-notes have the potential of being pretty expensive, so test with a lot of commits. A lot. So to make things cheaper, you have to opt-in explicitely, by setting the environment variable GIT_NOTES_TIMING_TESTS. This patch has been improved by the following contributions: - Junio C Hamano: tests: fix "export var=val" Signed-off-by: Johannes Schindelin Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- t/t3302-notes-index-expensive.sh | 98 ++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100755 t/t3302-notes-index-expensive.sh diff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh new file mode 100755 index 0000000000..0ef3e95959 --- /dev/null +++ b/t/t3302-notes-index-expensive.sh @@ -0,0 +1,98 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='Test commit notes index (expensive!)' + +. ./test-lib.sh + +test -z "$GIT_NOTES_TIMING_TESTS" && { + say Skipping timing tests + test_done + exit +} + +create_repo () { + number_of_commits=$1 + nr=0 + parent= + test -d .git || { + git init && + tree=$(git write-tree) && + while [ $nr -lt $number_of_commits ]; do + test_tick && + commit=$(echo $nr | git commit-tree $tree $parent) || + return + parent="-p $commit" + nr=$(($nr+1)) + done && + git update-ref refs/heads/master $commit && + { + GIT_INDEX_FILE=.git/temp; export GIT_INDEX_FILE; + git rev-list HEAD | cat -n | sed "s/^[ ][ ]*/ /g" | + while read nr sha1; do + blob=$(echo note $nr | git hash-object -w --stdin) && + echo $sha1 | sed "s/^/0644 $blob 0 /" + done | git update-index --index-info && + tree=$(git write-tree) && + test_tick && + commit=$(echo notes | git commit-tree $tree) && + git update-ref refs/notes/commits $commit + } && + git config core.notesRef refs/notes/commits + } +} + +test_notes () { + count=$1 && + git config core.notesRef refs/notes/commits && + git log | grep "^ " > output && + i=1 && + while [ $i -le $count ]; do + echo " $(($count-$i))" && + echo " note $i" && + i=$(($i+1)); + done > expect && + git diff expect output +} + +cat > time_notes << \EOF + mode=$1 + i=1 + while [ $i -lt $2 ]; do + case $1 in + no-notes) + GIT_NOTES_REF=non-existing; export GIT_NOTES_REF + ;; + notes) + unset GIT_NOTES_REF + ;; + esac + git log >/dev/null + i=$(($i+1)) + done +EOF + +time_notes () { + for mode in no-notes notes + do + echo $mode + /usr/bin/time sh ../time_notes $mode $1 + done +} + +for count in 10 100 1000 10000; do + + mkdir $count + (cd $count; + + test_expect_success "setup $count" "create_repo $count" + + test_expect_success 'notes work' "test_notes $count" + + test_expect_success 'notes timing' "time_notes 100" + ) +done + +test_done From d9246d4303f441c0e30614cd3a97a80fbe9354b6 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:01 +0200 Subject: [PATCH 109/884] Teach "-m " and "-F " to "git notes edit" The "-m" and "-F" options are already the established method (in both git-commit and git-tag) to specify a commit/tag message without invoking the editor. This patch teaches "git notes edit" to respect the same options for specifying a notes message without invoking the editor. Multiple "-m" and/or "-F" options are concatenated as separate paragraphs. The patch also updates the "git notes" documentation and adds selftests for the new functionality. Unfortunately, the added selftests include a couple of lines with trailing whitespace (without these the test will fail). This may cause git to warn about "whitespace errors". This patch has been improved by the following contributions: - Thomas Rast: fix trailing whitespace in t3301 Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 16 +++++++++- git-notes.sh | 64 ++++++++++++++++++++++++++++++++----- t/t3301-notes.sh | 36 +++++++++++++++++++++ 3 files changed, 107 insertions(+), 9 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 7136016c74..94cceb1319 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -8,7 +8,7 @@ git-notes - Add/inspect commit notes SYNOPSIS -------- [verse] -'git-notes' (edit | show) [commit] +'git-notes' (edit [-F | -m ] | show) [commit] DESCRIPTION ----------- @@ -33,6 +33,20 @@ show:: Show the notes for a given commit (defaults to HEAD). +OPTIONS +------- +-m :: + Use the given note message (instead of prompting). + If multiple `-m` (or `-F`) options are given, their + values are concatenated as separate paragraphs. + +-F :: + Take the note message from the given file. Use '-' to + read the note message from the standard input. + If multiple `-F` (or `-m`) options are given, their + values are concatenated as separate paragraphs. + + Author ------ Written by Johannes Schindelin diff --git a/git-notes.sh b/git-notes.sh index f06c2549c4..e642e47d9f 100755 --- a/git-notes.sh +++ b/git-notes.sh @@ -1,16 +1,59 @@ #!/bin/sh -USAGE="(edit | show) [commit]" +USAGE="(edit [-F | -m ] | show) [commit]" . git-sh-setup -test -n "$3" && usage - test -z "$1" && usage ACTION="$1"; shift test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="$(git config core.notesref)" test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="refs/notes/commits" +MESSAGE= +while test $# != 0 +do + case "$1" in + -m) + test "$ACTION" = "edit" || usage + shift + if test "$#" = "0"; then + die "error: option -m needs an argument" + else + if [ -z "$MESSAGE" ]; then + MESSAGE="$1" + else + MESSAGE="$MESSAGE + +$1" + fi + shift + fi + ;; + -F) + test "$ACTION" = "edit" || usage + shift + if test "$#" = "0"; then + die "error: option -F needs an argument" + else + if [ -z "$MESSAGE" ]; then + MESSAGE="$(cat "$1")" + else + MESSAGE="$MESSAGE + +$(cat "$1")" + fi + shift + fi + ;; + -*) + usage + ;; + *) + break + ;; + esac +done + COMMIT=$(git rev-parse --verify --default HEAD "$@") || die "Invalid commit: $@" @@ -29,19 +72,24 @@ edit) test -f "$GIT_INDEX_FILE" && rm "$GIT_INDEX_FILE" ' 0 - GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MSG_FILE" - CURRENT_HEAD=$(git show-ref "$GIT_NOTES_REF" | cut -f 1 -d ' ') if [ -z "$CURRENT_HEAD" ]; then PARENT= else PARENT="-p $CURRENT_HEAD" git read-tree "$GIT_NOTES_REF" || die "Could not read index" - git cat-file blob :$COMMIT >> "$MSG_FILE" 2> /dev/null fi - core_editor="$(git config core.editor)" - ${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} "$MSG_FILE" + if [ -z "$MESSAGE" ]; then + GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MSG_FILE" + if [ ! -z "$CURRENT_HEAD" ]; then + git cat-file blob :$COMMIT >> "$MSG_FILE" 2> /dev/null + fi + core_editor="$(git config core.editor)" + ${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} "$MSG_FILE" + else + echo "$MESSAGE" > "$MSG_FILE" + fi grep -v ^# < "$MSG_FILE" | git stripspace > "$MSG_FILE".processed mv "$MSG_FILE".processed "$MSG_FILE" diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 73e53be40b..1e34f4836f 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -110,5 +110,41 @@ test_expect_success 'show multi-line notes' ' git log -2 > output && test_cmp expect-multiline output ' +test_expect_success 'create -m and -F notes (setup)' ' + : > a4 && + git add a4 && + test_tick && + git commit -m 4th && + echo "xyzzy" > note5 && + git notes edit -m spam -F note5 -m "foo +bar +baz" +' + +whitespace=" " +cat > expect-m-and-F << EOF +commit 15023535574ded8b1a89052b32673f84cf9582b8 +Author: A U Thor +Date: Thu Apr 7 15:16:13 2005 -0700 + + 4th + +Notes: + spam +$whitespace + xyzzy +$whitespace + foo + bar + baz +EOF + +printf "\n" >> expect-m-and-F +cat expect-multiline >> expect-m-and-F + +test_expect_success 'show -m and -F notes' ' + git log -3 > output && + test_cmp expect-m-and-F output +' test_done From a8dd2e7d2bd7472b4e02b07aeef795de9c74f3e7 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:02 +0200 Subject: [PATCH 110/884] fast-import: Add support for importing commit notes Introduce a 'notemodify' subcommand of the 'commit' command. This subcommand is similar to 'filemodify', except that no mode is supplied (all notes have mode 0644), and the path is set to the hex SHA1 of the given "comittish". This enables fast import of note objects along with their associated commits, since the notes can now be named using the mark references of their corresponding commits. The patch also includes a test case of the added functionality. Signed-off-by: Johan Herland Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-fast-import.txt | 45 ++++++-- fast-import.c | 88 +++++++++++++++- t/t9300-fast-import.sh | 166 ++++++++++++++++++++++++++++++ 3 files changed, 289 insertions(+), 10 deletions(-) diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index c2f483a8d2..288032c7b8 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -316,7 +316,7 @@ change to the project. data ('from' SP LF)? ('merge' SP LF)? - (filemodify | filedelete | filecopy | filerename | filedeleteall)* + (filemodify | filedelete | filecopy | filerename | filedeleteall | notemodify)* LF? .... @@ -339,14 +339,13 @@ commit message use a 0 length data. Commit messages are free-form and are not interpreted by Git. Currently they must be encoded in UTF-8, as fast-import does not permit other encodings to be specified. -Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename` -and `filedeleteall` commands +Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename`, +`filedeleteall` and `notemodify` commands may be included to update the contents of the branch prior to creating the commit. These commands may be supplied in any order. However it is recommended that a `filedeleteall` command precede -all `filemodify`, `filecopy` and `filerename` commands in the same -commit, as `filedeleteall` -wipes the branch clean (see below). +all `filemodify`, `filecopy`, `filerename` and `notemodify` commands in +the same commit, as `filedeleteall` wipes the branch clean (see below). The `LF` after the command is optional (it used to be required). @@ -595,6 +594,40 @@ more memory per active branch (less than 1 MiB for even most large projects); so frontends that can easily obtain only the affected paths for a commit are encouraged to do so. +`notemodify` +^^^^^^^^^^^^ +Included in a `commit` command to add a new note (annotating a given +commit) or change the content of an existing note. This command has +two different means of specifying the content of the note. + +External data format:: + The data content for the note was already supplied by a prior + `blob` command. The frontend just needs to connect it to the + commit that is to be annotated. ++ +.... + 'N' SP SP LF +.... ++ +Here `` can be either a mark reference (`:`) +set by a prior `blob` command, or a full 40-byte SHA-1 of an +existing Git blob object. + +Inline data format:: + The data content for the note has not been supplied yet. + The frontend wants to supply it as part of this modify + command. ++ +.... + 'N' SP 'inline' SP LF + data +.... ++ +See below for a detailed description of the `data` command. + +In both formats `` is any of the commit specification +expressions also accepted by `from` (see above). + `mark` ~~~~~~ Arranges for fast-import to save a reference to the current object, allowing diff --git a/fast-import.c b/fast-import.c index 6faaaacb68..b41d29fd31 100644 --- a/fast-import.c +++ b/fast-import.c @@ -22,8 +22,8 @@ Format of STDIN stream: ('author' sp name sp '<' email '>' sp when lf)? 'committer' sp name sp '<' email '>' sp when lf commit_msg - ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? - ('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)* + ('from' sp committish lf)? + ('merge' sp committish lf)* file_change* lf?; commit_msg ::= data; @@ -41,15 +41,18 @@ Format of STDIN stream: file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf; file_inm ::= 'M' sp mode sp 'inline' sp path_str lf data; + note_obm ::= 'N' sp (hexsha1 | idnum) sp committish lf; + note_inm ::= 'N' sp 'inline' sp committish lf + data; new_tag ::= 'tag' sp tag_str lf - 'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf + 'from' sp committish lf ('tagger' sp name sp '<' email '>' sp when lf)? tag_msg; tag_msg ::= data; reset_branch ::= 'reset' sp ref_str lf - ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? + ('from' sp committish lf)? lf?; checkpoint ::= 'checkpoint' lf @@ -88,6 +91,7 @@ Format of STDIN stream: # stream formatting is: \, " and LF. Otherwise these values # are UTF8. # + committish ::= (ref_str | hexsha1 | sha1exp_str | idnum); ref_str ::= ref; sha1exp_str ::= sha1exp; tag_str ::= tag; @@ -2006,6 +2010,80 @@ static void file_change_cr(struct branch *b, int rename) leaf.tree); } +static void note_change_n(struct branch *b) +{ + const char *p = command_buf.buf + 2; + static struct strbuf uq = STRBUF_INIT; + struct object_entry *oe = oe; + struct branch *s; + unsigned char sha1[20], commit_sha1[20]; + uint16_t inline_data = 0; + + /* or 'inline' */ + if (*p == ':') { + char *x; + oe = find_mark(strtoumax(p + 1, &x, 10)); + hashcpy(sha1, oe->sha1); + p = x; + } else if (!prefixcmp(p, "inline")) { + inline_data = 1; + p += 6; + } 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); + + /* */ + s = lookup_branch(p); + if (s) { + hashcpy(commit_sha1, s->sha1); + } else if (*p == ':') { + uintmax_t commit_mark = strtoumax(p + 1, NULL, 10); + struct object_entry *commit_oe = find_mark(commit_mark); + if (commit_oe->type != OBJ_COMMIT) + die("Mark :%" PRIuMAX " not a commit", commit_mark); + hashcpy(commit_sha1, commit_oe->sha1); + } else if (!get_sha1(p, commit_sha1)) { + unsigned long size; + char *buf = read_object_with_reference(commit_sha1, + commit_type, &size, commit_sha1); + if (!buf || size < 46) + die("Not a valid commit: %s", p); + free(buf); + } else + die("Invalid ref name or SHA1 expression: %s", p); + + if (inline_data) { + static struct strbuf buf = STRBUF_INIT; + + if (p != uq.buf) { + strbuf_addstr(&uq, p); + p = uq.buf; + } + read_next_command(); + parse_data(&buf); + store_object(OBJ_BLOB, &buf, &last_blob, sha1, 0); + } else if (oe) { + if (oe->type != OBJ_BLOB) + die("Not a blob (actually a %s): %s", + typename(oe->type), command_buf.buf); + } else { + enum object_type type = sha1_object_info(sha1, NULL); + if (type < 0) + die("Blob not found: %s", command_buf.buf); + if (type != OBJ_BLOB) + die("Not a blob (actually a %s): %s", + typename(type), command_buf.buf); + } + + tree_content_set(&b->branch_tree, sha1_to_hex(commit_sha1), sha1, + S_IFREG | 0644, NULL); +} + static void file_change_deleteall(struct branch *b) { release_tree_content_recursive(b->branch_tree.tree); @@ -2175,6 +2253,8 @@ static void parse_new_commit(void) file_change_cr(b, 1); else if (!prefixcmp(command_buf.buf, "C ")) file_change_cr(b, 0); + else if (!prefixcmp(command_buf.buf, "N ")) + note_change_n(b); else if (!strcmp("deleteall", command_buf.buf)) file_change_deleteall(b); else { diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 821be7ce8d..b49815d108 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -1088,4 +1088,170 @@ INPUT_END test_expect_success 'P: fail on blob mark in gitlink' ' test_must_fail git fast-import input < $GIT_COMMITTER_DATE +data < $GIT_COMMITTER_DATE +data < $GIT_COMMITTER_DATE +data < $GIT_COMMITTER_DATE +data <expect < $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +first (:3) +EOF +test_expect_success \ + 'Q: verify first commit' \ + 'git cat-file commit notes-test~2 | sed 1d >actual && + test_cmp expect actual' + +cat >expect < $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +second (:5) +EOF +test_expect_success \ + 'Q: verify second commit' \ + 'git cat-file commit notes-test^ | sed 1d >actual && + test_cmp expect actual' + +cat >expect < $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +third (:6) +EOF +test_expect_success \ + 'Q: verify third commit' \ + 'git cat-file commit notes-test | sed 1d >actual && + test_cmp expect actual' + +cat >expect < $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +notes (:9) +EOF +test_expect_success \ + 'Q: verify notes commit' \ + 'git cat-file commit refs/notes/foobar | sed 1d >actual && + test_cmp expect actual' + +cat >expect.unsorted <expect +test_expect_success \ + 'Q: verify notes tree' \ + 'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual' + +echo "$note1_data" >expect +test_expect_success \ + 'Q: verify note for first commit' \ + 'git cat-file blob refs/notes/foobar:$commit1 >actual && test_cmp expect actual' + +echo "$note2_data" >expect +test_expect_success \ + 'Q: verify note for second commit' \ + 'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual' + +echo "$note3_data" >expect +test_expect_success \ + 'Q: verify note for third commit' \ + 'git cat-file blob refs/notes/foobar:$commit3 >actual && test_cmp expect actual' + test_done From 3ed24b6aaf35d6ca1eef2643cd0b9128eb152cda Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:03 +0200 Subject: [PATCH 111/884] t3302-notes-index-expensive: Speed up create_repo() Creating repos with 10/100/1000/10000 commits and notes takes a lot of time. However, using git-fast-import to do the job is a lot more efficient than using plumbing commands to do the same. This patch decreases the overall run-time of this test on my machine from ~3 to ~1 minutes. Signed-off-by: Johan Herland Acked-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/t3302-notes-index-expensive.sh | 74 ++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh index 0ef3e95959..ee84fc4884 100755 --- a/t/t3302-notes-index-expensive.sh +++ b/t/t3302-notes-index-expensive.sh @@ -16,30 +16,50 @@ test -z "$GIT_NOTES_TIMING_TESTS" && { create_repo () { number_of_commits=$1 nr=0 - parent= test -d .git || { git init && - tree=$(git write-tree) && - while [ $nr -lt $number_of_commits ]; do + ( + while [ $nr -lt $number_of_commits ]; do + nr=$(($nr+1)) + mark=$(($nr+$nr)) + notemark=$(($mark+1)) + test_tick && + cat < $GIT_COMMITTER_DATE +data <> note_commit + done && test_tick && - commit=$(echo $nr | git commit-tree $tree $parent) || - return - parent="-p $commit" - nr=$(($nr+1)) - done && - git update-ref refs/heads/master $commit && - { - GIT_INDEX_FILE=.git/temp; export GIT_INDEX_FILE; - git rev-list HEAD | cat -n | sed "s/^[ ][ ]*/ /g" | - while read nr sha1; do - blob=$(echo note $nr | git hash-object -w --stdin) && - echo $sha1 | sed "s/^/0644 $blob 0 /" - done | git update-index --index-info && - tree=$(git write-tree) && - test_tick && - commit=$(echo notes | git commit-tree $tree) && - git update-ref refs/notes/commits $commit - } && + cat < $GIT_COMMITTER_DATE +data < output && - i=1 && - while [ $i -le $count ]; do - echo " $(($count-$i))" && - echo " note $i" && - i=$(($i+1)); + i=$count && + while [ $i -gt 0 ]; do + echo " commit #$i" && + echo " note for commit #$i" && + i=$(($i-1)); done > expect && - git diff expect output + test_cmp expect output } cat > time_notes << \EOF From c56fcc89b951f3e8c9240ea02676b2eef5417da6 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:04 +0200 Subject: [PATCH 112/884] Add flags to get_commit_notes() to control the format of the note string This patch adds the following flags to get_commit_notes() for adjusting the format of the produced note string: - NOTES_SHOW_HEADER: Print "Notes:" line before the notes contents - NOTES_INDENT: Indent notes contents by 4 spaces Suggested-by: Johannes Schindelin Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 8 +++++--- notes.h | 5 ++++- pretty.c | 3 ++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/notes.c b/notes.c index 2b66723f5f..b7d79e1900 100644 --- a/notes.c +++ b/notes.c @@ -106,7 +106,7 @@ static unsigned char *lookup_notes(const unsigned char *commit_sha1) } void get_commit_notes(const struct commit *commit, struct strbuf *sb, - const char *output_encoding) + const char *output_encoding, int flags) { static const char utf8[] = "utf-8"; unsigned char *sha1; @@ -148,12 +148,14 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb, if (msglen && msg[msglen - 1] == '\n') msglen--; - strbuf_addstr(sb, "\nNotes:\n"); + if (flags & NOTES_SHOW_HEADER) + strbuf_addstr(sb, "\nNotes:\n"); for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) { linelen = strchrnul(msg_p, '\n') - msg_p; - strbuf_addstr(sb, " "); + if (flags & NOTES_INDENT) + strbuf_addstr(sb, " "); strbuf_add(sb, msg_p, linelen); strbuf_addch(sb, '\n'); } diff --git a/notes.h b/notes.h index 79d21b65f5..7f3eed4384 100644 --- a/notes.h +++ b/notes.h @@ -1,7 +1,10 @@ #ifndef NOTES_H #define NOTES_H +#define NOTES_SHOW_HEADER 1 +#define NOTES_INDENT 2 + void get_commit_notes(const struct commit *commit, struct strbuf *sb, - const char *output_encoding); + const char *output_encoding, int flags); #endif diff --git a/pretty.c b/pretty.c index e25db81eaa..01eadd0482 100644 --- a/pretty.c +++ b/pretty.c @@ -978,7 +978,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, strbuf_addch(sb, '\n'); if (fmt != CMIT_FMT_ONELINE) - get_commit_notes(commit, sb, encoding); + get_commit_notes(commit, sb, encoding, + NOTES_SHOW_HEADER | NOTES_INDENT); free(reencoded); } From 8b208f0213ed349ecb2ab8f7bb6c5072f8011a70 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Oct 2009 12:22:05 +0200 Subject: [PATCH 113/884] Add '%N'-format for pretty-printing commit notes Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/pretty-formats.txt | 1 + pretty.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 2a845b1e57..5fb10b3a15 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -123,6 +123,7 @@ The placeholders are: - '%s': subject - '%f': sanitized subject line, suitable for a filename - '%b': body +- '%N': commit notes - '%Cred': switch color to red - '%Cgreen': switch color to green - '%Cblue': switch color to blue diff --git a/pretty.c b/pretty.c index 01eadd0482..7f350bbdff 100644 --- a/pretty.c +++ b/pretty.c @@ -702,6 +702,10 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, case 'd': format_decoration(sb, commit); return 1; + case 'N': + get_commit_notes(commit, sb, git_log_output_encoding ? + git_log_output_encoding : git_commit_encoding, 0); + return 1; } /* For the rest we have to parse the commit header. */ From 27d57564102a98950bf4398daeeb14a15154478f Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:06 +0200 Subject: [PATCH 114/884] Teach notes code to free its internal data structures on request There's no need to be rude to memory-concious callers... This patch has been improved by the following contributions: - Junio C Hamano: avoid old-style declaration Signed-off-by: Junio C Hamano Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 7 +++++++ notes.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/notes.c b/notes.c index b7d79e1900..a5d888d772 100644 --- a/notes.c +++ b/notes.c @@ -105,6 +105,13 @@ static unsigned char *lookup_notes(const unsigned char *commit_sha1) return hash_map.entries[index].notes_sha1; } +void free_notes(void) +{ + free(hash_map.entries); + memset(&hash_map, 0, sizeof(struct hash_map)); + initialized = 0; +} + void get_commit_notes(const struct commit *commit, struct strbuf *sb, const char *output_encoding, int flags) { diff --git a/notes.h b/notes.h index 7f3eed4384..a1421e351a 100644 --- a/notes.h +++ b/notes.h @@ -1,6 +1,9 @@ #ifndef NOTES_H #define NOTES_H +/* Free (and de-initialize) the internal notes tree structure */ +void free_notes(void); + #define NOTES_SHOW_HEADER 1 #define NOTES_INDENT 2 From 23123aecf8418a6b0ec23378555ed78c438ae894 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:07 +0200 Subject: [PATCH 115/884] Teach the notes lookup code to parse notes trees with various fanout schemes The semantics used when parsing notes trees (with regards to fanout subtrees) follow Dscho's proposal fairly closely: - No concatenation/merging of notes is performed. If there are several notes objects referencing a given commit, only one of those objects are used. - If a notes object for a given commit is present in the "root" notes tree, no subtrees are consulted; the object in the root tree is used directly. - If there are more than one subtree that prefix-matches the given commit, only the subtree with the longest matching prefix is consulted. This means that if the given commit is e.g. "deadbeef", and the notes tree have subtrees "de" and "dead", then the following paths in the notes tree are searched: "deadbeef", "dead/beef". Note that "de/adbeef" is NOT searched. - Fanout directories (subtrees) must references a whole number of bytes from the SHA1 sum they subdivide. E.g. subtrees "dead" and "de" are acceptable; "d" and "dea" are not. - Multiple levels of fanout are allowed. All the above rules apply recursively. E.g. "de/adbeef" is preferred over "de/adbe/ef", etc. This patch changes the in-memory datastructure for holding parsed notes: Instead of holding all note (and subtree) entries in a hash table, a simple 16-tree structure is used instead. The tree structure consists of 16-arrays as internal nodes, and note/subtree entries as leaf nodes. The tree is traversed by indexing subsequent nibbles of the search key until a leaf node is encountered. If a subtree entry is encountered while searching for a note, the subtree is unpacked into the 16-tree structure, and the search continues into that subtree. The new algorithm performs significantly better in the cases where only a fraction of the notes need to be looked up (this is assumed to be the common case for notes lookup). The new code even performs marginally better in the worst case (where _all_ the notes are looked up). In addition to this, comes the massive performance win associated with organizing the notes tree according to some fanout scheme. Even a simple 2/38 fanout scheme is dramatically quicker to traverse (going from tens of seconds to sub-second runtimes). As for memory usage, the new code is marginally better than the old code in the worst case, but in the case of looking up only some notes from a notes tree with proper fanout, the new code uses only a small fraction of the memory needed to hold the entire notes tree. However, there is one casualty of this patch. The old notes lookup code was able to parse notes that were associated with non-SHA1s (e.g. refs). The new code requires the referenced object to be named by a SHA1 sum. Still, this is not considered a major setback, since the notes infrastructure was not originally intended to annotate objects outside the Git object database. Cc: Johannes Schindelin Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 313 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 246 insertions(+), 67 deletions(-) diff --git a/notes.c b/notes.c index a5d888d772..210c4b2263 100644 --- a/notes.c +++ b/notes.c @@ -6,109 +6,288 @@ #include "strbuf.h" #include "tree-walk.h" -struct entry { - unsigned char commit_sha1[20]; - unsigned char notes_sha1[20]; +/* + * Use a non-balancing simple 16-tree structure with struct int_node as + * internal nodes, and struct leaf_node as leaf nodes. Each int_node has a + * 16-array of pointers to its children. + * The bottom 2 bits of each pointer is used to identify the pointer type + * - ptr & 3 == 0 - NULL pointer, assert(ptr == NULL) + * - ptr & 3 == 1 - pointer to next internal node - cast to struct int_node * + * - ptr & 3 == 2 - pointer to note entry - cast to struct leaf_node * + * - ptr & 3 == 3 - pointer to subtree entry - cast to struct leaf_node * + * + * The root node is a statically allocated struct int_node. + */ +struct int_node { + void *a[16]; }; -struct hash_map { - struct entry *entries; - off_t count, size; +/* + * Leaf nodes come in two variants, note entries and subtree entries, + * distinguished by the LSb of the leaf node pointer (see above). + * As a note entry, the key is the SHA1 of the referenced commit, and the + * value is the SHA1 of the note object. + * As a subtree entry, the key is the prefix SHA1 (w/trailing NULs) of the + * referenced commit, using the last byte of the key to store the length of + * the prefix. The value is the SHA1 of the tree object containing the notes + * subtree. + */ +struct leaf_node { + unsigned char key_sha1[20]; + unsigned char val_sha1[20]; }; +#define PTR_TYPE_NULL 0 +#define PTR_TYPE_INTERNAL 1 +#define PTR_TYPE_NOTE 2 +#define PTR_TYPE_SUBTREE 3 + +#define GET_PTR_TYPE(ptr) ((uintptr_t) (ptr) & 3) +#define CLR_PTR_TYPE(ptr) ((void *) ((uintptr_t) (ptr) & ~3)) +#define SET_PTR_TYPE(ptr, type) ((void *) ((uintptr_t) (ptr) | (type))) + +#define GET_NIBBLE(n, sha1) (((sha1[n >> 1]) >> ((~n & 0x01) << 2)) & 0x0f) + +#define SUBTREE_SHA1_PREFIXCMP(key_sha1, subtree_sha1) \ + (memcmp(key_sha1, subtree_sha1, subtree_sha1[19])) + +static struct int_node root_node; + static int initialized; -static struct hash_map hash_map; -static int hash_index(struct hash_map *map, const unsigned char *sha1) +static void load_subtree(struct leaf_node *subtree, struct int_node *node, + unsigned int n); + +/* + * To find a leaf_node: + * 1. Start at the root node, with n = 0 + * 2. Use the nth nibble of the key as an index into a: + * - If a[n] is an int_node, recurse into that node and increment n + * - If a leaf_node with matching key, return leaf_node (assert note entry) + * - If a matching subtree entry, unpack that subtree entry (and remove it); + * restart search at the current level. + * - Otherwise, we end up at a NULL pointer, or a non-matching leaf_node. + * Backtrack out of the recursion, one level at a time and check a[0]: + * - If a[0] at the current level is a matching subtree entry, unpack that + * subtree entry (and remove it); restart search at the current level. + */ +static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n, + const unsigned char *key_sha1) { - int i = ((*(unsigned int *)sha1) % map->size); + struct leaf_node *l; + unsigned char i = GET_NIBBLE(n, key_sha1); + void *p = tree->a[i]; - for (;;) { - unsigned char *current = map->entries[i].commit_sha1; + switch(GET_PTR_TYPE(p)) { + case PTR_TYPE_INTERNAL: + l = note_tree_find(CLR_PTR_TYPE(p), n + 1, key_sha1); + if (l) + return l; + break; + case PTR_TYPE_NOTE: + l = (struct leaf_node *) CLR_PTR_TYPE(p); + if (!hashcmp(key_sha1, l->key_sha1)) + return l; /* return note object matching given key */ + break; + case PTR_TYPE_SUBTREE: + l = (struct leaf_node *) CLR_PTR_TYPE(p); + if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { + /* unpack tree and resume search */ + tree->a[i] = NULL; + load_subtree(l, tree, n); + free(l); + return note_tree_find(tree, n, key_sha1); + } + break; + case PTR_TYPE_NULL: + default: + assert(!p); + break; + } - if (!hashcmp(sha1, current)) - return i; + /* + * Did not find key at this (or any lower) level. + * Check if there's a matching subtree entry in tree->a[0]. + * If so, unpack tree and resume search. + */ + p = tree->a[0]; + if (GET_PTR_TYPE(p) != PTR_TYPE_SUBTREE) + return NULL; + l = (struct leaf_node *) CLR_PTR_TYPE(p); + if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { + /* unpack tree and resume search */ + tree->a[0] = NULL; + load_subtree(l, tree, n); + free(l); + return note_tree_find(tree, n, key_sha1); + } + return NULL; +} - if (is_null_sha1(current)) - return -1 - i; - - if (++i == map->size) - i = 0; +/* + * To insert a leaf_node: + * 1. Start at the root node, with n = 0 + * 2. Use the nth nibble of the key as an index into a: + * - If a[n] is NULL, store the tweaked pointer directly into a[n] + * - If a[n] is an int_node, recurse into that node and increment n + * - If a[n] is a leaf_node: + * 1. Check if they're equal, and handle that (abort? overwrite?) + * 2. Create a new int_node, and store both leaf_nodes there + * 3. Store the new int_node into a[n]. + */ +static int note_tree_insert(struct int_node *tree, unsigned char n, + const struct leaf_node *entry, unsigned char type) +{ + struct int_node *new_node; + const struct leaf_node *l; + int ret; + unsigned char i = GET_NIBBLE(n, entry->key_sha1); + void *p = tree->a[i]; + assert(GET_PTR_TYPE(entry) == PTR_TYPE_NULL); + switch(GET_PTR_TYPE(p)) { + case PTR_TYPE_NULL: + assert(!p); + tree->a[i] = SET_PTR_TYPE(entry, type); + return 0; + case PTR_TYPE_INTERNAL: + return note_tree_insert(CLR_PTR_TYPE(p), n + 1, entry, type); + default: + assert(GET_PTR_TYPE(p) == PTR_TYPE_NOTE || + GET_PTR_TYPE(p) == PTR_TYPE_SUBTREE); + l = (const struct leaf_node *) CLR_PTR_TYPE(p); + if (!hashcmp(entry->key_sha1, l->key_sha1)) + return -1; /* abort insert on matching key */ + new_node = (struct int_node *) + xcalloc(sizeof(struct int_node), 1); + ret = note_tree_insert(new_node, n + 1, + CLR_PTR_TYPE(p), GET_PTR_TYPE(p)); + if (ret) { + free(new_node); + return -1; + } + tree->a[i] = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); + return note_tree_insert(new_node, n + 1, entry, type); } } -static void add_entry(const unsigned char *commit_sha1, - const unsigned char *notes_sha1) +/* Free the entire notes data contained in the given tree */ +static void note_tree_free(struct int_node *tree) { - int index; + unsigned int i; + for (i = 0; i < 16; i++) { + void *p = tree->a[i]; + switch(GET_PTR_TYPE(p)) { + case PTR_TYPE_INTERNAL: + note_tree_free(CLR_PTR_TYPE(p)); + /* fall through */ + case PTR_TYPE_NOTE: + case PTR_TYPE_SUBTREE: + free(CLR_PTR_TYPE(p)); + } + } +} - if (hash_map.count + 1 > hash_map.size >> 1) { - int i, old_size = hash_map.size; - struct entry *old = hash_map.entries; +/* + * Convert a partial SHA1 hex string to the corresponding partial SHA1 value. + * - hex - Partial SHA1 segment in ASCII hex format + * - hex_len - Length of above segment. Must be multiple of 2 between 0 and 40 + * - sha1 - Partial SHA1 value is written here + * - sha1_len - Max #bytes to store in sha1, Must be >= hex_len / 2, and < 20 + * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format). + * Otherwise, returns number of bytes written to sha1 (i.e. hex_len / 2). + * Pads sha1 with NULs up to sha1_len (not included in returned length). + */ +static int get_sha1_hex_segment(const char *hex, unsigned int hex_len, + unsigned char *sha1, unsigned int sha1_len) +{ + unsigned int i, len = hex_len >> 1; + if (hex_len % 2 != 0 || len > sha1_len) + return -1; + for (i = 0; i < len; i++) { + unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]); + if (val & ~0xff) + return -1; + *sha1++ = val; + hex += 2; + } + for (; i < sha1_len; i++) + *sha1++ = 0; + return len; +} - hash_map.size = old_size ? old_size << 1 : 64; - hash_map.entries = (struct entry *) - xcalloc(sizeof(struct entry), hash_map.size); +static void load_subtree(struct leaf_node *subtree, struct int_node *node, + unsigned int n) +{ + unsigned char commit_sha1[20]; + unsigned int prefix_len; + int status; + void *buf; + struct tree_desc desc; + struct name_entry entry; - for (i = 0; i < old_size; i++) - if (!is_null_sha1(old[i].commit_sha1)) { - index = -1 - hash_index(&hash_map, - old[i].commit_sha1); - memcpy(hash_map.entries + index, old + i, - sizeof(struct entry)); + buf = fill_tree_descriptor(&desc, subtree->val_sha1); + if (!buf) + die("Could not read %s for notes-index", + sha1_to_hex(subtree->val_sha1)); + + prefix_len = subtree->key_sha1[19]; + assert(prefix_len * 2 >= n); + memcpy(commit_sha1, subtree->key_sha1, prefix_len); + while (tree_entry(&desc, &entry)) { + int len = get_sha1_hex_segment(entry.path, strlen(entry.path), + commit_sha1 + prefix_len, 20 - prefix_len); + if (len < 0) + continue; /* entry.path is not a SHA1 sum. Skip */ + len += prefix_len; + + /* + * If commit SHA1 is complete (len == 20), assume note object + * If commit SHA1 is incomplete (len < 20), assume note subtree + */ + if (len <= 20) { + unsigned char type = PTR_TYPE_NOTE; + struct leaf_node *l = (struct leaf_node *) + xcalloc(sizeof(struct leaf_node), 1); + hashcpy(l->key_sha1, commit_sha1); + hashcpy(l->val_sha1, entry.sha1); + if (len < 20) { + l->key_sha1[19] = (unsigned char) len; + type = PTR_TYPE_SUBTREE; } - free(old); + status = note_tree_insert(node, n, l, type); + assert(!status); + } } - - index = hash_index(&hash_map, commit_sha1); - if (index < 0) { - index = -1 - index; - hash_map.count++; - } - - hashcpy(hash_map.entries[index].commit_sha1, commit_sha1); - hashcpy(hash_map.entries[index].notes_sha1, notes_sha1); + free(buf); } -static void initialize_hash_map(const char *notes_ref_name) +static void initialize_notes(const char *notes_ref_name) { unsigned char sha1[20], commit_sha1[20]; unsigned mode; - struct tree_desc desc; - struct name_entry entry; - void *buf; + struct leaf_node root_tree; if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) || get_tree_entry(commit_sha1, "", sha1, &mode)) return; - buf = fill_tree_descriptor(&desc, sha1); - if (!buf) - die("Could not read %s for notes-index", sha1_to_hex(sha1)); - - while (tree_entry(&desc, &entry)) - if (!get_sha1(entry.path, commit_sha1)) - add_entry(commit_sha1, entry.sha1); - free(buf); + hashclr(root_tree.key_sha1); + hashcpy(root_tree.val_sha1, sha1); + load_subtree(&root_tree, &root_node, 0); } static unsigned char *lookup_notes(const unsigned char *commit_sha1) { - int index; - - if (!hash_map.size) - return NULL; - - index = hash_index(&hash_map, commit_sha1); - if (index < 0) - return NULL; - return hash_map.entries[index].notes_sha1; + struct leaf_node *found = note_tree_find(&root_node, 0, commit_sha1); + if (found) + return found->val_sha1; + return NULL; } void free_notes(void) { - free(hash_map.entries); - memset(&hash_map, 0, sizeof(struct hash_map)); + note_tree_free(&root_node); + memset(&root_node, 0, sizeof(struct int_node)); initialized = 0; } @@ -127,7 +306,7 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb, notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT); else if (!notes_ref_name) notes_ref_name = GIT_NOTES_DEFAULT_REF; - initialize_hash_map(notes_ref_name); + initialize_notes(notes_ref_name); initialized = 1; } From 0057c0917d35f9f21e01f2122e7f2b9f169a8b02 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:08 +0200 Subject: [PATCH 116/884] Add selftests verifying that we can parse notes trees with various fanouts Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- t/t3303-notes-subtrees.sh | 104 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100755 t/t3303-notes-subtrees.sh diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh new file mode 100755 index 0000000000..cbb9d35909 --- /dev/null +++ b/t/t3303-notes-subtrees.sh @@ -0,0 +1,104 @@ +#!/bin/sh + +test_description='Test commit notes organized in subtrees' + +. ./test-lib.sh + +number_of_commits=100 + +start_note_commit () { + test_tick && + cat < $GIT_COMMITTER_DATE +data < output && + i=$number_of_commits && + while [ $i -gt 0 ]; do + echo " commit #$i" && + echo " note for commit #$i" && + i=$(($i-1)); + done > expect && + test_cmp expect output +} + +test_expect_success "setup: create $number_of_commits commits" ' + + ( + nr=0 && + while [ $nr -lt $number_of_commits ]; do + nr=$(($nr+1)) && + test_tick && + cat < $GIT_COMMITTER_DATE +data < $GIT_COMMITTER_DATE +data < Date: Fri, 9 Oct 2009 12:22:09 +0200 Subject: [PATCH 117/884] Refactor notes code to concatenate multiple notes annotating the same object Currently, having multiple notes referring to the same commit from various locations in the notes tree is strongly discouraged, since only one of those notes will be parsed and shown. This patch teaches the notes code to _concatenate_ multiple notes that annotate the same commit. Notes are concatenated by creating a new blob object containing the concatenation of the notes in question, and replacing them with the concatenated note in the internal notes tree structure. Getting the concatenation right requires being more proactive in unpacking subtree entries in the internal notes tree structure, so that we don't return a note prematurely (i.e. before having found all other notes that annotate the same object). As such, this patch may incur a small performance penalty. Suggested-by: Sam Vilain Re-suggested-by: Johannes Schindelin Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 243 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 161 insertions(+), 82 deletions(-) diff --git a/notes.c b/notes.c index 210c4b2263..50a4672d7c 100644 --- a/notes.c +++ b/notes.c @@ -59,115 +59,196 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, unsigned int n); /* - * To find a leaf_node: + * Search the tree until the appropriate location for the given key is found: * 1. Start at the root node, with n = 0 - * 2. Use the nth nibble of the key as an index into a: - * - If a[n] is an int_node, recurse into that node and increment n - * - If a leaf_node with matching key, return leaf_node (assert note entry) + * 2. If a[0] at the current level is a matching subtree entry, unpack that + * subtree entry and remove it; restart search at the current level. + * 3. Use the nth nibble of the key as an index into a: + * - If a[n] is an int_node, recurse from #2 into that node and increment n * - If a matching subtree entry, unpack that subtree entry (and remove it); * restart search at the current level. - * - Otherwise, we end up at a NULL pointer, or a non-matching leaf_node. - * Backtrack out of the recursion, one level at a time and check a[0]: - * - If a[0] at the current level is a matching subtree entry, unpack that - * subtree entry (and remove it); restart search at the current level. + * - Otherwise, we have found one of the following: + * - a subtree entry which does not match the key + * - a note entry which may or may not match the key + * - an unused leaf node (NULL) + * In any case, set *tree and *n, and return pointer to the tree location. */ -static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n, - const unsigned char *key_sha1) +static void **note_tree_search(struct int_node **tree, + unsigned char *n, const unsigned char *key_sha1) { struct leaf_node *l; - unsigned char i = GET_NIBBLE(n, key_sha1); - void *p = tree->a[i]; + unsigned char i; + void *p = (*tree)->a[0]; + if (GET_PTR_TYPE(p) == PTR_TYPE_SUBTREE) { + l = (struct leaf_node *) CLR_PTR_TYPE(p); + if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { + /* unpack tree and resume search */ + (*tree)->a[0] = NULL; + load_subtree(l, *tree, *n); + free(l); + return note_tree_search(tree, n, key_sha1); + } + } + + i = GET_NIBBLE(*n, key_sha1); + p = (*tree)->a[i]; switch(GET_PTR_TYPE(p)) { case PTR_TYPE_INTERNAL: - l = note_tree_find(CLR_PTR_TYPE(p), n + 1, key_sha1); - if (l) - return l; - break; - case PTR_TYPE_NOTE: - l = (struct leaf_node *) CLR_PTR_TYPE(p); - if (!hashcmp(key_sha1, l->key_sha1)) - return l; /* return note object matching given key */ - break; + *tree = CLR_PTR_TYPE(p); + (*n)++; + return note_tree_search(tree, n, key_sha1); case PTR_TYPE_SUBTREE: l = (struct leaf_node *) CLR_PTR_TYPE(p); if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { /* unpack tree and resume search */ - tree->a[i] = NULL; - load_subtree(l, tree, n); + (*tree)->a[i] = NULL; + load_subtree(l, *tree, *n); free(l); - return note_tree_find(tree, n, key_sha1); + return note_tree_search(tree, n, key_sha1); } - break; - case PTR_TYPE_NULL: + /* fall through */ default: - assert(!p); - break; + return &((*tree)->a[i]); } +} - /* - * Did not find key at this (or any lower) level. - * Check if there's a matching subtree entry in tree->a[0]. - * If so, unpack tree and resume search. - */ - p = tree->a[0]; - if (GET_PTR_TYPE(p) != PTR_TYPE_SUBTREE) - return NULL; - l = (struct leaf_node *) CLR_PTR_TYPE(p); - if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { - /* unpack tree and resume search */ - tree->a[0] = NULL; - load_subtree(l, tree, n); - free(l); - return note_tree_find(tree, n, key_sha1); +/* + * To find a leaf_node: + * Search to the tree location appropriate for the given key: + * If a note entry with matching key, return the note entry, else return NULL. + */ +static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n, + const unsigned char *key_sha1) +{ + void **p = note_tree_search(&tree, &n, key_sha1); + if (GET_PTR_TYPE(*p) == PTR_TYPE_NOTE) { + struct leaf_node *l = (struct leaf_node *) CLR_PTR_TYPE(*p); + if (!hashcmp(key_sha1, l->key_sha1)) + return l; } return NULL; } +/* Create a new blob object by concatenating the two given blob objects */ +static int concatenate_notes(unsigned char *cur_sha1, + const unsigned char *new_sha1) +{ + char *cur_msg, *new_msg, *buf; + unsigned long cur_len, new_len, buf_len; + enum object_type cur_type, new_type; + int ret; + + /* read in both note blob objects */ + new_msg = read_sha1_file(new_sha1, &new_type, &new_len); + if (!new_msg || !new_len || new_type != OBJ_BLOB) { + free(new_msg); + return 0; + } + cur_msg = read_sha1_file(cur_sha1, &cur_type, &cur_len); + if (!cur_msg || !cur_len || cur_type != OBJ_BLOB) { + free(cur_msg); + free(new_msg); + hashcpy(cur_sha1, new_sha1); + return 0; + } + + /* we will separate the notes by a newline anyway */ + if (cur_msg[cur_len - 1] == '\n') + cur_len--; + + /* concatenate cur_msg and new_msg into buf */ + buf_len = cur_len + 1 + new_len; + buf = (char *) xmalloc(buf_len); + memcpy(buf, cur_msg, cur_len); + buf[cur_len] = '\n'; + memcpy(buf + cur_len + 1, new_msg, new_len); + + free(cur_msg); + free(new_msg); + + /* create a new blob object from buf */ + ret = write_sha1_file(buf, buf_len, "blob", cur_sha1); + free(buf); + return ret; +} + /* * To insert a leaf_node: - * 1. Start at the root node, with n = 0 - * 2. Use the nth nibble of the key as an index into a: - * - If a[n] is NULL, store the tweaked pointer directly into a[n] - * - If a[n] is an int_node, recurse into that node and increment n - * - If a[n] is a leaf_node: - * 1. Check if they're equal, and handle that (abort? overwrite?) - * 2. Create a new int_node, and store both leaf_nodes there - * 3. Store the new int_node into a[n]. + * Search to the tree location appropriate for the given leaf_node's key: + * - If location is unused (NULL), store the tweaked pointer directly there + * - If location holds a note entry that matches the note-to-be-inserted, then + * concatenate the two notes. + * - If location holds a note entry that matches the subtree-to-be-inserted, + * then unpack the subtree-to-be-inserted into the location. + * - If location holds a matching subtree entry, unpack the subtree at that + * location, and restart the insert operation from that level. + * - Else, create a new int_node, holding both the node-at-location and the + * node-to-be-inserted, and store the new int_node into the location. */ -static int note_tree_insert(struct int_node *tree, unsigned char n, - const struct leaf_node *entry, unsigned char type) +static void note_tree_insert(struct int_node *tree, unsigned char n, + struct leaf_node *entry, unsigned char type) { struct int_node *new_node; - const struct leaf_node *l; - int ret; - unsigned char i = GET_NIBBLE(n, entry->key_sha1); - void *p = tree->a[i]; - assert(GET_PTR_TYPE(entry) == PTR_TYPE_NULL); - switch(GET_PTR_TYPE(p)) { + struct leaf_node *l; + void **p = note_tree_search(&tree, &n, entry->key_sha1); + + assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ + l = (struct leaf_node *) CLR_PTR_TYPE(*p); + switch(GET_PTR_TYPE(*p)) { case PTR_TYPE_NULL: - assert(!p); - tree->a[i] = SET_PTR_TYPE(entry, type); - return 0; - case PTR_TYPE_INTERNAL: - return note_tree_insert(CLR_PTR_TYPE(p), n + 1, entry, type); - default: - assert(GET_PTR_TYPE(p) == PTR_TYPE_NOTE || - GET_PTR_TYPE(p) == PTR_TYPE_SUBTREE); - l = (const struct leaf_node *) CLR_PTR_TYPE(p); - if (!hashcmp(entry->key_sha1, l->key_sha1)) - return -1; /* abort insert on matching key */ - new_node = (struct int_node *) - xcalloc(sizeof(struct int_node), 1); - ret = note_tree_insert(new_node, n + 1, - CLR_PTR_TYPE(p), GET_PTR_TYPE(p)); - if (ret) { - free(new_node); - return -1; + assert(!*p); + *p = SET_PTR_TYPE(entry, type); + return; + case PTR_TYPE_NOTE: + switch (type) { + case PTR_TYPE_NOTE: + if (!hashcmp(l->key_sha1, entry->key_sha1)) { + /* skip concatenation if l == entry */ + if (!hashcmp(l->val_sha1, entry->val_sha1)) + return; + + if (concatenate_notes(l->val_sha1, + entry->val_sha1)) + die("failed to concatenate note %s " + "into note %s for commit %s", + sha1_to_hex(entry->val_sha1), + sha1_to_hex(l->val_sha1), + sha1_to_hex(l->key_sha1)); + free(entry); + return; + } + break; + case PTR_TYPE_SUBTREE: + if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1, + entry->key_sha1)) { + /* unpack 'entry' */ + load_subtree(entry, tree, n); + free(entry); + return; + } + break; } - tree->a[i] = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); - return note_tree_insert(new_node, n + 1, entry, type); + break; + case PTR_TYPE_SUBTREE: + if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) { + /* unpack 'l' and restart insert */ + *p = NULL; + load_subtree(l, tree, n); + free(l); + note_tree_insert(tree, n, entry, type); + return; + } + break; } + + /* non-matching leaf_node */ + assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE || + GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE); + new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1); + note_tree_insert(new_node, n + 1, l, GET_PTR_TYPE(*p)); + *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); + note_tree_insert(new_node, n + 1, entry, type); } /* Free the entire notes data contained in the given tree */ @@ -220,7 +301,6 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, { unsigned char commit_sha1[20]; unsigned int prefix_len; - int status; void *buf; struct tree_desc desc; struct name_entry entry; @@ -254,8 +334,7 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, l->key_sha1[19] = (unsigned char) len; type = PTR_TYPE_SUBTREE; } - status = note_tree_insert(node, n, l, type); - assert(!status); + note_tree_insert(node, n, l, type); } } free(buf); From a099469bbcf273e76d81573229971956b4ef0700 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:10 +0200 Subject: [PATCH 118/884] Add selftests verifying concatenation of multiple notes for the same commit Also verify that multiple references to the _same_ note blob are _not_ concatenated. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- t/t3303-notes-subtrees.sh | 84 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh index cbb9d35909..edc4bc8841 100755 --- a/t/t3303-notes-subtrees.sh +++ b/t/t3303-notes-subtrees.sh @@ -101,4 +101,88 @@ test_expect_success 'verify notes in 4/36-fanout' 'verify_notes' test_expect_success 'test notes in 2/2/36-fanout' 'test_sha1_based "s|^\(..\)\(..\)|\1/\2/|"' test_expect_success 'verify notes in 2/2/36-fanout' 'verify_notes' +test_same_notes () { + ( + start_note_commit && + nr=$number_of_commits && + git rev-list refs/heads/master | + while read sha1; do + first_note_path=$(echo "$sha1" | sed "$1") + second_note_path=$(echo "$sha1" | sed "$2") + cat < output && + i=$number_of_commits && + while [ $i -gt 0 ]; do + echo " commit #$i" && + echo " first note for commit #$i" && + echo " second note for commit #$i" && + i=$(($i-1)); + done > expect && + test_cmp expect output +} + +test_expect_success 'test notes in 4/36-fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" "s|^....|&/|"' +test_expect_success 'verify notes in 4/36-fanout concatenated with 2/38-fanout' 'verify_concatenated_notes' + +test_expect_success 'test notes in 2/38-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^..|&/|"' +test_expect_success 'verify notes in 2/38-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes' + +test_expect_success 'test notes in 4/36-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^....|&/|"' +test_expect_success 'verify notes in 4/36-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes' + test_done From 3a7cba95b74dd2460f8b9bf4f7cefa3c21d31fdd Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 19 Oct 2009 17:42:02 +0200 Subject: [PATCH 119/884] imap-send: remove useless uid code The imap-send code is based on code from isync, a program for syncing imap mailboxes. Because of this, it has inherited some code that makes sense for isync, but not for imap-send. In particular, when storing a message, it does one of: - if the server supports it, note the server-assigned unique identifier (UID) given to each message - otherwise, assigned a random UID and store it in the message header as X-TUID Presumably this is used in isync to be able to synchronize mailstores multiple times without duplication. But for imap-send, the values are useless; we never do anything with them and simply forget them at the end of the program. This patch removes the useless code. Not only is it nice for maintainability to get rid of dead code, but the removed code relied on the existence of /dev/urandom, which made it a portability problem for non-Unix platforms. Signed-off-by: Jeff King Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano --- imap-send.c | 155 ++++------------------------------------------------ 1 file changed, 11 insertions(+), 144 deletions(-) diff --git a/imap-send.c b/imap-send.c index 3847fd151d..8da7a94931 100644 --- a/imap-send.c +++ b/imap-send.c @@ -123,9 +123,6 @@ static int nfvasprintf(char **strp, const char *fmt, va_list ap) return len; } -static void arc4_init(void); -static unsigned char arc4_getbyte(void); - struct imap_server_conf { char *name; char *tunnel; @@ -489,52 +486,6 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...) return ret; } -static struct { - unsigned char i, j, s[256]; -} rs; - -static void arc4_init(void) -{ - int i, fd; - unsigned char j, si, dat[128]; - - if ((fd = open("/dev/urandom", O_RDONLY)) < 0 && (fd = open("/dev/random", O_RDONLY)) < 0) { - fprintf(stderr, "Fatal: no random number source available.\n"); - exit(3); - } - if (read_in_full(fd, dat, 128) != 128) { - fprintf(stderr, "Fatal: cannot read random number source.\n"); - exit(3); - } - close(fd); - - for (i = 0; i < 256; i++) - rs.s[i] = i; - for (i = j = 0; i < 256; i++) { - si = rs.s[i]; - j += si + dat[i & 127]; - rs.s[i] = rs.s[j]; - rs.s[j] = si; - } - rs.i = rs.j = 0; - - for (i = 0; i < 256; i++) - arc4_getbyte(); -} - -static unsigned char arc4_getbyte(void) -{ - unsigned char si, sj; - - rs.i++; - si = rs.s[rs.i]; - rs.j += si; - sj = rs.s[rs.j]; - rs.s[rs.i] = sj; - rs.s[rs.j] = si; - return rs.s[(si + sj) & 0xff]; -} - static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx, struct imap_cmd_cb *cb, const char *fmt, va_list ap) @@ -1198,88 +1149,20 @@ static int imap_make_flags(int flags, char *buf) return d; } -#define TUIDL 8 - -static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid) +static int imap_store_msg(struct store *gctx, struct msg_data *data) { struct imap_store *ctx = (struct imap_store *)gctx; struct imap *imap = ctx->imap; struct imap_cmd_cb cb; - char *fmap, *buf; const char *prefix, *box; - int ret, i, j, d, len, extra, nocr; - int start, sbreak = 0, ebreak = 0; - char flagstr[128], tuid[TUIDL * 2 + 1]; + int ret, d; + char flagstr[128]; memset(&cb, 0, sizeof(cb)); - fmap = data->data; - len = data->len; - nocr = !data->crlf; - extra = 0, i = 0; - if (!CAP(UIDPLUS) && uid) { - nloop: - start = i; - while (i < len) - if (fmap[i++] == '\n') { - extra += nocr; - if (i - 2 + nocr == start) { - sbreak = ebreak = i - 2 + nocr; - goto mktid; - } - if (!memcmp(fmap + start, "X-TUID: ", 8)) { - extra -= (ebreak = i) - (sbreak = start) + nocr; - goto mktid; - } - goto nloop; - } - /* invalid message */ - free(fmap); - return DRV_MSG_BAD; - mktid: - for (j = 0; j < TUIDL; j++) - sprintf(tuid + j * 2, "%02x", arc4_getbyte()); - extra += 8 + TUIDL * 2 + 2; - } - if (nocr) - for (; i < len; i++) - if (fmap[i] == '\n') - extra++; - - cb.dlen = len + extra; - buf = cb.data = xmalloc(cb.dlen); - i = 0; - if (!CAP(UIDPLUS) && uid) { - if (nocr) { - for (; i < sbreak; i++) - if (fmap[i] == '\n') { - *buf++ = '\r'; - *buf++ = '\n'; - } else - *buf++ = fmap[i]; - } else { - memcpy(buf, fmap, sbreak); - buf += sbreak; - } - memcpy(buf, "X-TUID: ", 8); - buf += 8; - memcpy(buf, tuid, TUIDL * 2); - buf += TUIDL * 2; - *buf++ = '\r'; - *buf++ = '\n'; - i = ebreak; - } - if (nocr) { - for (; i < len; i++) - if (fmap[i] == '\n') { - *buf++ = '\r'; - *buf++ = '\n'; - } else - *buf++ = fmap[i]; - } else - memcpy(buf, fmap + i, len - i); - - free(fmap); + cb.dlen = data->len; + cb.data = xmalloc(cb.dlen); + memcpy(cb.data, data->data, data->len); d = 0; if (data->flags) { @@ -1288,26 +1171,14 @@ static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid) } flagstr[d] = 0; - if (!uid) { - box = gctx->conf->trash; - prefix = ctx->prefix; - cb.create = 1; - if (ctx->trashnc) - imap->caps = imap->rcaps & ~(1 << LITERALPLUS); - } else { - box = gctx->name; - prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix; - cb.create = 0; - } - cb.ctx = uid; + box = gctx->name; + prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix; + cb.create = 0; ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr); imap->caps = imap->rcaps; if (ret != DRV_OK) return ret; - if (!uid) - ctx->trashnc = 0; - else - gctx->count++; + gctx->count++; return DRV_OK; } @@ -1483,7 +1354,6 @@ int main(int argc, char **argv) { struct msg_data all_msgs, msg; struct store *ctx = NULL; - int uid = 0; int ofs = 0; int r; int total, n = 0; @@ -1491,9 +1361,6 @@ int main(int argc, char **argv) git_extract_argv0_path(argv[0]); - /* init the random number generator */ - arc4_init(); - setup_git_directory_gently(&nongit_ok); git_config(git_imap_config, NULL); @@ -1540,7 +1407,7 @@ int main(int argc, char **argv) break; if (server.use_html) wrap_in_html(&msg); - r = imap_store_msg(ctx, &msg, &uid); + r = imap_store_msg(ctx, &msg); if (r != DRV_OK) break; n++; From 7a7796e9a7cd8f789fa964acbb1eec5efd05436a Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 19 Oct 2009 17:42:03 +0200 Subject: [PATCH 120/884] imap-send: use separate read and write fds This is a patch that enables us to use the run-command API, which is supported on Windows. Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano --- imap-send.c | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/imap-send.c b/imap-send.c index 8da7a94931..7216453ceb 100644 --- a/imap-send.c +++ b/imap-send.c @@ -151,7 +151,7 @@ struct imap_list { }; struct imap_socket { - int fd; + int fd[2]; SSL *ssl; }; @@ -301,8 +301,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve ssl_socket_perror("SSL_new"); return -1; } - if (!SSL_set_fd(sock->ssl, sock->fd)) { - ssl_socket_perror("SSL_set_fd"); + if (!SSL_set_rfd(sock->ssl, sock->fd[0])) { + ssl_socket_perror("SSL_set_rfd"); + return -1; + } + if (!SSL_set_wfd(sock->ssl, sock->fd[1])) { + ssl_socket_perror("SSL_set_wfd"); return -1; } @@ -324,11 +328,12 @@ static int socket_read(struct imap_socket *sock, char *buf, int len) n = SSL_read(sock->ssl, buf, len); else #endif - n = xread(sock->fd, buf, len); + n = xread(sock->fd[0], buf, len); if (n <= 0) { socket_perror("read", sock, n); - close(sock->fd); - sock->fd = -1; + close(sock->fd[0]); + close(sock->fd[1]); + sock->fd[0] = sock->fd[1] = -1; } return n; } @@ -341,11 +346,12 @@ static int socket_write(struct imap_socket *sock, const char *buf, int len) n = SSL_write(sock->ssl, buf, len); else #endif - n = write_in_full(sock->fd, buf, len); + n = write_in_full(sock->fd[1], buf, len); if (n != len) { socket_perror("write", sock, n); - close(sock->fd); - sock->fd = -1; + close(sock->fd[0]); + close(sock->fd[1]); + sock->fd[0] = sock->fd[1] = -1; } return n; } @@ -358,7 +364,8 @@ static void socket_shutdown(struct imap_socket *sock) SSL_free(sock->ssl); } #endif - close(sock->fd); + close(sock->fd[0]); + close(sock->fd[1]); } /* simple line buffering */ @@ -911,7 +918,7 @@ static void imap_close_server(struct imap_store *ictx) { struct imap *imap = ictx->imap; - if (imap->buf.sock.fd != -1) { + if (imap->buf.sock.fd[0] != -1) { imap_exec(ictx, NULL, "LOGOUT"); socket_shutdown(&imap->buf.sock); } @@ -939,7 +946,7 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) ctx = xcalloc(sizeof(*ctx), 1); ctx->imap = imap = xcalloc(sizeof(*imap), 1); - imap->buf.sock.fd = -1; + imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1; imap->in_progress_append = &imap->in_progress; /* open connection to IMAP server */ @@ -966,7 +973,8 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) close(a[0]); - imap->buf.sock.fd = a[1]; + imap->buf.sock.fd[0] = a[1]; + imap->buf.sock.fd[1] = dup(a[1]); imap_info("ok\n"); } else { @@ -1043,7 +1051,8 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) goto bail; } - imap->buf.sock.fd = s; + imap->buf.sock.fd[0] = s; + imap->buf.sock.fd[1] = dup(s); if (srvc->use_ssl && ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) { From c94d2dd0807328b1ee4aa8353382caa45bc24055 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 19 Oct 2009 17:42:04 +0200 Subject: [PATCH 121/884] imap-send: use run-command API for tunneling Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano --- imap-send.c | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/imap-send.c b/imap-send.c index 7216453ceb..72ed640125 100644 --- a/imap-send.c +++ b/imap-send.c @@ -24,6 +24,7 @@ #include "cache.h" #include "exec_cmd.h" +#include "run-command.h" #ifdef NO_OPENSSL typedef void *SSL; #endif @@ -940,8 +941,7 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) struct imap_store *ctx; struct imap *imap; char *arg, *rsp; - int s = -1, a[2], preauth; - pid_t pid; + int s = -1, preauth; ctx = xcalloc(sizeof(*ctx), 1); @@ -952,29 +952,24 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) /* open connection to IMAP server */ if (srvc->tunnel) { + const char *argv[4]; + struct child_process tunnel = {0}; + imap_info("Starting tunnel '%s'... ", srvc->tunnel); - if (socketpair(PF_UNIX, SOCK_STREAM, 0, a)) { - perror("socketpair"); - exit(1); - } + argv[0] = "sh"; + argv[1] = "-c"; + argv[2] = srvc->tunnel; + argv[3] = NULL; - pid = fork(); - if (pid < 0) - _exit(127); - if (!pid) { - if (dup2(a[0], 0) == -1 || dup2(a[0], 1) == -1) - _exit(127); - close(a[0]); - close(a[1]); - execl("/bin/sh", "sh", "-c", srvc->tunnel, NULL); - _exit(127); - } + tunnel.argv = argv; + tunnel.in = -1; + tunnel.out = -1; + if (start_command(&tunnel)) + die("cannot start proxy %s", argv[0]); - close(a[0]); - - imap->buf.sock.fd[0] = a[1]; - imap->buf.sock.fd[1] = dup(a[1]); + imap->buf.sock.fd[0] = tunnel.out; + imap->buf.sock.fd[1] = tunnel.in; imap_info("ok\n"); } else { From d23b1ecf11ee48cd9e266ada06f1b8298b917e92 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 19 Oct 2009 17:42:05 +0200 Subject: [PATCH 122/884] imap-send: fix compilation-error on Windows mmsystem.h (included from windows.h) defines DRV_OK to 1. To avoid an error due to DRV_OK redefenition, this patch undefines the old definition (i.e the one from mmsystem.h) before defining DRV_OK. Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano --- imap-send.c | 1 + 1 file changed, 1 insertion(+) diff --git a/imap-send.c b/imap-send.c index 72ed640125..69e6142456 100644 --- a/imap-send.c +++ b/imap-send.c @@ -94,6 +94,7 @@ struct msg_data { unsigned int crlf:1; }; +#undef DRV_OK #define DRV_OK 0 #define DRV_MSG_BAD -1 #define DRV_BOX_BAD -2 From f9a88b70f94917f95e247a0e6e4d37f163fb41e3 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 19 Oct 2009 17:42:06 +0200 Subject: [PATCH 123/884] imap-send: build imap-send on Windows Since the POSIX-specific tunneling code has been replaced by the run-command API (and a compile-error has been cleaned away), we can now enable imap-send on Windows builds. Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 12defd4c97..13980a52b5 100644 --- a/Makefile +++ b/Makefile @@ -350,6 +350,7 @@ EXTRA_PROGRAMS = PROGRAMS += $(EXTRA_PROGRAMS) PROGRAMS += git-fast-import$X PROGRAMS += git-hash-object$X +PROGRAMS += git-imap-send$X PROGRAMS += git-index-pack$X PROGRAMS += git-merge-index$X PROGRAMS += git-merge-tree$X @@ -1056,7 +1057,6 @@ EXTLIBS += -lz ifndef NO_POSIX_ONLY_PROGRAMS PROGRAMS += git-daemon$X - PROGRAMS += git-imap-send$X endif ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl From 514213bf72cb61725e42440aaf8dd53fa4c33a74 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 19 Oct 2009 17:42:07 +0200 Subject: [PATCH 124/884] mingw: wrap SSL_set_(w|r)fd to call _get_osfhandle SSL_set_fd (and friends) expects a OS file handle on Windows, not a file descriptor as on UNIX(-ish). This patch makes the Windows version of SSL_set_fd behave like the UNIX versions, by calling _get_osfhandle on it's input. Signed-off-by: Erik Faye-Lund Acked-by: Johannes Sixt Signed-off-by: Junio C Hamano --- compat/mingw.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/compat/mingw.h b/compat/mingw.h index 5b5258bceb..6907345f90 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -124,6 +124,27 @@ static inline int waitpid(pid_t pid, int *status, unsigned options) return -1; } +#ifndef NO_OPENSSL +#include +static inline int mingw_SSL_set_fd(SSL *ssl, int fd) +{ + return SSL_set_fd(ssl, _get_osfhandle(fd)); +} +#define SSL_set_fd mingw_SSL_set_fd + +static inline int mingw_SSL_set_rfd(SSL *ssl, int fd) +{ + return SSL_set_rfd(ssl, _get_osfhandle(fd)); +} +#define SSL_set_rfd mingw_SSL_set_rfd + +static inline int mingw_SSL_set_wfd(SSL *ssl, int fd) +{ + return SSL_set_wfd(ssl, _get_osfhandle(fd)); +} +#define SSL_set_wfd mingw_SSL_set_wfd +#endif + /* * implementations of missing functions */ From dd2e794a214350711db46c4e08d9b19188a7d63a Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 19 Oct 2009 17:48:08 +0200 Subject: [PATCH 125/884] Refactor pretty_print_commit arguments into a struct pretty_print_commit() has a bunch of rarely-used arguments, and introducing more of them requires yet another update of all the call sites. Refactor most of them into a struct to make future extensions easier. The ones that stay "plain" arguments were chosen on the grounds that all callers put real arguments there, whereas some callers have 0/NULL for all arguments that were factored into the struct. We declare the struct 'const' to ensure none of the callers are bitten by the changed (no longer call-by-value) semantics. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- archive.c | 4 +++- builtin-branch.c | 3 ++- builtin-checkout.c | 3 ++- builtin-commit.c | 8 ++++++-- builtin-log.c | 3 ++- builtin-merge.c | 7 +++++-- builtin-rev-list.c | 7 ++++--- builtin-shortlog.c | 9 ++++++--- builtin-show-branch.c | 4 ++-- commit.h | 19 +++++++++++++------ log-tree.c | 24 +++++++++++++----------- pretty.c | 27 ++++++++++++++------------- 12 files changed, 72 insertions(+), 46 deletions(-) diff --git a/archive.c b/archive.c index 0cc79d2a24..55b273246e 100644 --- a/archive.c +++ b/archive.c @@ -31,6 +31,8 @@ static void format_subst(const struct commit *commit, { char *to_free = NULL; struct strbuf fmt = STRBUF_INIT; + struct pretty_print_context ctx = {0}; + ctx.date_mode = DATE_NORMAL; if (src == buf->buf) to_free = strbuf_detach(buf, NULL); @@ -48,7 +50,7 @@ static void format_subst(const struct commit *commit, strbuf_add(&fmt, b + 8, c - b - 8); strbuf_add(buf, src, b - src); - format_commit_message(commit, fmt.buf, buf, DATE_NORMAL); + format_commit_message(commit, fmt.buf, buf, &ctx); len -= c + 1 - src; src = c + 1; } diff --git a/builtin-branch.c b/builtin-branch.c index 9f57992062..05e876e285 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -387,8 +387,9 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, commit = item->commit; if (commit && !parse_commit(commit)) { + struct pretty_print_context ctx = {0}; pretty_print_commit(CMIT_FMT_ONELINE, commit, - &subject, 0, NULL, NULL, 0, 0); + &subject, &ctx); sub = subject.buf; } diff --git a/builtin-checkout.c b/builtin-checkout.c index d050c3789f..075a49f1a0 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -302,8 +302,9 @@ static void show_local_changes(struct object *head) static void describe_detached_head(char *msg, struct commit *commit) { struct strbuf sb = STRBUF_INIT; + struct pretty_print_context ctx = {0}; parse_commit(commit); - pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, NULL, NULL, 0, 0); + pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx); fprintf(stderr, "%s %s... %s\n", msg, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf); strbuf_release(&sb); diff --git a/builtin-commit.c b/builtin-commit.c index 200ffdaad4..13edeee575 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -684,8 +684,10 @@ static const char *find_author_by_nickname(const char *name) prepare_revision_walk(&revs); commit = get_revision(&revs); if (commit) { + struct pretty_print_context ctx = {0}; + ctx.date_mode = DATE_NORMAL; strbuf_release(&buf); - format_commit_message(commit, "%an <%ae>", &buf, DATE_NORMAL); + format_commit_message(commit, "%an <%ae>", &buf, &ctx); return strbuf_detach(&buf, NULL); } die("No existing author found with '%s'", name); @@ -942,8 +944,10 @@ static void print_summary(const char *prefix, const unsigned char *sha1) initial_commit ? " (root-commit)" : ""); if (!log_tree_commit(&rev, commit)) { + struct pretty_print_context ctx = {0}; struct strbuf buf = STRBUF_INIT; - format_commit_message(commit, format + 7, &buf, DATE_NORMAL); + ctx.date_mode = DATE_NORMAL; + format_commit_message(commit, format + 7, &buf, &ctx); printf("%s\n", buf.buf); strbuf_release(&buf); } diff --git a/builtin-log.c b/builtin-log.c index 25e21ed415..207a36178b 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -1304,8 +1304,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) if (verbose) { struct strbuf buf = STRBUF_INIT; + struct pretty_print_context ctx = {0}; pretty_print_commit(CMIT_FMT_ONELINE, commit, - &buf, 0, NULL, NULL, 0, 0); + &buf, &ctx); printf("%c %s %s\n", sign, sha1_to_hex(commit->object.sha1), buf.buf); strbuf_release(&buf); diff --git a/builtin-merge.c b/builtin-merge.c index b6b84286b2..c69a3051f3 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -264,6 +264,7 @@ static void squash_message(void) struct strbuf out = STRBUF_INIT; struct commit_list *j; int fd; + struct pretty_print_context ctx = {0}; printf("Squash commit -- not updating HEAD\n"); fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666); @@ -285,13 +286,15 @@ static void squash_message(void) if (prepare_revision_walk(&rev)) die("revision walk setup failed"); + ctx.abbrev = rev.abbrev; + ctx.date_mode = rev.date_mode; + strbuf_addstr(&out, "Squashed commit of the following:\n"); while ((commit = get_revision(&rev)) != NULL) { strbuf_addch(&out, '\n'); strbuf_addf(&out, "commit %s\n", sha1_to_hex(commit->object.sha1)); - pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev, - NULL, NULL, rev.date_mode, 0); + pretty_print_commit(rev.commit_format, commit, &out, &ctx); } if (write(fd, out.buf, out.len) < 0) die_errno("Writing SQUASH_MSG"); diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 4ba1c12e0b..42cc8d8872 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -96,9 +96,10 @@ static void show_commit(struct commit *commit, void *data) if (revs->verbose_header && commit->buffer) { struct strbuf buf = STRBUF_INIT; - pretty_print_commit(revs->commit_format, commit, - &buf, revs->abbrev, NULL, NULL, - revs->date_mode, 0); + struct pretty_print_context ctx = {0}; + ctx.abbrev = revs->abbrev; + ctx.date_mode = revs->date_mode; + pretty_print_commit(revs->commit_format, commit, &buf, &ctx); if (revs->graph) { if (buf.len) { if (revs->commit_format != CMIT_FMT_ONELINE) diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 4d4a3c82d6..8aa63c7857 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -158,9 +158,12 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) sha1_to_hex(commit->object.sha1)); if (log->user_format) { struct strbuf buf = STRBUF_INIT; - - pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &buf, - DEFAULT_ABBREV, "", "", DATE_NORMAL, 0); + struct pretty_print_context ctx = {0}; + ctx.abbrev = DEFAULT_ABBREV; + ctx.subject = ""; + ctx.after_subject = ""; + ctx.date_mode = DATE_NORMAL; + pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &buf, &ctx); insert_one_record(log, author, buf.buf); strbuf_release(&buf); return; diff --git a/builtin-show-branch.c b/builtin-show-branch.c index be95930b78..9f13caa76d 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -293,8 +293,8 @@ static void show_one_commit(struct commit *commit, int no_name) struct commit_name *name = commit->util; if (commit->object.parsed) { - pretty_print_commit(CMIT_FMT_ONELINE, commit, - &pretty, 0, NULL, NULL, 0, 0); + struct pretty_print_context ctx = {0}; + pretty_print_commit(CMIT_FMT_ONELINE, commit, &pretty, &ctx); pretty_str = pretty.buf; } if (!prefixcmp(pretty_str, "[PATCH] ")) diff --git a/commit.h b/commit.h index f4fc5c5589..011766dee4 100644 --- a/commit.h +++ b/commit.h @@ -63,6 +63,15 @@ enum cmit_fmt { CMIT_FMT_UNSPECIFIED, }; +struct pretty_print_context +{ + int abbrev; + const char *subject; + const char *after_subject; + enum date_mode date_mode; + int need_8bit_cte; +}; + extern int non_ascii(int); extern int has_non_ascii(const char *text); struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ @@ -71,12 +80,10 @@ extern char *reencode_commit_message(const struct commit *commit, extern void get_commit_format(const char *arg, struct rev_info *); extern void format_commit_message(const struct commit *commit, const void *format, struct strbuf *sb, - enum date_mode dmode); -extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*, - struct strbuf *, - int abbrev, const char *subject, - const char *after_subject, enum date_mode, - int need_8bit_cte); + const struct pretty_print_context *context); +extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, + struct strbuf *sb, + const struct pretty_print_context *context); void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb, const char *line, enum date_mode dmode, const char *encoding); diff --git a/log-tree.c b/log-tree.c index f7d54f2f1b..1675035d84 100644 --- a/log-tree.c +++ b/log-tree.c @@ -179,8 +179,10 @@ void get_patch_filename(struct commit *commit, int nr, const char *suffix, strbuf_addf(buf, commit ? "%04d-" : "%d", nr); if (commit) { int max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len; + struct pretty_print_context ctx = {0}; + ctx.date_mode = DATE_NORMAL; - format_commit_message(commit, "%f", buf, DATE_NORMAL); + format_commit_message(commit, "%f", buf, &ctx); if (max_len < buf->len) strbuf_setlen(buf, max_len); strbuf_addstr(buf, suffix); @@ -277,10 +279,9 @@ void show_log(struct rev_info *opt) struct strbuf msgbuf = STRBUF_INIT; struct log_info *log = opt->loginfo; struct commit *commit = log->commit, *parent = log->parent; - int abbrev = opt->diffopt.abbrev; int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40; - const char *subject = NULL, *extra_headers = opt->extra_headers; - int need_8bit_cte = 0; + const char *extra_headers = opt->extra_headers; + struct pretty_print_context ctx = {0}; opt->loginfo = NULL; if (!opt->verbose_header) { @@ -347,8 +348,8 @@ void show_log(struct rev_info *opt) */ if (opt->commit_format == CMIT_FMT_EMAIL) { - log_write_email_headers(opt, commit, &subject, &extra_headers, - &need_8bit_cte); + log_write_email_headers(opt, commit, &ctx.subject, &extra_headers, + &ctx.need_8bit_cte); } else if (opt->commit_format != CMIT_FMT_USERFORMAT) { fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout); if (opt->commit_format != CMIT_FMT_ONELINE) @@ -405,11 +406,12 @@ void show_log(struct rev_info *opt) /* * And then the pretty-printed message itself */ - if (need_8bit_cte >= 0) - need_8bit_cte = has_non_ascii(opt->add_signoff); - pretty_print_commit(opt->commit_format, commit, &msgbuf, - abbrev, subject, extra_headers, opt->date_mode, - need_8bit_cte); + if (ctx.need_8bit_cte >= 0) + ctx.need_8bit_cte = has_non_ascii(opt->add_signoff); + ctx.date_mode = opt->date_mode; + ctx.abbrev = opt->diffopt.abbrev; + ctx.after_subject = extra_headers; + pretty_print_commit(opt->commit_format, commit, &msgbuf, &ctx); if (opt->add_signoff) append_signoff(&msgbuf, opt->add_signoff); diff --git a/pretty.c b/pretty.c index f5983f8baa..d6d57ebd23 100644 --- a/pretty.c +++ b/pretty.c @@ -442,7 +442,7 @@ struct chunk { struct format_commit_context { const struct commit *commit; - enum date_mode dmode; + const struct pretty_print_context *pretty_ctx; unsigned commit_header_parsed:1; unsigned commit_message_parsed:1; @@ -711,11 +711,11 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, case 'a': /* author ... */ return format_person_part(sb, placeholder[1], msg + c->author.off, c->author.len, - c->dmode); + c->pretty_ctx->date_mode); case 'c': /* committer ... */ return format_person_part(sb, placeholder[1], msg + c->committer.off, c->committer.len, - c->dmode); + c->pretty_ctx->date_mode); case 'e': /* encoding */ strbuf_add(sb, msg + c->encoding.off, c->encoding.len); return 1; @@ -741,13 +741,13 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, void format_commit_message(const struct commit *commit, const void *format, struct strbuf *sb, - enum date_mode dmode) + const struct pretty_print_context *pretty_ctx) { struct format_commit_context context; memset(&context, 0, sizeof(context)); context.commit = commit; - context.dmode = dmode; + context.pretty_ctx = pretty_ctx; strbuf_expand(sb, format, format_commit_item, &context); } @@ -900,18 +900,18 @@ char *reencode_commit_message(const struct commit *commit, const char **encoding } void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, - struct strbuf *sb, int abbrev, - const char *subject, const char *after_subject, - enum date_mode dmode, int need_8bit_cte) + struct strbuf *sb, + const struct pretty_print_context *context) { unsigned long beginning_of_body; int indent = 4; const char *msg = commit->buffer; char *reencoded; const char *encoding; + int need_8bit_cte = context->need_8bit_cte; if (fmt == CMIT_FMT_USERFORMAT) { - format_commit_message(commit, user_format, sb, dmode); + format_commit_message(commit, user_format, sb, context); return; } @@ -946,8 +946,9 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, } } - pp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb); - if (fmt != CMIT_FMT_ONELINE && !subject) { + pp_header(fmt, context->abbrev, context->date_mode, encoding, + commit, &msg, sb); + if (fmt != CMIT_FMT_ONELINE && !context->subject) { strbuf_addch(sb, '\n'); } @@ -956,8 +957,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, /* These formats treat the title line specially. */ if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL) - pp_title_line(fmt, &msg, sb, subject, - after_subject, encoding, need_8bit_cte); + pp_title_line(fmt, &msg, sb, context->subject, + context->after_subject, encoding, need_8bit_cte); beginning_of_body = sb->len; if (fmt != CMIT_FMT_ONELINE) From 72b103fec7af967d295410c2fd3899bc6e8386e2 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 19 Oct 2009 17:48:09 +0200 Subject: [PATCH 126/884] reflog-walk: refactor the branch@{num} formatting We'll use the same output in an upcoming commit, so refactor its formatting (which was duplicated anyway) into a separate function. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- reflog-walk.c | 54 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/reflog-walk.c b/reflog-walk.c index 5623ea6b48..596bafebdd 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -241,36 +241,46 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit) commit->object.flags &= ~(ADDED | SEEN | SHOWN); } -void show_reflog_message(struct reflog_walk_info *info, int oneline, +void get_reflog_selector(struct strbuf *sb, + struct reflog_walk_info *reflog_info, + enum date_mode dmode) +{ + struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; + struct reflog_info *info; + + if (!commit_reflog) + return; + + strbuf_addf(sb, "%s@{", commit_reflog->reflogs->ref); + if (commit_reflog->flag || dmode) { + info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; + strbuf_addstr(sb, show_date(info->timestamp, info->tz, dmode)); + } else { + strbuf_addf(sb, "%d", commit_reflog->reflogs->nr + - 2 - commit_reflog->recno); + } + + strbuf_addch(sb, '}'); +} + +void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline, enum date_mode dmode) { - if (info && info->last_commit_reflog) { - struct commit_reflog *commit_reflog = info->last_commit_reflog; + if (reflog_info && reflog_info->last_commit_reflog) { + struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; struct reflog_info *info; + struct strbuf selector = STRBUF_INIT; info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; + get_reflog_selector(&selector, reflog_info, dmode); if (oneline) { - printf("%s@{", commit_reflog->reflogs->ref); - if (commit_reflog->flag || dmode) - printf("%s", show_date(info->timestamp, - info->tz, - dmode)); - else - printf("%d", commit_reflog->reflogs->nr - - 2 - commit_reflog->recno); - printf("}: %s", info->message); + printf("%s: %s", selector.buf, info->message); } else { - printf("Reflog: %s@{", commit_reflog->reflogs->ref); - if (commit_reflog->flag || dmode) - printf("%s", show_date(info->timestamp, - info->tz, - dmode)); - else - printf("%d", commit_reflog->reflogs->nr - - 2 - commit_reflog->recno); - printf("} (%s)\nReflog message: %s", - info->email, info->message); + printf("Reflog: %s (%s)\nReflog message: %s", + selector.buf, info->email, info->message); } + + strbuf_release(&selector); } } From 8f8f5476cd6542387d435c242752404cf144005f Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 19 Oct 2009 17:48:10 +0200 Subject: [PATCH 127/884] Introduce new pretty formats %g[sdD] for reflog information Add three new --pretty=format escapes: %gD long reflog descriptor (e.g. refs/stash@{0}) %gd short reflog descriptor (e.g. stash@{0}) %gs reflog message This is achieved by passing down the reflog info, if any, inside the pretty_print_context struct. We use the newly refactored get_reflog_selector(), and give it some extra functionality to extract a shortened ref. The shortening is cached inside the commit_reflogs struct; the only allocation of it happens in read_complete_reflog(), where it is initialised to 0. Also add another helper get_reflog_message() for the message extraction. Note that the --format="%h %gD: %gs" tests may not work in real repositories, as the --pretty formatter doesn't know to leave away the ": " on the last commit in an incomplete (because git-gc removed the old part) reflog. This equivalence is nevertheless the main goal of this patch. Thanks to Jeff King for reviews, the %gd testcase and documentation. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- Documentation/pretty-formats.txt | 9 ++++++++ commit.h | 1 + log-tree.c | 1 + pretty.c | 17 ++++++++++++++++ reflog-walk.c | 35 +++++++++++++++++++++++++++++--- reflog-walk.h | 8 ++++++++ t/t6006-rev-list-format.sh | 18 ++++++++++++++++ 7 files changed, 86 insertions(+), 3 deletions(-) diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 2a845b1e57..38b9904791 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -123,6 +123,9 @@ The placeholders are: - '%s': subject - '%f': sanitized subject line, suitable for a filename - '%b': body +- '%gD': reflog selector, e.g., `refs/stash@\{1\}` +- '%gd': shortened reflog selector, e.g., `stash@\{1\}` +- '%gs': reflog subject - '%Cred': switch color to red - '%Cgreen': switch color to green - '%Cblue': switch color to blue @@ -132,6 +135,12 @@ The placeholders are: - '%n': newline - '%x00': print a byte from a hex code +NOTE: Some placeholders may depend on other options given to the +revision traversal engine. For example, the `%g*` reflog options will +insert an empty string unless we are traversing reflog entries (e.g., by +`git log -g`). The `%d` placeholder will use the "short" decoration +format if `--decorate` was not already provided on the command line. + * 'tformat:' + The 'tformat:' format works exactly like 'format:', except that it diff --git a/commit.h b/commit.h index 011766dee4..15cb64920c 100644 --- a/commit.h +++ b/commit.h @@ -70,6 +70,7 @@ struct pretty_print_context const char *after_subject; enum date_mode date_mode; int need_8bit_cte; + struct reflog_walk_info *reflog_info; }; extern int non_ascii(int); diff --git a/log-tree.c b/log-tree.c index 1675035d84..0fdf159f80 100644 --- a/log-tree.c +++ b/log-tree.c @@ -411,6 +411,7 @@ void show_log(struct rev_info *opt) ctx.date_mode = opt->date_mode; ctx.abbrev = opt->diffopt.abbrev; ctx.after_subject = extra_headers; + ctx.reflog_info = opt->reflog_info; pretty_print_commit(opt->commit_format, commit, &msgbuf, &ctx); if (opt->add_signoff) diff --git a/pretty.c b/pretty.c index d6d57ebd23..fc65fca58a 100644 --- a/pretty.c +++ b/pretty.c @@ -7,6 +7,7 @@ #include "mailmap.h" #include "log-tree.h" #include "color.h" +#include "reflog-walk.h" static char *user_format; @@ -701,6 +702,22 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, case 'd': format_decoration(sb, commit); return 1; + case 'g': /* reflog info */ + switch(placeholder[1]) { + case 'd': /* reflog selector */ + case 'D': + if (c->pretty_ctx->reflog_info) + get_reflog_selector(sb, + c->pretty_ctx->reflog_info, + c->pretty_ctx->date_mode, + (placeholder[1] == 'd')); + return 2; + case 's': /* reflog message */ + if (c->pretty_ctx->reflog_info) + get_reflog_message(sb, c->pretty_ctx->reflog_info); + return 2; + } + return 0; /* unknown %g placeholder */ } /* For the rest we have to parse the commit header. */ diff --git a/reflog-walk.c b/reflog-walk.c index 596bafebdd..caba4f743f 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -8,6 +8,7 @@ struct complete_reflogs { char *ref; + const char *short_ref; struct reflog_info { unsigned char osha1[20], nsha1[20]; char *email; @@ -243,15 +244,26 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit) void get_reflog_selector(struct strbuf *sb, struct reflog_walk_info *reflog_info, - enum date_mode dmode) + enum date_mode dmode, + int shorten) { struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; struct reflog_info *info; + const char *printed_ref; if (!commit_reflog) return; - strbuf_addf(sb, "%s@{", commit_reflog->reflogs->ref); + if (shorten) { + if (!commit_reflog->reflogs->short_ref) + commit_reflog->reflogs->short_ref + = shorten_unambiguous_ref(commit_reflog->reflogs->ref, 0); + printed_ref = commit_reflog->reflogs->short_ref; + } else { + printed_ref = commit_reflog->reflogs->ref; + } + + strbuf_addf(sb, "%s@{", printed_ref); if (commit_reflog->flag || dmode) { info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; strbuf_addstr(sb, show_date(info->timestamp, info->tz, dmode)); @@ -263,6 +275,23 @@ void get_reflog_selector(struct strbuf *sb, strbuf_addch(sb, '}'); } +void get_reflog_message(struct strbuf *sb, + struct reflog_walk_info *reflog_info) +{ + struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; + struct reflog_info *info; + size_t len; + + if (!commit_reflog) + return; + + info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; + len = strlen(info->message); + if (len > 0) + len--; /* strip away trailing newline */ + strbuf_add(sb, info->message, len); +} + void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline, enum date_mode dmode) { @@ -272,7 +301,7 @@ void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline, struct strbuf selector = STRBUF_INIT; info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; - get_reflog_selector(&selector, reflog_info, dmode); + get_reflog_selector(&selector, reflog_info, dmode, 0); if (oneline) { printf("%s: %s", selector.buf, info->message); } diff --git a/reflog-walk.h b/reflog-walk.h index 74c90964bd..7bd2cd4c4e 100644 --- a/reflog-walk.h +++ b/reflog-walk.h @@ -3,6 +3,8 @@ #include "cache.h" +struct reflog_walk_info; + extern void init_reflog_walk(struct reflog_walk_info** info); extern int add_reflog_for_walk(struct reflog_walk_info *info, struct commit *commit, const char *name); @@ -10,5 +12,11 @@ extern void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit); extern void show_reflog_message(struct reflog_walk_info *info, int, enum date_mode); +extern void get_reflog_message(struct strbuf *sb, + struct reflog_walk_info *reflog_info); +extern void get_reflog_selector(struct strbuf *sb, + struct reflog_walk_info *reflog_info, + enum date_mode dmode, + int shorten); #endif diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 59d1f6283b..7f61ab0e52 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -162,4 +162,22 @@ test_expect_success 'empty email' ' } ' +test_expect_success '"%h %gD: %gs" is same as git-reflog' ' + git reflog >expect && + git log -g --format="%h %gD: %gs" >actual && + test_cmp expect actual +' + +test_expect_success '"%h %gD: %gs" is same as git-reflog (with date)' ' + git reflog --date=raw >expect && + git log -g --format="%h %gD: %gs" --date=raw >actual && + test_cmp expect actual +' + +test_expect_success '%gd shortens ref name' ' + echo "master@{0}" >expect.gd-short && + git log -g -1 --format=%gd refs/heads/master >actual.gd-short && + test_cmp expect.gd-short actual.gd-short +' + test_done From 391c53bdcd7bbce366eaef7288afb948525ed3e8 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 19 Oct 2009 17:48:11 +0200 Subject: [PATCH 128/884] stash list: use new %g formats instead of sed With the new formats, we can rewrite 'git stash list' in terms of an appropriate pretty format, instead of hand-editing with sed. This has the advantage that it obeys the normal settings for git-log, notably the pager. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- git-stash.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/git-stash.sh b/git-stash.sh index 4febbbfa5d..f8847c1fc0 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -205,8 +205,7 @@ have_stash () { list_stash () { have_stash || return 0 - git log --no-color --pretty=oneline -g "$@" $ref_stash -- | - sed -n -e 's/^[.0-9a-f]* refs\///p' + git log --format="%gd: %gs" -g "$@" $ref_stash -- } show_stash () { From b7b10385a84c741a4fe219807c9511f69403640a Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 19 Oct 2009 17:48:12 +0200 Subject: [PATCH 129/884] stash list: drop the default limit of 10 stashes 'git stash list' had an undocumented limit of 10 stashes, unless other git-log arguments were specified. This surprised at least one user, but possibly served to cut the output below a screenful without using a pager. Since the last commit, 'git stash list' will fire up a pager according to the same rules as the 'git log' it calls, so we can drop the limit. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- Documentation/git-stash.txt | 3 +-- git-stash.sh | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index fafe728f89..3f14b727b8 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -78,8 +78,7 @@ stash@{1}: On master: 9cc0589... Add git-stash ---------------------------------------------------------------- + The command takes options applicable to the 'git-log' -command to control what is shown and how. If no options are set, the -default is `-n 10`. See linkgit:git-log[1]. +command to control what is shown and how. See linkgit:git-log[1]. show []:: diff --git a/git-stash.sh b/git-stash.sh index f8847c1fc0..f796c2fe24 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -382,11 +382,6 @@ test -n "$seen_non_option" || set "save" "$@" case "$1" in list) shift - if test $# = 0 - then - set x -n 10 - shift - fi list_stash "$@" ;; show) From 752c0c24926aacbceca0d27de6ad22cbb7dd0709 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 19 Oct 2009 14:38:32 +0200 Subject: [PATCH 130/884] Add the --submodule option to the diff option family When you use the option --submodule=log you can see the submodule summaries inlined in the diff, instead of not-quite-helpful SHA-1 pairs. The format imitates what "git submodule summary" shows. To do that, /.git/objects/ is added to the alternate object databases (if that directory exists). This option was requested by Jens Lehmann at the GitTogether in Berlin. Signed-off-by: Johannes Schindelin Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 7 ++ Makefile | 2 + diff.c | 18 ++++++ diff.h | 3 + submodule.c | 113 +++++++++++++++++++++++++++++++++ submodule.h | 8 +++ 6 files changed, 151 insertions(+) create mode 100644 submodule.c create mode 100644 submodule.h diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 9276faeb11..e26b84706f 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -87,6 +87,13 @@ endif::git-format-patch[] Show only names and status of changed files. See the description of the `--diff-filter` option on what the status letters mean. +--submodule[=]:: + Chose the output format for submodule differences. can be one of + 'short' and 'log'. 'short' just shows pairs of commit names, this format + is used when this option is not given. 'log' is the default value for this + option and lists the commits in that commit range like the 'summary' + option of linkgit:git-submodule[1] does. + --color:: Show colored diff. diff --git a/Makefile b/Makefile index 12defd4c97..efade29ea8 100644 --- a/Makefile +++ b/Makefile @@ -448,6 +448,7 @@ LIB_H += sideband.h LIB_H += sigchain.h LIB_H += strbuf.h LIB_H += string-list.h +LIB_H += submodule.h LIB_H += tag.h LIB_H += transport.h LIB_H += tree.h @@ -546,6 +547,7 @@ LIB_OBJS += sideband.o LIB_OBJS += sigchain.o LIB_OBJS += strbuf.o LIB_OBJS += string-list.o +LIB_OBJS += submodule.o LIB_OBJS += symlinks.o LIB_OBJS += tag.o LIB_OBJS += trace.o diff --git a/diff.c b/diff.c index e1be189742..6c63b87a8c 100644 --- a/diff.c +++ b/diff.c @@ -13,6 +13,7 @@ #include "utf8.h" #include "userdiff.h" #include "sigchain.h" +#include "submodule.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -1453,6 +1454,17 @@ static void builtin_diff(const char *name_a, const char *a_prefix, *b_prefix; const char *textconv_one = NULL, *textconv_two = NULL; + if (DIFF_OPT_TST(o, SUBMODULE_LOG) && + (!one->mode || S_ISGITLINK(one->mode)) && + (!two->mode || S_ISGITLINK(two->mode))) { + const char *del = diff_get_color_opt(o, DIFF_FILE_OLD); + const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); + show_submodule_summary(o->file, one ? one->path : two->path, + one->sha1, two->sha1, + del, add, reset); + return; + } + if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) { textconv_one = get_textconv(one); textconv_two = get_textconv(two); @@ -2640,6 +2652,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_CLR(options, ALLOW_TEXTCONV); else if (!strcmp(arg, "--ignore-submodules")) DIFF_OPT_SET(options, IGNORE_SUBMODULES); + else if (!strcmp(arg, "--submodule")) + DIFF_OPT_SET(options, SUBMODULE_LOG); + else if (!prefixcmp(arg, "--submodule=")) { + if (!strcmp(arg + 12, "log")) + DIFF_OPT_SET(options, SUBMODULE_LOG); + } /* misc options */ else if (!strcmp(arg, "-z")) diff --git a/diff.h b/diff.h index 6616877ee5..2740421cfe 100644 --- a/diff.h +++ b/diff.h @@ -66,6 +66,9 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_DIRSTAT_CUMULATIVE (1 << 19) #define DIFF_OPT_DIRSTAT_BY_FILE (1 << 20) #define DIFF_OPT_ALLOW_TEXTCONV (1 << 21) + +#define DIFF_OPT_SUBMODULE_LOG (1 << 23) + #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) #define DIFF_OPT_CLR(opts, flag) ((opts)->flags &= ~DIFF_OPT_##flag) diff --git a/submodule.c b/submodule.c new file mode 100644 index 0000000000..d5fce7a882 --- /dev/null +++ b/submodule.c @@ -0,0 +1,113 @@ +#include "cache.h" +#include "submodule.h" +#include "dir.h" +#include "diff.h" +#include "commit.h" +#include "revision.h" + +int add_submodule_odb(const char *path) +{ + struct strbuf objects_directory = STRBUF_INIT; + struct alternate_object_database *alt_odb; + + strbuf_addf(&objects_directory, "%s/.git/objects/", path); + if (!is_directory(objects_directory.buf)) + return -1; + + /* avoid adding it twice */ + for (alt_odb = alt_odb_list; alt_odb; alt_odb = alt_odb->next) + if (alt_odb->name - alt_odb->base == objects_directory.len && + !strncmp(alt_odb->base, objects_directory.buf, + objects_directory.len)) + return 0; + + alt_odb = xmalloc(objects_directory.len + 42 + sizeof(*alt_odb)); + alt_odb->next = alt_odb_list; + strcpy(alt_odb->base, objects_directory.buf); + alt_odb->name = alt_odb->base + objects_directory.len; + alt_odb->name[2] = '/'; + alt_odb->name[40] = '\0'; + alt_odb->name[41] = '\0'; + alt_odb_list = alt_odb; + prepare_alt_odb(); + return 0; +} + +void show_submodule_summary(FILE *f, const char *path, + unsigned char one[20], unsigned char two[20], + const char *del, const char *add, const char *reset) +{ + struct rev_info rev; + struct commit *commit, *left = left, *right; + struct commit_list *merge_bases, *list; + const char *message = NULL; + struct strbuf sb = STRBUF_INIT; + static const char *format = " %m %s"; + int fast_forward = 0, fast_backward = 0; + + if (is_null_sha1(two)) + message = "(submodule deleted)"; + else if (add_submodule_odb(path)) + message = "(not checked out)"; + else if (is_null_sha1(one)) + message = "(new submodule)"; + else if (!(left = lookup_commit_reference(one)) || + !(right = lookup_commit_reference(two))) + message = "(commits not present)"; + + if (!message) { + init_revisions(&rev, NULL); + setup_revisions(0, NULL, &rev, NULL); + rev.left_right = 1; + rev.first_parent_only = 1; + left->object.flags |= SYMMETRIC_LEFT; + add_pending_object(&rev, &left->object, path); + add_pending_object(&rev, &right->object, path); + merge_bases = get_merge_bases(left, right, 1); + if (merge_bases) { + if (merge_bases->item == left) + fast_forward = 1; + else if (merge_bases->item == right) + fast_backward = 1; + } + for (list = merge_bases; list; list = list->next) { + list->item->object.flags |= UNINTERESTING; + add_pending_object(&rev, &list->item->object, + sha1_to_hex(list->item->object.sha1)); + } + if (prepare_revision_walk(&rev)) + message = "(revision walker failed)"; + } + + strbuf_addf(&sb, "Submodule %s %s..", path, + find_unique_abbrev(one, DEFAULT_ABBREV)); + if (!fast_backward && !fast_forward) + strbuf_addch(&sb, '.'); + strbuf_addf(&sb, "%s", find_unique_abbrev(two, DEFAULT_ABBREV)); + if (message) + strbuf_addf(&sb, " %s\n", message); + else + strbuf_addf(&sb, "%s:\n", fast_backward ? " (rewind)" : ""); + fwrite(sb.buf, sb.len, 1, f); + + if (!message) { + while ((commit = get_revision(&rev))) { + strbuf_setlen(&sb, 0); + if (commit->object.flags & SYMMETRIC_LEFT) { + if (del) + strbuf_addstr(&sb, del); + } + else if (add) + strbuf_addstr(&sb, add); + format_commit_message(commit, format, &sb, + rev.date_mode); + if (reset) + strbuf_addstr(&sb, reset); + strbuf_addch(&sb, '\n'); + fprintf(f, "%s", sb.buf); + } + clear_commit_marks(left, ~0); + clear_commit_marks(right, ~0); + } + strbuf_release(&sb); +} diff --git a/submodule.h b/submodule.h new file mode 100644 index 0000000000..4c0269d679 --- /dev/null +++ b/submodule.h @@ -0,0 +1,8 @@ +#ifndef SUBMODULE_H +#define SUBMODULE_H + +void show_submodule_summary(FILE *f, const char *path, + unsigned char one[20], unsigned char two[20], + const char *del, const char *add, const char *reset); + +#endif From 3ed0b11e7e9a8a35956d2c8c39c51c8d8f5eac23 Mon Sep 17 00:00:00 2001 From: Matt Kraai Date: Mon, 19 Oct 2009 22:22:25 -0700 Subject: [PATCH 131/884] Documentation/git-gc.txt: change "references" to "reference" Signed-off-by: Matt Kraai Signed-off-by: Junio C Hamano --- Documentation/git-gc.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 1f6df6ad6b..4cd9cdf905 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -120,7 +120,7 @@ Notes particular, it will keep not only objects referenced by your current set of branches and tags, but also objects referenced by the index, remote tracking branches, refs saved by 'git-filter-branch' in -refs/original/, or reflogs (which may references commits in branches +refs/original/, or reflogs (which may reference commits in branches that were later amended or rewound). If you are expecting some objects to be collected and they aren't, check From e133d65c3e3d7f5f869edb2d6205af68af0fe38f Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 15 Oct 2009 21:14:59 -0700 Subject: [PATCH 132/884] gitweb: linkify author/committer names with search It's nice to search for an author by merely clicking on their name in gitweb. This is usually faster than selecting the name, copying the selection, pasting it into the search box, selecting between author/committer and then hitting enter. Linkify the avatar icon in log/shortlog view because the icon is directly adjacent to the name and thus more related. The same is not true when in commit/tag view where the icon is farther away. Signed-off-by: Stephen Boyd Signed-off-by: Junio C Hamano --- gitweb/gitweb.css | 4 ++++ gitweb/gitweb.perl | 40 +++++++++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 8f68fe3091..27405e647d 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -32,6 +32,10 @@ img.avatar { vertical-align: middle; } +a.list img.avatar { + border-style: none; +} + div.page_header { height: 25px; padding: 8px; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 4b21ad25df..c160a56c6c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1594,6 +1594,29 @@ sub git_get_avatar { } } +sub format_search_author { + my ($author, $searchtype, $displaytext) = @_; + my $have_search = gitweb_check_feature('search'); + + if ($have_search) { + my $performed = ""; + if ($searchtype eq 'author') { + $performed = "authored"; + } elsif ($searchtype eq 'committer') { + $performed = "committed"; + } + + return $cgi->a({-href => href(action=>"search", hash=>$hash, + searchtext=>$author, + searchtype=>$searchtype), class=>"list", + title=>"Search for commits $performed by $author"}, + $displaytext); + + } else { + return $displaytext; + } +} + # format the author name of the given commit with the given tag # the author name is chopped and escaped according to the other # optional parameters (see chop_str). @@ -1602,8 +1625,10 @@ sub format_author_html { my $co = shift; my $author = chop_and_escape_str($co->{'author_name'}, @_); return "<$tag class=\"author\">" . - git_get_avatar($co->{'author_email'}, -pad_after => 1) . - $author . ""; + format_search_author($co->{'author_name'}, "author", + git_get_avatar($co->{'author_email'}, -pad_after => 1) . + $author) . + ""; } # format git diff header line, i.e. "diff --(git|combined|cc) ..." @@ -3372,10 +3397,11 @@ sub git_print_authorship { my $co = shift; my %opts = @_; my $tag = $opts{-tag} || 'div'; + my $author = $co->{'author_name'}; my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'}); print "<$tag class=\"author_date\">" . - esc_html($co->{'author_name'}) . + format_search_author($author, "author", esc_html($author)) . " [$ad{'rfc2822'}"; print_local_time(%ad) if ($opts{-localtime}); print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1) @@ -3394,8 +3420,12 @@ sub git_print_authorship_rows { @people = ('author', 'committer') unless @people; foreach my $who (@people) { my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"}); - print "$who" . esc_html($co->{$who}) . "" . - "" . + print "$who" . + format_search_author($co->{"${who}_name"}, $who, + esc_html($co->{"${who}_name"})) . " " . + format_search_author($co->{"${who}_email"}, $who, + esc_html("<" . $co->{"${who}_email"} . ">")) . + "" . git_get_avatar($co->{"${who}_email"}, -size => 'double') . "\n" . "" . From f29cd3938dbe9300dd75096d8ee2ad6311cc00af Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Oct 2009 11:46:55 -0700 Subject: [PATCH 133/884] fsck: default to "git fsck --full" Linus and other git developers from the early days trained their fingers to type the command, every once in a while even without thinking, to check the consistency of the repository back when the lower core part of the git was still being developed. Developers who wanted to make sure that git correctly dealt with packfiles could deliberately trigger their creation and checked them after they were created carefully, but loose objects are the ones that are written by various commands from random codepaths. It made some technical sense to have a mode that checked only loose objects from the debugging point of view for that reason. Even for git developers, there no longer is any reason to type "git fsck" every five minutes these days, worried that some newly created objects might be corrupt due to recent change to git. The reason we did not make "--full" the default is probably we trust our filesystems a bit too much. At least, we trusted filesystems more than we trusted the lower core part of git that was under development. Once a packfile is created and we always use it read-only, there didn't seem to be much point in suspecting that the underlying filesystems or disks may corrupt them in such a way that is not caught by the SHA-1 checksum over the entire packfile and per object checksum. That trust in the filesystems might have been a good tradeoff between fsck performance and reliability on platforms git was initially developed on and for, but it may not be true anymore as we run on many more platforms these days. Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.6.txt | 11 +++++++++++ Documentation/git-fsck.txt | 5 +++-- builtin-fsck.c | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Documentation/RelNotes-1.6.6.txt b/Documentation/RelNotes-1.6.6.txt index 5f1fecb5ec..fa0e11a180 100644 --- a/Documentation/RelNotes-1.6.6.txt +++ b/Documentation/RelNotes-1.6.6.txt @@ -1,6 +1,14 @@ GIT v1.6.6 Release Notes ======================== +In this release, "git fsck" defaults to "git fsck --full" and checks +packfiles, and because of this it will take much longer to complete +than before. If you prefer a quicker check only on loose objects (the +old default), you can say "git fsck --no-full". This has been +supported by 1.5.4 and newer versions of git, so it is safe to write +it in your script even if you use slightly older git on some of your +machines. + In git 1.7.0, which is planned to be the release after 1.6.6, "git push" into a branch that is currently checked out will be refused by default. @@ -38,6 +46,9 @@ Updates since v1.6.5 (usability, bells and whistles) + * "git fsck" by default checks the packfiles (i.e. "--full" is the + default); you can turn it off with "git fsck --no-full". + * "git log --decorate" shows the location of HEAD as well. (developers) diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index 287c4fc5e0..6fe9484da3 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs] - [--full] [--strict] [--verbose] [--lost-found] [*] + [--[no-]full] [--strict] [--verbose] [--lost-found] [*] DESCRIPTION ----------- @@ -52,7 +52,8 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless or $GIT_DIR/objects/info/alternates, and in packed git archives found in $GIT_DIR/objects/pack and corresponding pack subdirectories in alternate - object pools. + object pools. This is now default; you can turn it off + with --no-full. --strict:: Enable more strict checking, namely to catch a file mode diff --git a/builtin-fsck.c b/builtin-fsck.c index c58b0e337e..2d88e4570f 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -19,7 +19,7 @@ static int show_root; static int show_tags; static int show_unreachable; static int include_reflogs = 1; -static int check_full; +static int check_full = 1; static int check_strict; static int keep_cache_objects; static unsigned char head_sha1[20]; From 66bcb6a68f8759ab7a7b5ca47085ab1c1402062e Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Wed, 21 Oct 2009 13:35:15 +0200 Subject: [PATCH 134/884] Remove a left-over file from t/t5100 This mbox file must have been added by accident in e9fe804 (git-mailinfo: Fix getting the subject from the in-body [PATCH] line, 2008-07-14). Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- t/t5100/0010 | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 t/t5100/0010 diff --git a/t/t5100/0010 b/t/t5100/0010 deleted file mode 100644 index f5892c9da7..0000000000 --- a/t/t5100/0010 +++ /dev/null @@ -1,35 +0,0 @@ -From b9704a518e21158433baa2cc2d591fea687967f6 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Lukas=20Sandstr=C3=B6m?= -Date: Thu, 10 Jul 2008 23:41:33 +0200 -Subject: Re: discussion that lead to this patch -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -[PATCH] git-mailinfo: Fix getting the subject from the body - -"Subject: " isn't in the static array "header", and thus -memcmp("Subject: ", header[i], 7) will never match. - -Signed-off-by: Lukas Sandström -Signed-off-by: Junio C Hamano ---- - builtin-mailinfo.c | 2 +- - 1 files changed, 1 insertions(+), 1 deletions(-) - -diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c -index 962aa34..2d1520f 100644 ---- a/builtin-mailinfo.c -+++ b/builtin-mailinfo.c -@@ -334,7 +334,7 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over - return 1; - if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) { - for (i = 0; header[i]; i++) { -- if (!memcmp("Subject: ", header[i], 9)) { -+ if (!memcmp("Subject", header[i], 7)) { - if (! handle_header(line, hdr_data[i], 0)) { - return 1; - } --- -1.5.6.2.455.g1efb2 - From 814a9bfee93d6b7beefa4a6079d1acf4d4166724 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Wed, 21 Oct 2009 13:35:16 +0200 Subject: [PATCH 135/884] Mark files in t/t5100 as UTF-8 This enables gitk to show the patch text with correct glyphs if the locale is not UTF-8. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- t/t5100/.gitattributes | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 t/t5100/.gitattributes diff --git a/t/t5100/.gitattributes b/t/t5100/.gitattributes new file mode 100644 index 0000000000..c93f5142fa --- /dev/null +++ b/t/t5100/.gitattributes @@ -0,0 +1,4 @@ +msg* encoding=UTF-8 +info* encoding=UTF-8 +rfc2047-info-* encoding=UTF-8 +sample.mbox encoding=UTF-8 From 975457f185acd7c1f96ccff67cd5dc8dcb7908a0 Mon Sep 17 00:00:00 2001 From: Nasser Grainawi Date: Wed, 21 Oct 2009 14:06:21 -0700 Subject: [PATCH 136/884] Document `delta` attribute in "git help attributes". Signed-off-by: Junio C Hamano --- Documentation/gitattributes.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 1195e83b6e..1f472cea59 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -560,6 +560,16 @@ in the file. E.g. the string `$Format:%H$` will be replaced by the commit hash. +Packing objects +~~~~~~~~~~~~~~~ + +`delta` +^^^^^^^ + +Delta compression will not be attempted for blobs for paths with the +attribute `delta` set to false. + + Viewing files in GUI tools ~~~~~~~~~~~~~~~~~~~~~~~~~~ From d504f6975d34025ed3b5478b657789410b52cdb1 Mon Sep 17 00:00:00 2001 From: Clemens Buchacher Date: Wed, 21 Oct 2009 19:21:23 +0200 Subject: [PATCH 137/884] modernize fetch/merge/pull examples The "git pull" documentation has examples which follow an outdated style. Update the examples to use "git merge" where appropriate and move the examples to the corresponding manpages. Furthermore, - show that pull is equivalent to fetch and merge, which is still a frequently asked question, - explain the default fetch refspec. Signed-off-by: Clemens Buchacher Signed-off-by: Junio C Hamano --- Documentation/git-fetch.txt | 29 ++++++++++++++++++++++ Documentation/git-merge.txt | 33 +++++++++++++++++++++++++ Documentation/git-pull.txt | 49 +++---------------------------------- 3 files changed, 66 insertions(+), 45 deletions(-) diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index d3164c5c88..f2483d624e 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -37,6 +37,35 @@ include::pull-fetch-param.txt[] include::urls-remotes.txt[] + +EXAMPLES +-------- + +* Update the remote-tracking branches: ++ +------------------------------------------------ +$ git fetch origin +------------------------------------------------ ++ +The above command copies all branches from the remote refs/heads/ +namespace and stores them to the local refs/remotes/origin/ namespace, +unless the branch..fetch option is used to specify a non-default +refspec. + +* Using refspecs explicitly: ++ +------------------------------------------------ +$ git fetch origin +pu:pu maint:tmp +------------------------------------------------ ++ +This updates (or creates, as necessary) branches `pu` and `tmp` in +the local repository by fetching from the branches (respectively) +`pu` and `maint` from the remote repository. ++ +The `pu` branch will be updated even if it is does not fast-forward, +because it is prefixed with a plus sign; `tmp` will not be. + + SEE ALSO -------- linkgit:git-pull[1] diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index d05f324462..e886c2ef54 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -212,6 +212,39 @@ You can work through the conflict with a number of tools: common ancestor, 'git show :2:filename' shows the HEAD version and 'git show :3:filename' shows the remote version. + +EXAMPLES +-------- + +* Merge branches `fixes` and `enhancements` on top of + the current branch, making an octopus merge: ++ +------------------------------------------------ +$ git merge fixes enhancements +------------------------------------------------ + +* Merge branch `obsolete` into the current branch, using `ours` + merge strategy: ++ +------------------------------------------------ +$ git merge -s ours obsolete +------------------------------------------------ + +* Merge branch `maint` into the current branch, but do not make + a new commit automatically: ++ +------------------------------------------------ +$ git merge --no-commit maint +------------------------------------------------ ++ +This can be used when you want to include further changes to the +merge, or want to write your own merge commit message. ++ +You should refrain from abusing this option to sneak substantial +changes into a merge commit. Small fixups like bumping +release/version name would be acceptable. + + SEE ALSO -------- linkgit:git-fmt-merge-msg[1], linkgit:git-pull[1], diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 7578623edb..de2bcd63bb 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -131,54 +131,13 @@ $ git pull origin next ------------------------------------------------ + This leaves a copy of `next` temporarily in FETCH_HEAD, but -does not update any remote-tracking branches. - -* Bundle local branch `fixes` and `enhancements` on top of - the current branch, making an Octopus merge: +does not update any remote-tracking branches. Using remote-tracking +branches, the same can be done by invoking fetch and merge: + ------------------------------------------------ -$ git pull . fixes enhancements +$ git fetch origin +$ git merge origin/next ------------------------------------------------ -+ -This `git pull .` syntax is equivalent to `git merge`. - -* Merge local branch `obsolete` into the current branch, using `ours` - merge strategy: -+ ------------------------------------------------- -$ git pull -s ours . obsolete ------------------------------------------------- - -* Merge local branch `maint` into the current branch, but do not make - a commit automatically: -+ ------------------------------------------------- -$ git pull --no-commit . maint ------------------------------------------------- -+ -This can be used when you want to include further changes to the -merge, or want to write your own merge commit message. -+ -You should refrain from abusing this option to sneak substantial -changes into a merge commit. Small fixups like bumping -release/version name would be acceptable. - -* Command line pull of multiple branches from one repository: -+ ------------------------------------------------- -$ git checkout master -$ git fetch origin +pu:pu maint:tmp -$ git pull . tmp ------------------------------------------------- -+ -This updates (or creates, as necessary) branches `pu` and `tmp` in -the local repository by fetching from the branches (respectively) -`pu` and `maint` from the remote repository. -+ -The `pu` branch will be updated even if it is does not fast-forward; -the others will not be. -+ -The final command then merges the newly fetched `tmp` into master. If you tried a pull which resulted in a complex conflicts and From 6b276e19fa71fabe64039cf004aba908d7083e82 Mon Sep 17 00:00:00 2001 From: Jari Aalto Date: Wed, 21 Oct 2009 23:07:49 +0300 Subject: [PATCH 138/884] Documentation/fetch-options.txt: order options alphabetically git-fetch.{1,html} will be helped with this patch Signed-off-by: Jari Aalto Signed-off-by: Junio C Hamano --- Documentation/fetch-options.txt | 48 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 5eb2b0ee07..28868747da 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -1,25 +1,13 @@ -ifndef::git-pull[] --q:: ---quiet:: - Pass --quiet to git-fetch-pack and silence any other internally - used git commands. - --v:: ---verbose:: - Be verbose. -endif::git-pull[] - -a:: --append:: Append ref names and object names of fetched refs to the existing contents of `.git/FETCH_HEAD`. Without this option old data in `.git/FETCH_HEAD` will be overwritten. ---upload-pack :: - When given, and the repository to fetch from is handled - by 'git-fetch-pack', '--exec=' is passed to - the command to specify non-default path for the command - run on the other end. +--depth=:: + Deepen the history of a 'shallow' repository created by + `git clone` with `--depth=` option (see linkgit:git-clone[1]) + by the specified number of commits. -f:: --force:: @@ -29,6 +17,10 @@ endif::git-pull[] fetches is a descendant of ``. This option overrides that check. +-k:: +--keep:: + Keep downloaded pack. + ifdef::git-pull[] --no-tags:: endif::git-pull[] @@ -49,10 +41,6 @@ endif::git-pull[] flag lets all tags and their associated objects be downloaded. --k:: ---keep:: - Keep downloaded pack. - -u:: --update-head-ok:: By default 'git-fetch' refuses to update the head which @@ -62,7 +50,19 @@ endif::git-pull[] implementing your own Porcelain you are not supposed to use it. ---depth=:: - Deepen the history of a 'shallow' repository created by - `git clone` with `--depth=` option (see linkgit:git-clone[1]) - by the specified number of commits. +--upload-pack :: + When given, and the repository to fetch from is handled + by 'git-fetch-pack', '--exec=' is passed to + the command to specify non-default path for the command + run on the other end. + +ifndef::git-pull[] +-q:: +--quiet:: + Pass --quiet to git-fetch-pack and silence any other internally + used git commands. + +-v:: +--verbose:: + Be verbose. +endif::git-pull[] From 46148dd7ea41de10fc784c247924f73ddb21121b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Oct 2009 00:49:23 -0700 Subject: [PATCH 139/884] git checkout --no-guess Porcelains may want to make sure their calls to "git checkout" will reliably fail regardless of the presense of random remote tracking branches by the new DWIMmery introduced. Luckily all existing in-tree callers have extra checks to make sure they feed local branch name when they want to switch, or they explicitly ask to detach HEAD at the given commit, so there is no need to add this option for them. As this is strictly script-only option, do not even bother to document it, and do bother to hide it from "git checkout -h". Signed-off-by: Junio C Hamano --- builtin-checkout.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/builtin-checkout.c b/builtin-checkout.c index fb7e68ac5f..da04eed391 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -616,6 +616,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) struct tree *source_tree = NULL; char *conflict_style = NULL; int patch_mode = 0; + int dwim_new_local_branch = 1; struct option options[] = { OPT__QUIET(&opts.quiet), OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"), @@ -631,6 +632,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_STRING(0, "conflict", &conflict_style, "style", "conflict style (merge or diff3)"), OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), + { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL, + "second guess 'git checkout no-such-branch'", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_END(), }; int has_dash_dash; @@ -715,6 +719,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (has_dash_dash) /* case (1) */ die("invalid reference: %s", arg); if (!patch_mode && + dwim_new_local_branch && opts.track == BRANCH_TRACK_UNSPECIFIED && !opts.new_branch && !check_filename(NULL, arg) && From dad5f89fc568639c43bdbf344411757adf39d636 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Oct 2009 15:26:10 -0700 Subject: [PATCH 140/884] gc --auto --quiet: make the notice a bit less verboase When "gc --auto --quiet" decides there is something to do, it tells the user what it is doing, as it is going to make the user wait for a bit. But the message was a bit too long. Signed-off-by: Junio C Hamano --- builtin-gc.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/builtin-gc.c b/builtin-gc.c index 7d3e9cc7a0..093517e390 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -216,10 +216,13 @@ int cmd_gc(int argc, const char **argv, const char *prefix) */ if (!need_to_gc()) return 0; - fprintf(stderr, "Auto packing your repository for optimum " - "performance. You may also\n" - "run \"git gc\" manually. See " - "\"git help gc\" for more information.\n"); + fprintf(stderr, + "Auto packing the repository for optimum performance.%s\n", + quiet + ? "" + : (" You may also\n" + "run \"git gc\" manually. See " + "\"git help gc\" for more information.")); } else append_option(argv_repack, prune_expire && !strcmp(prune_expire, "now") From 77e3efbf43a1060049121350cc3ae8c208e77d57 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Oct 2009 14:56:40 -0700 Subject: [PATCH 141/884] receive-pack: run "gc --auto --quiet" and optionally "update-server-info" Introduce two new configuration variables, receive.autogc (defaults to true) and receive.updateserverinfo (defaults to false). When these are set, receive-pack runs "gc --auto --quiet" and "update-server-info" respectively after it finishes receiving data from "git push" and updating refs. Signed-off-by: Junio C Hamano Acked-by: Nicolas Pitre --- Documentation/config.txt | 9 +++++++++ builtin-receive-pack.c | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Documentation/config.txt b/Documentation/config.txt index cd1781498e..ba6ed1080f 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1320,6 +1320,11 @@ rebase.stat:: Whether to show a diffstat of what changed upstream since the last rebase. False by default. +receive.autogc:: + By default, git-receive-pack will run "git-gc --auto" after + receiving data from git-push and updating refs. You can stop + it by setting this variable to false. + receive.fsckObjects:: If it is set to true, git-receive-pack will check all received objects. It will abort in the case of a malformed object or a @@ -1355,6 +1360,10 @@ receive.denyNonFastForwards:: even if that push is forced. This configuration variable is set when initializing a shared repository. +receive.updateserverinfo:: + If set to true, git-receive-pack will run git-update-server-info + after receiving data from git-push and updating refs. + remote..url:: The URL of a remote repository. See linkgit:git-fetch[1] or linkgit:git-push[1]. diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index b771fe9b20..e8bde02c27 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -28,6 +28,8 @@ static int transfer_unpack_limit = -1; static int unpack_limit = 100; static int report_status; static int prefer_ofs_delta = 1; +static int auto_update_server_info; +static int auto_gc = 1; static const char *head_name; static char *capabilities_to_send; @@ -88,6 +90,16 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.updateserverinfo") == 0) { + auto_update_server_info = git_config_bool(var, value); + return 0; + } + + if (strcmp(var, "receive.autogc") == 0) { + auto_gc = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -672,6 +684,14 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) report(unpack_status); run_receive_hook(post_receive_hook); run_update_post_hook(commands); + if (auto_gc) { + const char *argv_gc_auto[] = { + "gc", "--auto", "--quiet", NULL, + }; + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + } + if (auto_update_server_info) + update_server_info(0); } return 0; } From 2a94552887ecbaa8b848a92eb607e032d77c8a2c Mon Sep 17 00:00:00 2001 From: Ingmar Vanhassel Date: Tue, 20 Oct 2009 12:29:32 +0200 Subject: [PATCH 142/884] import-tars: Add support for tarballs compressed with lzma, xz Also handle the extensions .tlz and .txz, aliases for .tar.lzma and .tar.xz respectively. Signed-off-by: Ingmar Vanhassel Liked-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- contrib/fast-import/import-tars.perl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl index 7001862bfd..95438e1ed4 100755 --- a/contrib/fast-import/import-tars.perl +++ b/contrib/fast-import/import-tars.perl @@ -20,7 +20,7 @@ use Getopt::Long; my $metaext = ''; -die "usage: import-tars [--metainfo=extension] *.tar.{gz,bz2,Z}\n" +die "usage: import-tars [--metainfo=extension] *.tar.{gz,bz2,lzma,xz,Z}\n" unless GetOptions('metainfo=s' => \$metaext) && @ARGV; my $branch_name = 'import-tars'; @@ -49,6 +49,9 @@ foreach my $tar_file (@ARGV) } elsif ($tar_name =~ s/\.tar\.Z$//) { open(I, '-|', 'uncompress', '-c', $tar_file) or die "Unable to uncompress -c $tar_file: $!\n"; + } elsif ($tar_name =~ s/\.(tar\.(lzma|xz)|(tlz|txz))$//) { + open(I, '-|', 'xz', '-dc', $tar_file) + or die "Unable to xz -dc $tar_file: $!\n"; } elsif ($tar_name =~ s/\.tar$//) { open(I, $tar_file) or die "Unable to open $tar_file: $!\n"; } else { From 550c66f3f9c8b2106ecca19c267c6e7e019e3cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Tue, 20 Oct 2009 22:38:38 +0200 Subject: [PATCH 143/884] git-clone.txt: Fix grammar and formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the missing definite article ("the") in several places. Change "note to..." to "note for...", since "note to" means that that the note is addressed to someone (source: Google search). Change "progressbar" to "progress bar" (source: Wikipedia). Format git commands, options, and file names consistently using back quotes (i.e. a fixed font in the resulting HTML document). Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano --- Documentation/git-clone.txt | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 5ebcba1c7c..7e7d9fcf50 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -39,7 +39,7 @@ OPTIONS --local:: -l:: When the repository to clone from is on a local machine, - this flag bypasses normal "git aware" transport + this flag bypasses the normal "git aware" transport mechanism and clones the repository by making a copy of HEAD and everything under objects and refs directories. The files under `.git/objects/` directory are hardlinked @@ -60,7 +60,7 @@ OPTIONS -s:: When the repository to clone is on the local machine, instead of using hard links, automatically setup - .git/objects/info/alternates to share the objects + `.git/objects/info/alternates` to share the objects with the source repository. The resulting repository starts out without any object of its own. + @@ -69,7 +69,7 @@ it unless you understand what it does. If you clone your repository using this option and then delete branches (or use any other git command that makes any existing commit unreferenced) in the source repository, some objects may become unreferenced (or dangling). -These objects may be removed by normal git operations (such as 'git-commit') +These objects may be removed by normal git operations (such as `git commit`) which automatically call `git gc --auto`. (See linkgit:git-gc[1].) If these objects are removed and were referenced by the cloned repository, then the cloned repository will become corrupt. @@ -86,13 +86,13 @@ objects from the source repository into a pack in the cloned repository. --reference :: If the reference repository is on the local machine, - automatically setup .git/objects/info/alternates to + automatically setup `.git/objects/info/alternates` to obtain objects from the reference repository. Using an already existing repository as an alternate will require fewer objects to be copied from the repository being cloned, reducing network and local storage costs. + -*NOTE*: see NOTE to --shared option. +*NOTE*: see the NOTE for the `--shared` option. --quiet:: -q:: @@ -101,7 +101,7 @@ objects from the source repository into a pack in the cloned repository. --verbose:: -v:: - Display the progressbar, even in case the standard output is not + Display the progress bar, even in case the standard output is not a terminal. --no-checkout:: @@ -121,17 +121,17 @@ objects from the source repository into a pack in the cloned repository. configuration variables are created. --mirror:: - Set up a mirror of the remote repository. This implies --bare. + Set up a mirror of the remote repository. This implies `--bare`. --origin :: -o :: - Instead of using the remote name 'origin' to keep track - of the upstream repository, use . + Instead of using the remote name `origin` to keep track + of the upstream repository, use ``. --branch :: -b :: Instead of pointing the newly created HEAD to the branch pointed - to by the cloned repository's HEAD, point to branch + to by the cloned repository's HEAD, point to `` branch instead. In a non-bare repository, this is the branch that will be checked out. @@ -158,7 +158,7 @@ objects from the source repository into a pack in the cloned repository. --recursive:: After the clone is created, initialize all submodules within, using their default settings. This is equivalent to running - 'git submodule update --init --recursive' immediately after + `git submodule update --init --recursive` immediately after the clone is finished. This option is ignored if the cloned repository does not have a worktree/checkout (i.e. if any of `--no-checkout`/`-n`, `--bare`, or `--mirror` is given) @@ -171,8 +171,8 @@ objects from the source repository into a pack in the cloned repository. :: The name of a new directory to clone into. The "humanish" part of the source repository is used if no directory is - explicitly given ("repo" for "/path/to/repo.git" and "foo" - for "host.xz:foo/.git"). Cloning into an existing directory + explicitly given (`repo` for `/path/to/repo.git` and `foo` + for `host.xz:foo/.git`). Cloning into an existing directory is only allowed if the directory is empty. :git-clone: 1 From a5ca8367c223b154b485ea51dc8c97201498caa4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 19 Oct 2009 22:06:28 -0500 Subject: [PATCH 144/884] blame: make sure that the last line ends in an LF This is convenient when parsing multiple the blame of multiple files, for example: git ls-files -z --exclude-standard -- "*.[ch]" | xargs --null -n 1 git blame -p > output and then analyzing the 'output' file using a seperate script. Currently the parsing is difficult when not all files have a newline at EOF, this patch ensures that even such files have a newline at the end of the blame output. Signed-off-by: Sverre Rabbelier CC: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-blame.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/builtin-blame.c b/builtin-blame.c index 7512773b40..dd16b22297 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1604,6 +1604,9 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) } while (ch != '\n' && cp < sb->final_buf + sb->final_buf_size); } + + if (sb->final_buf_size && cp[-1] != '\n') + putchar('\n'); } static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) @@ -1667,6 +1670,9 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) } while (ch != '\n' && cp < sb->final_buf + sb->final_buf_size); } + + if (sb->final_buf_size && cp[-1] != '\n') + putchar('\n'); } static void output(struct scoreboard *sb, int option) From 39eea7bdd943a103deadfd4c4fd204bbbf111219 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Oct 2009 23:06:14 -0700 Subject: [PATCH 145/884] Fix incorrect error check while reading deflated pack data The loop in get_size_from_delta() feeds a deflated delta data from the pack stream _until_ we get inflated result of 20 bytes[*] or we reach the end of stream. Side note. This magic number 20 does not have anything to do with the size of the hash we use, but comes from 1a3b55c (reduce delta head inflated size, 2006-10-18). The loop reads like this: do { in = use_pack(); stream.next_in = in; st = git_inflate(&stream, Z_FINISH); curpos += stream.next_in - in; } while ((st == Z_OK || st == Z_BUF_ERROR) && stream.total_out < sizeof(delta_head)); This git_inflate() can return: - Z_STREAM_END, if use_pack() fed it enough input and the delta itself was smaller than 20 bytes; - Z_OK, when some progress has been made; - Z_BUF_ERROR, if no progress is possible, because we either ran out of input (due to corrupt pack), or we ran out of output before we saw the end of the stream. The fix b3118bd (sha1_file: Fix infinite loop when pack is corrupted, 2009-10-14) attempted was against a corruption that appears to be a valid stream that produces a result larger than the output buffer, but we are not even trying to read the stream to the end in this loop. If avail_out becomes zero, total_out will be the same as sizeof(delta_head) so the loop will terminate without the "fix". There is no fix from b3118bd needed for this loop, in other words. The loop in unpack_compressed_entry() is quite a different story. It feeds a deflated stream (either delta or base) and allows the stream to produce output up to what we expect but no more. do { in = use_pack(); stream.next_in = in; st = git_inflate(&stream, Z_FINISH); curpos += stream.next_in - in; } while (st == Z_OK || st == Z_BUF_ERROR) This _does_ risk falling into an endless interation, as we can exhaust avail_out if the length we expect is smaller than what the stream wants to produce (due to pack corruption). In such a case, avail_out will become zero and inflate() will return Z_BUF_ERROR, while avail_in may (or may not) be zero. But this is not a right fix: do { in = use_pack(); stream.next_in = in; st = git_inflate(&stream, Z_FINISH); + if (st == Z_BUF_ERROR && (stream.avail_in || !stream.avail_out) + break; /* wants more input??? */ curpos += stream.next_in - in; } while (st == Z_OK || st == Z_BUF_ERROR) as Z_BUF_ERROR from inflate() may be telling us that avail_in has also run out before reading the end of stream marker. In such a case, both avail_in and avail_out would be zero, and the loop should iterate to allow the end of stream marker to be seen by inflate from the input stream. The right fix for this loop is likely to be to increment the initial avail_out by one (we allocate one extra byte to terminate it with NUL anyway, so there is no risk to overrun the buffer), and break out if we see that avail_out has become zero, in order to detect that the stream wants to produce more than what we expect. After the loop, we have a check that exactly tests this condition: if ((st != Z_STREAM_END) || stream.total_out != size) { free(buffer); return NULL; } So here is a patch (without my previous botched attempts) to fix this issue. The first hunk reverts the corresponding hunk from b3118bd, and the second hunk is the same fix proposed earlier. Signed-off-by: Junio C Hamano --- sha1_file.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sha1_file.c b/sha1_file.c index 4cc8939e4b..63981fb3fd 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1357,8 +1357,6 @@ unsigned long get_size_from_delta(struct packed_git *p, in = use_pack(p, w_curs, curpos, &stream.avail_in); stream.next_in = in; st = git_inflate(&stream, Z_FINISH); - if (st == Z_BUF_ERROR && (stream.avail_in || !stream.avail_out)) - break; curpos += stream.next_in - in; } while ((st == Z_OK || st == Z_BUF_ERROR) && stream.total_out < sizeof(delta_head)); @@ -1589,15 +1587,15 @@ static void *unpack_compressed_entry(struct packed_git *p, buffer[size] = 0; memset(&stream, 0, sizeof(stream)); stream.next_out = buffer; - stream.avail_out = size; + stream.avail_out = size + 1; git_inflate_init(&stream); do { in = use_pack(p, w_curs, curpos, &stream.avail_in); stream.next_in = in; st = git_inflate(&stream, Z_FINISH); - if (st == Z_BUF_ERROR && (stream.avail_in || !stream.avail_out)) - break; + if (!stream.avail_out) + break; /* the payload is larger than it should be */ curpos += stream.next_in - in; } while (st == Z_OK || st == Z_BUF_ERROR); git_inflate_end(&stream); From 3f7a9b5ad128151e6eb0b45266779e63e4b2287a Mon Sep 17 00:00:00 2001 From: Jari Aalto Date: Thu, 22 Oct 2009 17:14:57 +0300 Subject: [PATCH 146/884] Documentation/git-pull.txt: Add subtitles above included option files Signed-off-by: Jari Aalto Signed-off-by: Junio C Hamano --- Documentation/git-pull.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 7578623edb..51534dd848 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -26,6 +26,10 @@ Also note that options meant for 'git-pull' itself and underlying OPTIONS ------- + +Options related to merging +~~~~~~~~~~~~~~~~~~~~~~~~~~ + include::merge-options.txt[] :git-pull: 1 @@ -47,6 +51,9 @@ unless you have read linkgit:git-rebase[1] carefully. --no-rebase:: Override earlier --rebase. +Options related to fetching +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + include::fetch-options.txt[] include::pull-fetch-param.txt[] From 7c85d27429784c181ca633fb1465a9c651dbce8a Mon Sep 17 00:00:00 2001 From: Jari Aalto Date: Thu, 22 Oct 2009 22:25:20 +0300 Subject: [PATCH 147/884] Documentation/merge-options.txt: order options in alphabetical groups Signed-off-by: Jari Aalto Signed-off-by: Junio C Hamano --- Documentation/merge-options.txt | 88 +++++++++++++++++---------------- 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index adadf8e4bf..48d04a5d88 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -1,43 +1,42 @@ --q:: ---quiet:: - Operate quietly. +--commit:: +--no-commit:: + Perform the merge and commit the result. This option can + be used to override --no-commit. ++ +With --no-commit perform the merge but pretend the merge +failed and do not autocommit, to give the user a chance to +inspect and further tweak the merge result before committing. --v:: ---verbose:: - Be verbose. - ---stat:: - Show a diffstat at the end of the merge. The diffstat is also - controlled by the configuration option merge.stat. - --n:: ---no-stat:: - Do not show a diffstat at the end of the merge. - ---summary:: ---no-summary:: - Synonyms to --stat and --no-stat; these are deprecated and will be - removed in the future. +--ff:: +--no-ff:: + Do not generate a merge commit if the merge resolved as + a fast-forward, only update the branch pointer. This is + the default behavior of git-merge. ++ +With --no-ff Generate a merge commit even if the merge +resolved as a fast-forward. --log:: +--no-log:: In addition to branch names, populate the log message with one-line descriptions from the actual commits that are being merged. ++ +With --no-log do not list one-line descriptions from the +actual commits being merged. ---no-log:: - Do not list one-line descriptions from the actual commits being - merged. ---no-commit:: - Perform the merge but pretend the merge failed and do - not autocommit, to give the user a chance to inspect and - further tweak the merge result before committing. - ---commit:: - Perform the merge and commit the result. This option can - be used to override --no-commit. +--stat:: +-n:: +--no-stat:: + Show a diffstat at the end of the merge. The diffstat is also + controlled by the configuration option merge.stat. ++ +With -n or --no-stat do not show a diffstat at the end of the +merge. --squash:: +--no-squash:: Produce the working tree and index state as if a real merge happened (except for the merge information), but do not actually make a commit or @@ -46,19 +45,9 @@ commit. This allows you to create a single commit on top of the current branch whose effect is the same as merging another branch (or more in case of an octopus). - ---no-squash:: - Perform the merge and commit the result. This option can - be used to override --squash. - ---no-ff:: - Generate a merge commit even if the merge resolved as a - fast-forward. - ---ff:: - Do not generate a merge commit if the merge resolved as - a fast-forward, only update the branch pointer. This is - the default behavior of git-merge. ++ +With --no-squash perform the merge and commit the result. This +option can be used to override --squash. -s :: --strategy=:: @@ -67,3 +56,16 @@ If there is no `-s` option, a built-in list of strategies is used instead ('git-merge-recursive' when merging a single head, 'git-merge-octopus' otherwise). + +--summary:: +--no-summary:: + Synonyms to --stat and --no-stat; these are deprecated and will be + removed in the future. + +-q:: +--quiet:: + Operate quietly. + +-v:: +--verbose:: + Be verbose. From 204d363f5a05bba0bdeb13f96a08d5078dcee820 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Thu, 22 Oct 2009 10:19:06 +0200 Subject: [PATCH 148/884] Quote ' as \(aq in manpages The docbook/xmlto toolchain insists on quoting ' as \'. This does achieve the quoting goal, but modern 'man' implementations turn the apostrophe into a unicode "proper" apostrophe (given the right circumstances), breaking code examples in many of our manpages. Quote them as \(aq instead, which is an "apostrophe quote" as per the groff_char manpage. Unfortunately, as Anders Kaseorg kindly pointed out, this is not portable beyond groff, so we add an extra Makefile variable GNU_ROFF which you need to enable to get the new quoting. Thanks also to Miklos Vajna for documentation. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- Documentation/Makefile | 8 ++++++++ Documentation/manpage-quote-apos.xsl | 16 ++++++++++++++++ Makefile | 4 ++++ 3 files changed, 28 insertions(+) create mode 100644 Documentation/manpage-quote-apos.xsl diff --git a/Documentation/Makefile b/Documentation/Makefile index 7a8037f586..7614844efa 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -103,6 +103,14 @@ ifdef DOCBOOK_SUPPRESS_SP XMLTO_EXTRA += -m manpage-suppress-sp.xsl endif +# If your target system uses GNU groff, it may try to render +# apostrophes as a "pretty" apostrophe using unicode. This breaks +# cut&paste, so you should set GNU_ROFF to force them to be ASCII +# apostrophes. Unfortunately does not work with non-GNU roff. +ifdef GNU_ROFF +XMLTO_EXTRA += -m manpage-quote-apos.xsl +endif + SHELL_PATH ?= $(SHELL) # Shell quote; SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) diff --git a/Documentation/manpage-quote-apos.xsl b/Documentation/manpage-quote-apos.xsl new file mode 100644 index 0000000000..aeb8839f33 --- /dev/null +++ b/Documentation/manpage-quote-apos.xsl @@ -0,0 +1,16 @@ + + + + + + + + ' + \(aq + + + + diff --git a/Makefile b/Makefile index f88ed3e14d..7974717419 100644 --- a/Makefile +++ b/Makefile @@ -142,6 +142,10 @@ all:: # # Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72. # +# Define GNU_ROFF if your target system uses GNU groff. This forces +# apostrophes to be ASCII so that cut&pasting examples to the shell +# will work. +# # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's # MakeMaker (e.g. using ActiveState under Cygwin). # From 00d3947366a50a06da40989a1fd1e3f99885a4c3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Oct 2009 23:40:35 -0700 Subject: [PATCH 149/884] Teach --wrap to only indent without wrapping When a zero or negative width is given to "shortlog -w,," and --format=%[wrap(w,in1,in2)...%], just indent the text by in1 without wrapping. Signed-off-by: Junio C Hamano --- utf8.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/utf8.c b/utf8.c index da996695cc..5c18f0c281 100644 --- a/utf8.c +++ b/utf8.c @@ -310,6 +310,19 @@ int strbuf_add_wrapped_text(struct strbuf *buf, int w = indent, assume_utf8 = is_utf8(text); const char *bol = text, *space = NULL; + if (width <= 0) { + /* just indent */ + while (*text) { + const char *eol = strchrnul(text, '\n'); + if (*eol == '\n') + eol++; + print_spaces(buf, indent); + strbuf_write(buf, text, eol-text); + text = eol; + } + return 1; + } + if (indent < 0) { w = -indent; space = text; From 02edd56b84f00c1a88c5602f5608033d4bc1cbff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 17 Oct 2009 23:04:19 +0200 Subject: [PATCH 150/884] Implement wrap format %w() as if it is a mode switch I always considered line wrapping to be more similar to a colour, i.e. a state that one can change and that is applied to all following text until the next state change, except that it's always reset at the end of the format string. Here's a patch to implement this behaviour, using Dscho's strbuf_add_wrapped_text() Signed-off-by: Junio C Hamano --- pretty.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/pretty.c b/pretty.c index 587101f846..91be0ce0a0 100644 --- a/pretty.c +++ b/pretty.c @@ -445,6 +445,7 @@ struct format_commit_context { enum date_mode dmode; unsigned commit_header_parsed:1; unsigned commit_message_parsed:1; + size_t width, indent1, indent2; /* These offsets are relative to the start of the commit message. */ struct chunk author; @@ -458,6 +459,7 @@ struct format_commit_context { struct chunk abbrev_commit_hash; struct chunk abbrev_tree_hash; struct chunk abbrev_parent_hashes; + size_t wrap_start; }; static int add_again(struct strbuf *sb, struct chunk *chunk) @@ -595,6 +597,35 @@ static void format_decoration(struct strbuf *sb, const struct commit *commit) strbuf_addch(sb, ')'); } +static void strbuf_wrap(struct strbuf *sb, size_t pos, + size_t width, size_t indent1, size_t indent2) +{ + struct strbuf tmp = STRBUF_INIT; + + if (pos) + strbuf_add(&tmp, sb->buf, pos); + strbuf_add_wrapped_text(&tmp, sb->buf + pos, + (int) indent1, (int) indent2, (int) width); + strbuf_swap(&tmp, sb); + strbuf_release(&tmp); +} + +static void rewrap_message_tail(struct strbuf *sb, + struct format_commit_context *c, + size_t new_width, size_t new_indent1, + size_t new_indent2) +{ + if (c->width == new_width && c->indent1 == new_indent1 && + c->indent2 == new_indent2) + return; + if (c->wrap_start && c->wrap_start < sb->len) + strbuf_wrap(sb, c->wrap_start, c->width, c->indent1, c->indent2); + c->wrap_start = sb->len; + c->width = new_width; + c->indent1 = new_indent1; + c->indent2 = new_indent2; +} + static size_t format_commit_item(struct strbuf *sb, const char *placeholder, void *context) { @@ -645,6 +676,30 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, return 3; } else return 0; + case 'w': + if (placeholder[1] == '(') { + unsigned long width = 0, indent1 = 0, indent2 = 0; + char *next; + const char *start = placeholder + 2; + const char *end = strchr(start, ')'); + if (!end) + return 0; + if (end > start) { + width = strtoul(start, &next, 10); + if (*next == ',') { + indent1 = strtoul(next + 1, &next, 10); + if (*next == ',') { + indent2 = strtoul(next + 1, + &next, 10); + } + } + if (*next != ')') + return 0; + } + rewrap_message_tail(sb, c, width, indent1, indent2); + return end - placeholder + 1; + } else + return 0; } /* these depend on the commit */ @@ -748,7 +803,9 @@ void format_commit_message(const struct commit *commit, memset(&context, 0, sizeof(context)); context.commit = commit; context.dmode = dmode; + context.wrap_start = sb->len; strbuf_expand(sb, format, format_commit_item, &context); + rewrap_message_tail(sb, &context, 0, 0, 0); } static void pp_header(enum cmit_fmt fmt, From 4192e1cd02365c63f1155fc547e4ef2163050c59 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Wed, 21 Oct 2009 19:04:50 +0200 Subject: [PATCH 151/884] mingw: enable OpenSSL Since we have OpenSSL in msysgit now, enable it to support SSL encryption for imap-send. Signed-off-by: Erik Faye-Lund Acked-by: Johannes Sixt Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 13980a52b5..6a197f3272 100644 --- a/Makefile +++ b/Makefile @@ -933,7 +933,7 @@ else ifneq (,$(findstring MINGW,$(uname_S))) pathsep = ; NO_PREAD = YesPlease - NO_OPENSSL = YesPlease + NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease NO_SYMLINK_HEAD = YesPlease NO_IPV6 = YesPlease From c36e16385be363a11abaa4a43edd5a2b2b2dff81 Mon Sep 17 00:00:00 2001 From: Marius Storm-Olsen Date: Wed, 21 Oct 2009 19:04:51 +0200 Subject: [PATCH 152/884] MSVC: Enable OpenSSL, and translate -lcrypto We don't use crypto, but rather require libeay32 and ssleay32. handle it in both the Makefile msvc linker script, and the buildsystem generator. Signed-off-by: Marius Storm-Olsen Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano --- Makefile | 2 +- compat/vcbuild/scripts/clink.pl | 3 +++ contrib/buildsystems/engine.pl | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6a197f3272..5403fad3e8 100644 --- a/Makefile +++ b/Makefile @@ -881,7 +881,7 @@ ifdef MSVC GIT_VERSION := $(GIT_VERSION).MSVC pathsep = ; NO_PREAD = YesPlease - NO_OPENSSL = YesPlease + NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease NO_SYMLINK_HEAD = YesPlease NO_IPV6 = YesPlease diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl index 0ffd59f9fb..fce1e2423e 100644 --- a/compat/vcbuild/scripts/clink.pl +++ b/compat/vcbuild/scripts/clink.pl @@ -29,6 +29,9 @@ while (@ARGV) { push(@args, "zlib.lib"); } elsif ("$arg" eq "-liconv") { push(@args, "iconv.lib"); + } elsif ("$arg" eq "-lcrypto") { + push(@args, "libeay32.lib"); + push(@args, "ssleay32.lib"); } elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") { $arg =~ s/^-L/-LIBPATH:/; push(@args, $arg); diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl index 20bd061b3e..d506717bfd 100644 --- a/contrib/buildsystems/engine.pl +++ b/contrib/buildsystems/engine.pl @@ -315,6 +315,9 @@ sub handleLinkLine $appout = shift @parts; } elsif ("$part" eq "-lz") { push(@libs, "zlib.lib"); + } elsif ("$part" eq "-lcrypto") { + push(@libs, "libeay32.lib"); + push(@libs, "ssleay32.lib"); } elsif ($part =~ /^-/) { push(@lflags, $part); } elsif ($part =~ /\.(a|lib)$/) { From 9bccfcdbff3b11e11a69d05c50c8bc1a9a6dbb5f Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Thu, 22 Oct 2009 20:26:29 +0200 Subject: [PATCH 153/884] Windows: use BLK_SHA1 again Since NO_OPENSSL is no longer defined on Windows, BLK_SHA1 is not defined anymore implicitly. Define it explicitly. As a nice side-effect, we no longer link against libcrypto.dll, which has non-trivial startup costs because it depends on 6 otherwise unneeded DLLs. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 5403fad3e8..f666d2f1b7 100644 --- a/Makefile +++ b/Makefile @@ -911,6 +911,7 @@ ifdef MSVC NO_REGEX = YesPlease NO_CURL = YesPlease NO_PTHREADS = YesPlease + BLK_SHA1 = YesPlease CC = compat/vcbuild/scripts/clink.pl AR = compat/vcbuild/scripts/lib.pl @@ -960,6 +961,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) UNRELIABLE_FSTAT = UnfortunatelyYes OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo NO_REGEX = YesPlease + BLK_SHA1 = YesPlease COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o From 024ab976fffdc1d78b6d0b15e261b52f3a5d594b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 23 Oct 2009 11:42:39 -0700 Subject: [PATCH 154/884] Do not fail "describe --always" in a tag-less repository This fixes a regression introduce by d68dc34 (git-describe: Die early if there are no possible descriptions, 2009-08-06). Signed-off-by: Junio C Hamano --- builtin-describe.c | 2 +- t/t6120-describe.sh | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/builtin-describe.c b/builtin-describe.c index df67a733ae..7542b5705c 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -197,7 +197,7 @@ static void describe(const char *arg, int last_one) for_each_ref(get_name, NULL); } - if (!found_names) + if (!found_names && !always) die("cannot describe '%s'", sha1_to_hex(sha1)); n = cmit->util; diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 8c7e081c53..f5a1b615f6 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -34,6 +34,8 @@ test_expect_success setup ' echo one >file && git add file && git commit -m initial && one=$(git rev-parse HEAD) && + git describe --always HEAD && + test_tick && echo two >file && git add file && git commit -m second && two=$(git rev-parse HEAD) && From 86140d56c150aa17e6c98dde4ba669516f3c302a Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Fri, 23 Oct 2009 13:25:33 +0200 Subject: [PATCH 155/884] add tests for git diff --submodule Copied from the submodule summary test and changed to reflect the differences in the output of git diff --submodule. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- t/t4041-diff-submodule.sh | 260 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100755 t/t4041-diff-submodule.sh diff --git a/t/t4041-diff-submodule.sh b/t/t4041-diff-submodule.sh new file mode 100755 index 0000000000..5bb4fed3f5 --- /dev/null +++ b/t/t4041-diff-submodule.sh @@ -0,0 +1,260 @@ +#!/bin/sh +# +# Copyright (c) 2009 Jens Lehmann, based on t7401 by Ping Yin +# + +test_description='Support for verbose submodule differences in git diff + +This test tries to verify the sanity of the --submodule option of git diff. +' + +. ./test-lib.sh + +add_file () { + sm=$1 + shift + owd=$(pwd) + cd "$sm" + for name; do + echo "$name" > "$name" && + git add "$name" && + test_tick && + git commit -m "Add $name" + done >/dev/null + git rev-parse --verify HEAD | cut -c1-7 + cd "$owd" +} +commit_file () { + test_tick && + git commit "$@" -m "Commit $*" >/dev/null +} + +test_create_repo sm1 && +add_file . foo >/dev/null + +head1=$(add_file sm1 foo1 foo2) + +test_expect_success 'added submodule' " + git add sm1 && + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 0000000...$head1 (new submodule) +EOF +" + +commit_file sm1 && +head2=$(add_file sm1 foo3) + +test_expect_success 'modified submodule(forward)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head1..$head2: + > Add foo3 +EOF +" + +test_expect_success 'modified submodule(forward)' " + git diff --submodule=log >actual && + diff actual - <<-EOF +Submodule sm1 $head1..$head2: + > Add foo3 +EOF +" + +test_expect_success 'modified submodule(forward) --submodule' " + git diff --submodule >actual && + diff actual - <<-EOF +Submodule sm1 $head1..$head2: + > Add foo3 +EOF +" + +fullhead1=$(cd sm1; git rev-list --max-count=1 $head1) +fullhead2=$(cd sm1; git rev-list --max-count=1 $head2) +test_expect_success 'modified submodule(forward) --submodule=short' " + git diff --submodule=short >actual && + diff actual - <<-EOF +diff --git a/sm1 b/sm1 +index $head1..$head2 160000 +--- a/sm1 ++++ b/sm1 +@@ -1 +1 @@ +-Subproject commit $fullhead1 ++Subproject commit $fullhead2 +EOF +" + +commit_file sm1 && +cd sm1 && +git reset --hard HEAD~2 >/dev/null && +head3=$(git rev-parse --verify HEAD | cut -c1-7) && +cd .. + +test_expect_success 'modified submodule(backward)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head2..$head3 (rewind): + < Add foo3 + < Add foo2 +EOF +" + +head4=$(add_file sm1 foo4 foo5) && +head4_full=$(GIT_DIR=sm1/.git git rev-parse --verify HEAD) +test_expect_success 'modified submodule(backward and forward)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head2...$head4: + > Add foo5 + > Add foo4 + < Add foo3 + < Add foo2 +EOF +" + +commit_file sm1 && +mv sm1 sm1-bak && +echo sm1 >sm1 && +head5=$(git hash-object sm1 | cut -c1-7) && +git add sm1 && +rm -f sm1 && +mv sm1-bak sm1 + +test_expect_success 'typechanged submodule(submodule->blob), --cached' " + git diff --submodule=log --cached >actual && + diff actual - <<-EOF +Submodule sm1 41fbea9...0000000 (submodule deleted) +diff --git a/sm1 b/sm1 +new file mode 100644 +index 0000000..9da5fb8 +--- /dev/null ++++ b/sm1 +@@ -0,0 +1 @@ ++sm1 +EOF +" + +test_expect_success 'typechanged submodule(submodule->blob)' " + git diff --submodule=log >actual && + diff actual - <<-EOF +diff --git a/sm1 b/sm1 +deleted file mode 100644 +index 9da5fb8..0000000 +--- a/sm1 ++++ /dev/null +@@ -1 +0,0 @@ +-sm1 +Submodule sm1 0000000...$head4 (new submodule) +EOF +" + +rm -rf sm1 && +git checkout-index sm1 +test_expect_success 'typechanged submodule(submodule->blob)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head4...0000000 (submodule deleted) +diff --git a/sm1 b/sm1 +new file mode 100644 +index 0000000..$head5 +--- /dev/null ++++ b/sm1 +@@ -0,0 +1 @@ ++sm1 +EOF +" + +rm -f sm1 && +test_create_repo sm1 && +head6=$(add_file sm1 foo6 foo7) +fullhead6=$(cd sm1; git rev-list --max-count=1 $head6) +test_expect_success 'nonexistent commit' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head4...$head6 (commits not present) +EOF +" + +commit_file +test_expect_success 'typechanged submodule(blob->submodule)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +diff --git a/sm1 b/sm1 +deleted file mode 100644 +index $head5..0000000 +--- a/sm1 ++++ /dev/null +@@ -1 +0,0 @@ +-sm1 +Submodule sm1 0000000...$head6 (new submodule) +EOF +" + +commit_file sm1 && +rm -rf sm1 +test_expect_success 'deleted submodule' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6...0000000 (submodule deleted) +EOF +" + +test_create_repo sm2 && +head7=$(add_file sm2 foo8 foo9) && +git add sm2 + +test_expect_success 'multiple submodules' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6...0000000 (submodule deleted) +Submodule sm2 0000000...$head7 (new submodule) +EOF +" + +test_expect_success 'path filter' " + git diff-index -p --submodule=log HEAD sm2 >actual && + diff actual - <<-EOF +Submodule sm2 0000000...$head7 (new submodule) +EOF +" + +commit_file sm2 +test_expect_success 'given commit' " + git diff-index -p --submodule=log HEAD^ >actual && + diff actual - <<-EOF +Submodule sm1 $head6...0000000 (submodule deleted) +Submodule sm2 0000000...$head7 (new submodule) +EOF +" + +test_expect_success 'given commit --submodule' " + git diff-index -p --submodule HEAD^ >actual && + diff actual - <<-EOF +Submodule sm1 $head6...0000000 (submodule deleted) +Submodule sm2 0000000...$head7 (new submodule) +EOF +" + +fullhead7=$(cd sm2; git rev-list --max-count=1 $head7) + +test_expect_success 'given commit --submodule=short' " + git diff-index -p --submodule=short HEAD^ >actual && + diff actual - <<-EOF +diff --git a/sm1 b/sm1 +deleted file mode 160000 +index $head6..0000000 +--- a/sm1 ++++ /dev/null +@@ -1 +0,0 @@ +-Subproject commit $fullhead6 +diff --git a/sm2 b/sm2 +new file mode 160000 +index 0000000..$head7 +--- /dev/null ++++ b/sm2 +@@ -0,0 +1 @@ ++Subproject commit $fullhead7 +EOF +" + +test_done From 2cf6b4bfec068cf24bdc3e4d46ad233091471a02 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 23 Oct 2009 22:38:44 -0700 Subject: [PATCH 156/884] Fix list of released versions in the toc document --- Documentation/git.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git.txt b/Documentation/git.txt index d11c5c1651..8519928d45 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,7 +43,7 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.5/git.html[documentation for release 1.6.5] +* link:v1.6.5.1/git.html[documentation for release 1.6.5.1] * release notes for link:RelNotes-1.6.5.1.txt[1.6.5.1], From 36e561064e83d3f890f8fa128b3ca4e0d7691f10 Mon Sep 17 00:00:00 2001 From: Andreas Schwab Date: Sat, 24 Oct 2009 15:06:57 +0200 Subject: [PATCH 157/884] Work around option parsing bug in the busybox tar implementation The first argument of the tar command is interpreted as a bundle of letters specifying the mode of operation and additional options, with any option arguments taken from subsequent words on the command line as needed. The implementation of tar in busybox treats this bundle as if preceded by a dash and then parses it by getopt rules, which mishandles 'tar xfo -'. Use 'tar xof -' instead to work this around. Signed-off-by: Andreas Schwab Signed-off-by: Junio C Hamano --- templates/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/Makefile b/templates/Makefile index a12c6e214e..408f0137a8 100644 --- a/templates/Makefile +++ b/templates/Makefile @@ -50,4 +50,4 @@ clean: install: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(template_instdir_SQ)' (cd blt && $(TAR) cf - .) | \ - (cd '$(DESTDIR_SQ)$(template_instdir_SQ)' && umask 022 && $(TAR) xfo -) + (cd '$(DESTDIR_SQ)$(template_instdir_SQ)' && umask 022 && $(TAR) xof -) From 3319df6f3aa3271124847e2c91c7e6d0be961bc6 Mon Sep 17 00:00:00 2001 From: Markus Heidelberg Date: Sun, 25 Oct 2009 01:39:19 +0200 Subject: [PATCH 158/884] t7800-difftool: fix the effectless GIT_DIFFTOOL_PROMPT test GIT_DIFFTOOL_PROMPT doesn't have any effect if overridden with --prompt. Signed-off-by: Markus Heidelberg Signed-off-by: Junio C Hamano --- t/t7800-difftool.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index ebdccf9a1e..fff6a6d0ea 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -136,7 +136,7 @@ test_expect_success 'GIT_DIFFTOOL_PROMPT variable' ' GIT_DIFFTOOL_PROMPT=true && export GIT_DIFFTOOL_PROMPT && - prompt=$(echo | git difftool --prompt branch | tail -1) && + prompt=$(echo | git difftool branch | tail -1) && prompt_given "$prompt" && restore_test_defaults From a75d7b54097ef0d0945cbe673a9940d6c561f95c Mon Sep 17 00:00:00 2001 From: Felipe Contreras Date: Sat, 24 Oct 2009 11:31:32 +0300 Subject: [PATCH 159/884] Use 'fast-forward' all over the place It's a compound word. Signed-off-by: Felipe Contreras Signed-off-by: Junio C Hamano --- Documentation/config.txt | 2 +- Documentation/git-http-push.txt | 4 ++-- Documentation/git-push.txt | 14 +++++++------- Documentation/git-read-tree.txt | 2 +- Documentation/git-receive-pack.txt | 2 +- Documentation/git-reset.txt | 4 ++-- Documentation/git-send-pack.txt | 4 ++-- Documentation/gitcore-tutorial.txt | 4 ++-- Documentation/githooks.txt | 2 +- Documentation/glossary-content.txt | 4 ++-- Documentation/howto/maintain-git.txt | 2 +- Documentation/howto/revert-branch-rebase.txt | 4 ++-- Documentation/howto/update-hook-example.txt | 2 +- Documentation/pull-fetch-param.txt | 4 ++-- Documentation/user-manual.txt | 14 +++++++------- builtin-fetch--tool.c | 8 ++++---- builtin-fetch.c | 4 ++-- builtin-merge.c | 8 ++++---- builtin-push.c | 2 +- builtin-receive-pack.c | 4 ++-- builtin-remote.c | 2 +- builtin-send-pack.c | 2 +- contrib/examples/git-merge.sh | 8 ++++---- contrib/examples/git-resolve.sh | 2 +- contrib/hooks/post-receive-email | 6 +++--- git-merge-octopus.sh | 2 +- git-pull.sh | 2 +- git-rebase--interactive.sh | 6 +++--- git-rebase.sh | 2 +- t/t1001-read-tree-m-2way.sh | 6 +++--- t/t5505-remote.sh | 2 +- t/t5518-fetch-exit-status.sh | 2 +- t/t6028-merge-up-to-date.sh | 2 +- transport.c | 2 +- unpack-trees.c | 2 +- 35 files changed, 71 insertions(+), 71 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index cd1781498e..52bbafbefa 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1351,7 +1351,7 @@ receive.denyCurrentBranch:: receive.denyNonFastForwards:: If set to true, git-receive-pack will deny a ref update which is - not a fast forward. Use this to prevent such an update via a push, + not a fast-forward. Use this to prevent such an update via a push, even if that push is forced. This configuration variable is set when initializing a shared repository. diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt index aef383e0b1..ddf7a18dc4 100644 --- a/Documentation/git-http-push.txt +++ b/Documentation/git-http-push.txt @@ -82,11 +82,11 @@ destination side. Without '--force', the ref is stored at the remote only if does not exist, or is a proper subset (i.e. an -ancestor) of . This check, known as "fast forward check", +ancestor) of . This check, known as "fast-forward check", is performed in order to avoid accidentally overwriting the remote ref and lose other peoples' commits from there. -With '--force', the fast forward check is disabled for all refs. +With '--force', the fast-forward check is disabled for all refs. Optionally, a parameter can be prefixed with a plus '+' sign to disable the fast-forward check only on that ref. diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index ba6a8a2fb2..1a9d886bb3 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -50,9 +50,9 @@ updated. + The object referenced by is used to update the reference on the remote side, but by default this is only allowed if the -update can fast forward . By having the optional leading `{plus}`, +update can fast-forward . By having the optional leading `{plus}`, you can tell git to update the ref even when the update is not a -fast forward. This does *not* attempt to merge into . See +fast-forward. This does *not* attempt to merge into . See EXAMPLES below for details. + `tag ` means the same as `refs/tags/:refs/tags/`. @@ -60,7 +60,7 @@ EXAMPLES below for details. Pushing an empty allows you to delete the ref from the remote repository. + -The special refspec `:` (or `{plus}:` to allow non-fast forward updates) +The special refspec `:` (or `{plus}:` to allow non-fast-forward updates) directs git to push "matching" branches: for every branch that exists on the local side, the remote side is updated if a branch of the same name already exists on the remote side. This is the default operation mode @@ -171,10 +171,10 @@ summary:: For a successfully pushed ref, the summary shows the old and new values of the ref in a form suitable for using as an argument to `git log` (this is `..` in most cases, and - `...` for forced non-fast forward updates). For a + `...` for forced non-fast-forward updates). For a failed update, more details are given for the failure. The string `rejected` indicates that git did not try to send the - ref at all (typically because it is not a fast forward). The + ref at all (typically because it is not a fast-forward). The string `remote rejected` indicates that the remote end refused the update; this rejection is typically caused by a hook on the remote side. The string `remote failure` indicates that the @@ -342,9 +342,9 @@ git push origin :experimental:: git push origin {plus}dev:master:: Update the origin repository's master branch with the dev branch, - allowing non-fast forward updates. *This can leave unreferenced + allowing non-fast-forward updates. *This can leave unreferenced commits dangling in the origin repository.* Consider the - following situation, where a fast forward is not possible: + following situation, where a fast-forward is not possible: + ---- o---o---o---A---B origin/master diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 4a932b08c6..a10ce4ba40 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -144,7 +144,7 @@ Two Tree Merge Typically, this is invoked as `git read-tree -m $H $M`, where $H is the head commit of the current repository, and $M is the head of a foreign tree, which is simply ahead of $H (i.e. we are in a -fast forward situation). +fast-forward situation). When two trees are specified, the user is telling 'git-read-tree' the following: diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index 514f03c979..cb5f405280 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -20,7 +20,7 @@ The UI for the protocol is on the 'git-send-pack' side, and the program pair is meant to be used to push updates to remote repository. For pull operations, see linkgit:git-fetch-pack[1]. -The command allows for creation and fast forwarding of sha1 refs +The command allows for creation and fast-forwarding of sha1 refs (heads/tags) on the remote end (strictly speaking, it is the local end 'git-receive-pack' runs, but to the user who is sitting at the send-pack end, it is updating the remote. Confused?) diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 469cf6dbac..2d27e405a3 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -150,7 +150,7 @@ Automatic merge failed; fix conflicts and then commit the result. $ git reset --hard <2> $ git pull . topic/branch <3> Updating from 41223... to 13134... -Fast forward +Fast-forward $ git reset --hard ORIG_HEAD <4> ------------ + @@ -161,7 +161,7 @@ right now, so you decide to do that later. which is a synonym for "git reset --hard HEAD" clears the mess from the index file and the working tree. <3> Merge a topic branch into the current branch, which resulted -in a fast forward. +in a fast-forward. <4> But you decided that the topic branch is not ready for public consumption yet. "pull" or "merge" always leaves the original tip of the current branch in ORIG_HEAD, so resetting hard to it diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index 399821832c..5a04c6eaf7 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -105,11 +105,11 @@ name. See linkgit:git-rev-parse[1]. Without '--force', the ref is stored at the remote only if does not exist, or is a proper subset (i.e. an -ancestor) of . This check, known as "fast forward check", +ancestor) of . This check, known as "fast-forward check", is performed in order to avoid accidentally overwriting the remote ref and lose other peoples' commits from there. -With '--force', the fast forward check is disabled for all refs. +With '--force', the fast-forward check is disabled for all refs. Optionally, a parameter can be prefixed with a plus '+' sign to disable the fast-forward check only on that ref. diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index b3640c4e64..253ef624eb 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -993,7 +993,7 @@ would be different) ---------------- Updating from ae3a2da... to a80b4aa.... -Fast forward (no commit created; -m option ignored) +Fast-forward (no commit created; -m option ignored) example | 1 + hello | 1 + 2 files changed, 2 insertions(+), 0 deletions(-) @@ -1003,7 +1003,7 @@ Because your branch did not contain anything more than what had already been merged into the `master` branch, the merge operation did not actually do a merge. Instead, it just updated the top of the tree of your branch to that of the `master` branch. This is -often called 'fast forward' merge. +often called 'fast-forward' merge. You can run `gitk \--all` again to see how the commit ancestry looks like, or run 'show-branch', which tells you this. diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 06e0f315c3..4cc3d1387f 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -229,7 +229,7 @@ from updating that ref. This hook can be used to prevent 'forced' update on certain refs by making sure that the object name is a commit object that is a descendant of the commit object named by the old object name. -That is, to enforce a "fast forward only" policy. +That is, to enforce a "fast-forward only" policy. It could also be used to log the old..new status. However, it does not know the entire set of branches, so it would end up diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 43d84d15e9..1f029f8aa0 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -124,7 +124,7 @@ to point at the new commit. An evil merge is a <> that introduces changes that do not appear in any <>. -[[def_fast_forward]]fast forward:: +[[def_fast_forward]]fast-forward:: A fast-forward is a special type of <> where you have a <> and you are "merging" another <>'s changes that happen to be a descendant of what @@ -220,7 +220,7 @@ to point at the new commit. conflict, manual intervention may be required to complete the merge. + -As a noun: unless it is a <>, a +As a noun: unless it is a <>, a successful merge results in the creation of a new <> representing the result of the merge, and having as <> the tips of the merged <>. diff --git a/Documentation/howto/maintain-git.txt b/Documentation/howto/maintain-git.txt index 4357e26913..d527b30770 100644 --- a/Documentation/howto/maintain-git.txt +++ b/Documentation/howto/maintain-git.txt @@ -59,7 +59,7 @@ The policy. not yet pass the criteria set for 'next'. - The tips of 'master', 'maint' and 'next' branches will always - fast forward, to allow people to build their own + fast-forward, to allow people to build their own customization on top of them. - Usually 'master' contains all of 'maint', 'next' contains all diff --git a/Documentation/howto/revert-branch-rebase.txt b/Documentation/howto/revert-branch-rebase.txt index e70d8a31e7..8c32da6deb 100644 --- a/Documentation/howto/revert-branch-rebase.txt +++ b/Documentation/howto/revert-branch-rebase.txt @@ -85,7 +85,7 @@ Fortunately I did not have to; what I have in the current branch ------------------------------------------------ $ git checkout master -$ git merge revert-c99 ;# this should be a fast forward +$ git merge revert-c99 ;# this should be a fast-forward Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c... cache.h | 8 ++++---- commit.c | 2 +- @@ -95,7 +95,7 @@ Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c... 5 files changed, 8 insertions(+), 8 deletions(-) ------------------------------------------------ -There is no need to redo the test at this point. We fast forwarded +There is no need to redo the test at this point. We fast-forwarded and we know 'master' matches 'revert-c99' exactly. In fact: ------------------------------------------------ diff --git a/Documentation/howto/update-hook-example.txt b/Documentation/howto/update-hook-example.txt index 697d918885..b7f8d416d6 100644 --- a/Documentation/howto/update-hook-example.txt +++ b/Documentation/howto/update-hook-example.txt @@ -76,7 +76,7 @@ case "$1" in if expr "$2" : '0*$' >/dev/null; then info "The branch '$1' is new..." else - # updating -- make sure it is a fast forward + # updating -- make sure it is a fast-forward mb=$(git-merge-base "$2" "$3") case "$mb,$2" in "$2,$mb") info "Update is fast-forward" ;; diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt index f9811f2473..44d936341f 100644 --- a/Documentation/pull-fetch-param.txt +++ b/Documentation/pull-fetch-param.txt @@ -11,9 +11,9 @@ + The remote ref that matches is fetched, and if is not empty string, the local -ref that matches it is fast forwarded using . +ref that matches it is fast-forwarded using . If the optional plus `+` is used, the local ref -is updated even if it does not result in a fast forward +is updated even if it does not result in a fast-forward update. + [NOTE] diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 67ebffa568..269ec475e6 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -1384,7 +1384,7 @@ were merged. However, if the current branch is a descendant of the other--so every commit present in the one is already contained in the other--then git -just performs a "fast forward"; the head of the current branch is moved +just performs a "fast-forward"; the head of the current branch is moved forward to point at the head of the merged-in branch, without any new commits being created. @@ -1719,7 +1719,7 @@ producing a default commit message documenting the branch and repository that you pulled from. (But note that no such commit will be created in the case of a -<>; instead, your branch will just be +<>; instead, your branch will just be updated to point to the latest commit from the upstream branch.) The `git pull` command can also be given "." as the "remote" repository, @@ -1943,7 +1943,7 @@ $ git push ssh://yourserver.com/~you/proj.git master ------------------------------------------------- As with `git fetch`, `git push` will complain if this does not result in a -<>; see the following section for details on +<>; see the following section for details on handling this case. Note that the target of a "push" is normally a @@ -1976,7 +1976,7 @@ details. What to do when a push fails ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If a push would not result in a <> of the +If a push would not result in a <> of the remote branch, then it will fail with an error like: ------------------------------------------------- @@ -2115,7 +2115,7 @@ $ git checkout release && git pull Important note! If you have any local changes in these branches, then this merge will create a commit object in the history (with no local -changes git will simply do a "Fast forward" merge). Many people dislike +changes git will simply do a "fast-forward" merge). Many people dislike the "noise" that this creates in the Linux history, so you should avoid doing this capriciously in the "release" branch, as these noisy commits will become part of the permanent history when you ask Linus to pull @@ -2729,9 +2729,9 @@ In the previous example, when updating an existing branch, "git fetch" checks to make sure that the most recent commit on the remote branch is a descendant of the most recent commit on your copy of the branch before updating your copy of the branch to point at the new -commit. Git calls this process a <>. +commit. Git calls this process a <>. -A fast forward looks something like this: +A fast-forward looks something like this: ................................................ o--o--o--o <-- old head of the branch diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index 3dbdf7a288..cd10dbcbc9 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -97,21 +97,21 @@ static int update_local_ref(const char *name, strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); if (in_merge_bases(current, &updated, 1)) { - fprintf(stderr, "* %s: fast forward to %s\n", + fprintf(stderr, "* %s: fast-forward to %s\n", name, note); fprintf(stderr, " old..new: %s..%s\n", oldh, newh); - return update_ref_env("fast forward", name, sha1_new, sha1_old); + return update_ref_env("fast-forward", name, sha1_new, sha1_old); } if (!force) { fprintf(stderr, - "* %s: not updating to non-fast forward %s\n", + "* %s: not updating to non-fast-forward %s\n", name, note); fprintf(stderr, " old...new: %s...%s\n", oldh, newh); return 1; } fprintf(stderr, - "* %s: forcing update to non-fast forward %s\n", + "* %s: forcing update to non-fast-forward %s\n", name, note); fprintf(stderr, " old...new: %s...%s\n", oldh, newh); return update_ref_env("forced-update", name, sha1_new, sha1_old); diff --git a/builtin-fetch.c b/builtin-fetch.c index cb48c57ca3..6303aa0f58 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -269,7 +269,7 @@ static int update_local_ref(struct ref *ref, strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); strcat(quickref, ".."); strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); - r = s_update_ref("fast forward", ref, 1); + r = s_update_ref("fast-forward", ref, 1); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ', SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, pretty_ref, r ? " (unable to update local ref)" : ""); @@ -287,7 +287,7 @@ static int update_local_ref(struct ref *ref, r ? "unable to update local ref" : "forced update"); return r; } else { - sprintf(display, "! %-*s %-*s -> %s (non fast forward)", + sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)", SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, pretty_ref); return 1; diff --git a/builtin-merge.c b/builtin-merge.c index b6b84286b2..a595b8b47b 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -166,7 +166,7 @@ static struct option builtin_merge_options[] = { OPT_BOOLEAN(0, "commit", &option_commit, "perform a commit if the merge succeeds (default)"), OPT_BOOLEAN(0, "ff", &allow_fast_forward, - "allow fast forward (default)"), + "allow fast-forward (default)"), OPT_CALLBACK('s', "strategy", &use_strategies, "strategy", "merge strategy to use", option_parse_strategy), OPT_CALLBACK('m', "message", &merge_msg, "message", @@ -1013,7 +1013,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) hex, find_unique_abbrev(remoteheads->item->object.sha1, DEFAULT_ABBREV)); - strbuf_addstr(&msg, "Fast forward"); + strbuf_addstr(&msg, "Fast-forward"); if (have_message) strbuf_addstr(&msg, " (no commit created; -m option ignored)"); @@ -1031,12 +1031,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } else if (!remoteheads->next && common->next) ; /* - * We are not doing octopus and not fast forward. Need + * We are not doing octopus and not fast-forward. Need * a real merge. */ else if (!remoteheads->next && !common->next && option_commit) { /* - * We are not doing octopus, not fast forward, and have + * We are not doing octopus, not fast-forward, and have * only one common. */ refresh_cache(REFRESH_QUIET); diff --git a/builtin-push.c b/builtin-push.c index 3cb1ee46d1..e883574f03 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -159,7 +159,7 @@ static int do_push(const char *repo, int flags) error("failed to push some refs to '%s'", url[i]); if (nonfastforward && advice_push_nonfastforward) { printf("To prevent you from losing history, non-fast-forward updates were rejected\n" - "Merge the remote changes before pushing again. See the 'non-fast forward'\n" + "Merge the remote changes before pushing again. See the 'non-fast-forward'\n" "section of 'git push --help' for details.\n"); } errs++; diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index b771fe9b20..fea8fcdd8a 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -329,9 +329,9 @@ static const char *update(struct command *cmd) break; free_commit_list(bases); if (!ent) { - error("denying non-fast forward %s" + error("denying non-fast-forward %s" " (you should pull first)", name); - return "non-fast forward"; + return "non-fast-forward"; } } if (run_update_hook(cmd)) { diff --git a/builtin-remote.c b/builtin-remote.c index 0777dd719b..9aafc19c4d 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -953,7 +953,7 @@ static int show_push_info_item(struct string_list_item *item, void *cb_data) status = "up to date"; break; case PUSH_STATUS_FASTFORWARD: - status = "fast forwardable"; + status = "fast-forwardable"; break; case PUSH_STATUS_OUTOFDATE: status = "local out of date"; diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 37e528e283..37acad5ac1 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -246,7 +246,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count) break; case REF_STATUS_REJECT_NONFASTFORWARD: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "non-fast forward"); + "non-fast-forward"); break; case REF_STATUS_REMOTE_REJECT: print_ref_status('!', "[remote rejected]", ref, diff --git a/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh index e9588eec33..500635fe4b 100755 --- a/contrib/examples/git-merge.sh +++ b/contrib/examples/git-merge.sh @@ -14,7 +14,7 @@ summary (synonym to --stat) log add list of one-line log to merge commit message squash create a single commit instead of doing a merge commit perform a commit if the merge succeeds (default) -ff allow fast forward (default) +ff allow fast-forward (default) s,strategy= merge strategy to use m,message= message to be used for the merge commit (if any) " @@ -353,7 +353,7 @@ t,1,"$head",*) # Again the most common case of merging one remote. echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)" git update-index --refresh 2>/dev/null - msg="Fast forward" + msg="Fast-forward" if test -n "$have_message" then msg="$msg (no commit created; -m option ignored)" @@ -365,11 +365,11 @@ t,1,"$head",*) exit 0 ;; ?,1,?*"$LF"?*,*) - # We are not doing octopus and not fast forward. Need a + # We are not doing octopus and not fast-forward. Need a # real merge. ;; ?,1,*,) - # We are not doing octopus, not fast forward, and have only + # We are not doing octopus, not fast-forward, and have only # one common. git update-index --refresh 2>/dev/null case "$allow_trivial_merge" in diff --git a/contrib/examples/git-resolve.sh b/contrib/examples/git-resolve.sh index 0ee1bd898e..8f98142f77 100755 --- a/contrib/examples/git-resolve.sh +++ b/contrib/examples/git-resolve.sh @@ -48,7 +48,7 @@ case "$common" in "$head") echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $merge)" git read-tree -u -m $head $merge || exit 1 - git update-ref -m "resolve $merge_name: Fast forward" \ + git update-ref -m "resolve $merge_name: Fast-forward" \ HEAD "$merge" "$head" git diff-tree -p $head $merge | git apply --stat dropheads diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 2a66063e44..58a35c8287 100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -315,8 +315,8 @@ generate_update_branch_email() # "remotes/" will be ignored as well. # List all of the revisions that were removed by this update, in a - # fast forward update, this list will be empty, because rev-list O - # ^N is empty. For a non fast forward, O ^N is the list of removed + # fast-forward update, this list will be empty, because rev-list O + # ^N is empty. For a non-fast-forward, O ^N is the list of removed # revisions fast_forward="" rev="" @@ -411,7 +411,7 @@ generate_update_branch_email() # revision because the base is effectively a random revision at this # point - the user will be interested in what this revision changed # - including the undoing of previous revisions in the case of - # non-fast forward updates. + # non-fast-forward updates. echo "" echo "Summary of changes:" git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh index 1dadbb4966..825c52c243 100755 --- a/git-merge-octopus.sh +++ b/git-merge-octopus.sh @@ -81,7 +81,7 @@ do # tree as the intermediate result of the merge. # We still need to count this as part of the parent set. - echo "Fast forwarding to: $SHA1" + echo "Fast-forwarding to: $SHA1" git read-tree -u -m $head $SHA1 || exit MRC=$SHA1 MRT=$(git write-tree) continue diff --git a/git-pull.sh b/git-pull.sh index fc78592ae0..f36eb3e397 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -171,7 +171,7 @@ then # First update the working tree to match $curr_head. echo >&2 "Warning: fetch updated the current branch head." - echo >&2 "Warning: fast forwarding your working tree from" + echo >&2 "Warning: fast-forwarding your working tree from" echo >&2 "Warning: commit $orig_head." git update-index -q --refresh git read-tree -u -m "$orig_head" "$curr_head" || diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 23ded48322..c8987887f2 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -168,7 +168,7 @@ pick_one () { output git reset --hard $sha1 test "a$1" = a-n && output git reset --soft $current_sha1 sha1=$(git rev-parse --short $sha1) - output warn Fast forward to $sha1 + output warn Fast-forward to $sha1 else output git cherry-pick "$@" fi @@ -248,9 +248,9 @@ pick_one_preserving_merges () { done case $fast_forward in t) - output warn "Fast forward to $sha1" + output warn "Fast-forward to $sha1" output git reset --hard $sha1 || - die "Cannot fast forward to $sha1" + die "Cannot fast-forward to $sha1" ;; f) first_parent=$(expr "$new_parents" : ' \([^ ]*\)') diff --git a/git-rebase.sh b/git-rebase.sh index 6ec155cf03..6830e1627d 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -496,7 +496,7 @@ then fi # If the $onto is a proper descendant of the tip of the branch, then -# we just fast forwarded. +# we just fast-forwarded. if test "$mb" = "$branch" then say "Fast-forwarded $branch_name to $onto_name." diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index 271bc4e17f..c2d408b461 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -5,7 +5,7 @@ test_description='Two way merge with read-tree -m $H $M -This test tries two-way merge (aka fast forward with carry forward). +This test tries two-way merge (aka fast-forward with carry forward). There is the head (called H) and another commit (called M), which is simply ahead of H. The index and the work tree contains a state that @@ -51,7 +51,7 @@ check_cache_at () { } cat >bozbar-old <<\EOF -This is a sample file used in two-way fast forward merge +This is a sample file used in two-way fast-forward merge tests. Its second line ends with a magic word bozbar which will be modified by the merged head to gnusto. It has some extra lines so that external tools can @@ -300,7 +300,7 @@ test_expect_success \ echo gnusto gnusto >bozbar && if read_tree_twoway $treeH $treeM; then false; else :; fi' -# This fails with straight two-way fast forward. +# This fails with straight two-way fast-forward. test_expect_success \ '22 - local change cache updated.' \ 'rm -f .git/index && diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 852ccb5d7d..220b6a3413 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -158,7 +158,7 @@ cat > test/expect << EOF another master Local refs configured for 'git push': - ahead forces to master (fast forwardable) + ahead forces to master (fast-forwardable) master pushes to another (up to date) EOF diff --git a/t/t5518-fetch-exit-status.sh b/t/t5518-fetch-exit-status.sh index c6bc65faa0..c2060bb870 100755 --- a/t/t5518-fetch-exit-status.sh +++ b/t/t5518-fetch-exit-status.sh @@ -22,7 +22,7 @@ test_expect_success setup ' git commit -a -m next ' -test_expect_success 'non fast forward fetch' ' +test_expect_success 'non-fast-forward fetch' ' test_must_fail git fetch . master:side diff --git a/t/t6028-merge-up-to-date.sh b/t/t6028-merge-up-to-date.sh index f8f3e3ff2c..a91644e3b2 100755 --- a/t/t6028-merge-up-to-date.sh +++ b/t/t6028-merge-up-to-date.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='merge fast forward and up to date' +test_description='merge fast-forward and up to date' . ./test-lib.sh diff --git a/transport.c b/transport.c index 644a30a0b2..d81a42aec4 100644 --- a/transport.c +++ b/transport.c @@ -668,7 +668,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i break; case REF_STATUS_REJECT_NONFASTFORWARD: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "non-fast forward", porcelain); + "non-fast-forward", porcelain); break; case REF_STATUS_REMOTE_REJECT: print_ref_status('!', "[remote rejected]", ref, diff --git a/unpack-trees.c b/unpack-trees.c index 720f7a1616..157d5d001f 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -895,7 +895,7 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o) * Two-way merge. * * The rule is to "carry forward" what is in the index without losing - * information across a "fast forward", favoring a successful merge + * information across a "fast-forward", favoring a successful merge * over a merge failure when it makes sense. For details of the * "carry forward" rule, please see . * From 6c0efa2ac07f275c1edd11ae6646a647d7f19f36 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Oct 2009 18:37:56 -0700 Subject: [PATCH 160/884] GIT 1.6.5.2 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.5.2.txt | 19 +++++++++++++++++++ Documentation/git.txt | 3 ++- GIT-VERSION-GEN | 2 +- RelNotes | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 Documentation/RelNotes-1.6.5.2.txt diff --git a/Documentation/RelNotes-1.6.5.2.txt b/Documentation/RelNotes-1.6.5.2.txt new file mode 100644 index 0000000000..aa7ccce3a2 --- /dev/null +++ b/Documentation/RelNotes-1.6.5.2.txt @@ -0,0 +1,19 @@ +GIT v1.6.5.2 Release Notes +========================== + +Fixes since v1.6.5.1 +-------------------- + + * Installation of templates triggered a bug in busybox when using tar + implementation from it. + + * "git add -i" incorrectly ignored paths that are already in the index + if they matched .gitignore patterns. + + * "git describe --always" should have produced some output even there + were no tags in the repository, but it didn't. + + * "git ls-files" when showing tracked files incorrectly paid attention + to the exclude patterns. + +Other minor documentation updates are included. diff --git a/Documentation/git.txt b/Documentation/git.txt index 8519928d45..e3fe970b34 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.5.1/git.html[documentation for release 1.6.5.1] +* link:v1.6.5.2/git.html[documentation for release 1.6.5.2] * release notes for + link:RelNotes-1.6.5.2.txt[1.6.5.2], link:RelNotes-1.6.5.1.txt[1.6.5.1], link:RelNotes-1.6.5.txt[1.6.5]. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index d11e43cf31..09cb354ca0 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.5.1 +DEF_VER=v1.6.5.2 LF=' ' diff --git a/RelNotes b/RelNotes index dda06d1d8f..1cb2571eaa 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.6.5.1.txt \ No newline at end of file +Documentation/RelNotes-1.6.5.2.txt \ No newline at end of file From 610f99ec7d22b5750f12350f67c1341d6be8030f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Oct 2009 18:41:09 -0700 Subject: [PATCH 161/884] Update draft release notes to 1.6.6 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.6.txt | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Documentation/RelNotes-1.6.6.txt b/Documentation/RelNotes-1.6.6.txt index fa0e11a180..abf34e6ace 100644 --- a/Documentation/RelNotes-1.6.6.txt +++ b/Documentation/RelNotes-1.6.6.txt @@ -46,11 +46,28 @@ Updates since v1.6.5 (usability, bells and whistles) + * The object replace mechanism can be bypassed with --no-replace-objects + global option given to the "git" program. + * "git fsck" by default checks the packfiles (i.e. "--full" is the default); you can turn it off with "git fsck --no-full". + * import-tars contributed fast-import frontend learned more types of + compressed tarballs. + + * "git instaweb" knows how to talk with mod_cgid to apache2. + * "git log --decorate" shows the location of HEAD as well. + * "git rebase -i" learned "reword" that acts like "edit" but immediately + starts an editor to tweak the log message without returning control to + the shell, which is done by "edit" to give an opportunity to tweak the + contents. + + * Author names shown in gitweb output are links to search commits by the + author. + + (developers) Fixes since v1.6.5 @@ -69,3 +86,23 @@ release, unless otherwise noted. whitespace attribute). The 'trailing-space' whitespace error class has become a short-hand to cover both of these and there is no behaviour change for existing set-ups. + + * "git cvsimport" did not work well when it is fed filenames from the + command line and is not started at the top of the work tree. We should + backport this by merging f6fdbb6 (cvsimport: fix relative argument + filenames, 2009-10-19). + + * The way gitweb escapes its CGI parameters were broken especially when + the parameter was a UTF-8 string. We may want to backport this to + 1.6.5.X series by merging 452e225 (gitweb: fix esc_param, 2009-10-13). + + * gitweb used to show 'patch' link for merge commits but the output from + it is not usable to feed "git am" with. We may want to backport this + to 1.6.5.X series by merging 1655c98 (gitweb: Do not show 'patch' link + for merge commits, 2009-10-09). + +--- +exec >/var/tmp/1 +echo O=$(git describe master) +O=v1.6.5.2-73-g9b12444 +git shortlog --no-merges $O..master --not maint From 2aae905f23f79f004625346d057e5be7a81dbcd4 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sun, 25 Oct 2009 15:15:22 -0400 Subject: [PATCH 162/884] push: always load default config This is needed because we want to use the advice.pushnonfastforward variable. Previously, we would load the config on demand only when we needed to look at push.default. Which meant that "git push" would load it, but "git push remote" would not, leading to differing behavior. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-push.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builtin-push.c b/builtin-push.c index 3cb1ee46d1..8a19f10354 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -66,7 +66,6 @@ static void setup_push_tracking(void) static void setup_default_push_refspecs(void) { - git_config(git_default_config, NULL); switch (push_default) { default: case PUSH_DEFAULT_MATCHING: @@ -173,7 +172,6 @@ int cmd_push(int argc, const char **argv, const char *prefix) int tags = 0; int rc; const char *repo = NULL; /* default repository */ - struct option options[] = { OPT_BIT('q', "quiet", &flags, "be quiet", TRANSPORT_PUSH_QUIET), OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE), @@ -191,6 +189,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_END() }; + git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, push_usage, 0); if (tags) From ffd5c8e457f8139ee89006083d97d47e17a066b8 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 22 Oct 2009 23:39:04 -0700 Subject: [PATCH 163/884] git svn: fix fetch where glob is on the top-level URL In cases where the top-level URL we're tracking is the path we glob against, we can once again track odd repositories that keep branches/tags at the top level. This regression was introduced in commit 6f5748e14cc5bb0a836b649fb8e2d6a5eb166f1d. Thanks to Daniel Cordero for the original bug report and bisection. Signed-off-by: Eric Wong --- git-svn.perl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index eb4b75aff3..56af2216a9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1765,7 +1765,7 @@ sub read_all_remotes { my $use_svm_props = eval { command_oneline(qw/config --bool svn.useSvmProps/) }; $use_svm_props = $use_svm_props eq 'true' if $use_svm_props; - my $svn_refspec = qr{\s*/?(.*?)\s*:\s*(.+?)\s*}; + my $svn_refspec = qr{\s*(.*?)\s*:\s*(.+?)\s*}; foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) { if (m!^(.+)\.fetch=$svn_refspec$!) { my ($remote, $local_ref, $remote_ref) = ($1, $2, $3); @@ -1979,7 +1979,7 @@ sub find_ref { my ($ref_id) = @_; foreach (command(qw/config -l/)) { next unless m!^svn-remote\.(.+)\.fetch= - \s*/?(.*?)\s*:\s*(.+?)\s*$!x; + \s*(.*?)\s*:\s*(.+?)\s*$!x; my ($repo_id, $path, $ref) = ($1, $2, $3); if ($ref eq $ref_id) { $path = '' if ($path =~ m#^\./?#); From cb74a0ca61278b97d9e12af9ed9f7040ab13f507 Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Tue, 20 Oct 2009 15:41:59 +1300 Subject: [PATCH 164/884] git-svn: add test data for SVK merge, with script. Dump generated with SVK 2.0.2 and SVN 1.5.1 (on lenny amd64). Signed-off-by: Sam Vilain Acked-by: Eric Wong --- t/t9150/make-svk-dump | 57 ++++ t/t9150/svk-merge.dump | 616 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 673 insertions(+) create mode 100644 t/t9150/make-svk-dump create mode 100644 t/t9150/svk-merge.dump diff --git a/t/t9150/make-svk-dump b/t/t9150/make-svk-dump new file mode 100644 index 0000000000..2242f14ebe --- /dev/null +++ b/t/t9150/make-svk-dump @@ -0,0 +1,57 @@ +#!/bin/sh +# +# this script sets up a Subversion repository for Makefile in the +# first ever git merge, as if it were done with svk. +# + +set -e + +svk depotmap foo ~/.svk/foo +svk co /foo/ foo +cd foo +mkdir trunk +mkdir branches +svk add trunk branches +svk commit -m "Setup trunk and branches" +cd trunk + +git cat-file blob 6683463e:Makefile > Makefile +svk add Makefile + +svk commit -m "ancestor" +cd .. +svk cp trunk branches/left + +svk commit -m "make left branch" +cd branches/left/ + +git cat-file blob 5873b67e:Makefile > Makefile +svk commit -m "left update 1" + +cd ../../trunk +git cat-file blob 75118b13:Makefile > Makefile +svk commit -m "trunk update" + +cd ../branches/left +git cat-file blob b5039db6:Makefile > Makefile +svk commit -m "left update 2" + +cd ../../trunk +svk sm /foo/branches/left +# in theory we could delete the "left" branch here, but it's not +# required so don't do it, in case people start getting ideas ;) +svk commit -m "merge branch 'left' into 'trunk'" + +git cat-file blob b51ad431:Makefile > Makefile + +svk diff Makefile && echo "Hey! No differences, magic" + +cd ../.. + +svnadmin dump ~/.svk/foo > svk-merge.dump + +svk co -d foo +rm -rf foo +svk depotmap -d /foo/ +rm -rf ~/.svk/foo + diff --git a/t/t9150/svk-merge.dump b/t/t9150/svk-merge.dump new file mode 100644 index 0000000000..42f70dbec7 --- /dev/null +++ b/t/t9150/svk-merge.dump @@ -0,0 +1,616 @@ +SVN-fs-dump-format-version: 2 + +UUID: b48289b2-9c08-4d72-af37-0358a40b9c15 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2009-10-19T23:44:03.722969Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 123 +Content-length: 123 + +K 7 +svn:log +V 24 +Setup trunk and branches +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:04.927533Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 106 +Content-length: 106 + +K 7 +svn:log +V 8 +ancestor +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:05.835585Z +PROPS-END + +Node-path: trunk/Makefile +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2401 +Text-content-md5: bfd8ff778d1492dc6758567373176a89 +Content-length: 2411 + +PROPS-END +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 3 +Prop-content-length: 115 +Content-length: 115 + +K 7 +svn:log +V 16 +make left branch +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:06.719737Z +PROPS-END + +Node-path: branches/left +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk + + +Revision-number: 4 +Prop-content-length: 112 +Content-length: 112 + +K 7 +svn:log +V 13 +left update 1 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:07.167666Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2465 +Text-content-md5: 16e38d9753b061731650561ce01b1195 +Content-length: 2465 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 5 +Prop-content-length: 111 +Content-length: 111 + +K 7 +svn:log +V 12 +trunk update +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:07.619633Z +PROPS-END + +Node-path: trunk/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2521 +Text-content-md5: 0668418a621333f4aa8b6632cd63e2a0 +Content-length: 2521 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base merge-cache + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +merge-cache: merge-cache.o read-cache.o + $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 6 +Prop-content-length: 112 +Content-length: 112 + +K 7 +svn:log +V 13 +left update 2 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:08.067554Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2593 +Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba +Content-length: 2593 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 7 +Prop-content-length: 131 +Content-length: 131 + +K 7 +svn:log +V 32 +merge branch 'left' into 'trunk' +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:08.971801Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 83 +Content-length: 83 + +K 9 +svk:merge +V 53 +b48289b2-9c08-4d72-af37-0358a40b9c15:/branches/left:6 +PROPS-END + + +Node-path: trunk/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2713 +Text-content-md5: 0afbe34f244cd662b1f97d708c687f90 +Content-length: 2713 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base merge-cache + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +merge-cache: merge-cache.o read-cache.o + $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + From a5e9c7dfe4732b560ab5dfc75e718bfda2027990 Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Tue, 20 Oct 2009 15:42:00 +1300 Subject: [PATCH 165/884] git-svn: allow test setup script to support PERL env. var Possibly the 'perl' in the PATH is not the one to be used for the tests; let PERL set in the environment select it. Signed-off-by: Sam Vilain Acked-by: Eric Wong --- t/lib-git-svn.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index fd8631f906..0f7f35ccc9 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -16,6 +16,7 @@ fi GIT_DIR=$PWD/.git GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn SVN_TREE=$GIT_SVN_DIR/svn-tree +PERL=${PERL:-perl} svn >/dev/null 2>&1 if test $? -ne 1 @@ -29,7 +30,7 @@ export svnrepo svnconf=$PWD/svnconf export svnconf -perl -w -e " +$PERL -w -e " use SVN::Core; use SVN::Repos; \$SVN::Core::VERSION gt '1.1.0' or exit(42); @@ -130,7 +131,7 @@ stop_httpd () { } convert_to_rev_db () { - perl -w -- - "$@" <<\EOF + $PERL -w -- - "$@" <<\EOF use strict; @ARGV == 2 or die "Usage: convert_to_rev_db "; open my $wr, '+>', $ARGV[1] or die "$!: couldn't open: $ARGV[1]"; From f1264bd65451a68e87e0f99c9601cfa1b3244db6 Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Tue, 20 Oct 2009 15:42:01 +1300 Subject: [PATCH 166/884] git-svn: convert SVK merge tickets to extra parents SVK is a simple case to start with, as its idea of merge parents matches git's one. When a svk:merge ticket is encountered, check each of the listed merged revisions to see if they are in the history of this commit; if not, then we have encountered a merge - record it. [ew: minor formatting cleanups] Signed-off-by: Sam Vilain Acked-by: Eric Wong --- git-svn.perl | 52 ++++++++++++++++++++++++++++++++++++- t/t9150-svk-mergetickets.sh | 23 ++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 t/t9150-svk-mergetickets.sh diff --git a/git-svn.perl b/git-svn.perl index 56af2216a9..1b918aef71 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2878,14 +2878,64 @@ sub check_author { $author; } +sub find_extra_svk_parents { + my ($self, $ed, $tickets, $parents) = @_; + # aha! svk:merge property changed... + my @tickets = split "\n", $tickets; + my @known_parents; + for my $ticket ( @tickets ) { + my ($uuid, $path, $rev) = split /:/, $ticket; + if ( $uuid eq $self->ra_uuid ) { + my $url = $self->rewrite_root || $self->{url}; + my $repos_root = $url; + my $branch_from = $path; + $branch_from =~ s{^/}{}; + my $gs = $self->other_gs($repos_root."/".$branch_from, + $url, + $branch_from, + $rev, + $self->{ref_id}); + if ( my $commit = $gs->rev_map_get($rev, $uuid) ) { + # wahey! we found it, but it might be + # an old one (!) + push @known_parents, $commit; + } + } + } + for my $parent ( @known_parents ) { + my @cmd = ('rev-list', $parent, map { "^$_" } @$parents ); + my ($msg_fh, $ctx) = command_output_pipe(@cmd); + my $new; + while ( <$msg_fh> ) { + $new=1;last; + } + command_close_pipe($msg_fh, $ctx); + if ( $new ) { + print STDERR + "Found merge parent (svk:merge ticket): $parent\n"; + push @$parents, $parent; + } + } +} + sub make_log_entry { my ($self, $rev, $parents, $ed) = @_; my $untracked = $self->get_untracked($ed); + my @parents = @$parents; + my $ps = $ed->{path_strip} || ""; + for my $path ( grep { m/$ps/ } %{$ed->{dir_prop}} ) { + my $props = $ed->{dir_prop}{$path}; + if ( $props->{"svk:merge"} ) { + $self->find_extra_svk_parents + ($ed, $props->{"svk:merge"}, \@parents); + } + } + open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; print $un "r$rev\n" or croak $!; print $un $_, "\n" foreach @$untracked; - my %log_entry = ( parents => $parents || [], revision => $rev, + my %log_entry = ( parents => \@parents, revision => $rev, log => ''); my $headrev; diff --git a/t/t9150-svk-mergetickets.sh b/t/t9150-svk-mergetickets.sh new file mode 100644 index 0000000000..8000c347b4 --- /dev/null +++ b/t/t9150-svk-mergetickets.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# +# Copyright (c) 2007 Sam Vilain +# + +test_description='git-svn svk merge tickets' + +. ./lib-git-svn.sh + +test_expect_success 'load svk depot' " + svnadmin load -q '$rawsvnrepo' < '../t9150/svk-merge.dump' && + git svn init --minimize-url -R svkmerge \ + -T trunk -b branches '$svnrepo' && + git svn fetch --all + " + +uuid=b48289b2-9c08-4d72-af37-0358a40b9c15 + +test_expect_success 'svk merges were represented coming in' " + [ `git-cat-file commit HEAD | grep parent | wc -l` -eq 2 ] + " + +test_done From ce62683096d144749c77e557ecd21c91b840b0f1 Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Tue, 20 Oct 2009 15:42:02 +1300 Subject: [PATCH 167/884] git-svn: add test data for SVN 1.5+ merge, with script. Dump generated with SVN 1.5.1 (on lenny amd64). This test should hopefully cover all but a few intermediate versions of the svnmerge.py script. Signed-off-by: Sam Vilain Acked-by: Eric Wong --- t/t9151/.gitignore | 2 + t/t9151/make-svnmerge-dump | 73 ++++ t/t9151/svn-mergeinfo.dump | 736 +++++++++++++++++++++++++++++++++++++ 3 files changed, 811 insertions(+) create mode 100644 t/t9151/.gitignore create mode 100644 t/t9151/make-svnmerge-dump create mode 100644 t/t9151/svn-mergeinfo.dump diff --git a/t/t9151/.gitignore b/t/t9151/.gitignore new file mode 100644 index 0000000000..587c37dba3 --- /dev/null +++ b/t/t9151/.gitignore @@ -0,0 +1,2 @@ +foo +foo.svn diff --git a/t/t9151/make-svnmerge-dump b/t/t9151/make-svnmerge-dump new file mode 100644 index 0000000000..e35d64d585 --- /dev/null +++ b/t/t9151/make-svnmerge-dump @@ -0,0 +1,73 @@ +#!/bin/sh +# +# this script sets up a Subversion repository for Makefile in the +# first ever git merge, as if it were done with svnmerge (SVN 1.5+) +# + +rm -rf foo.svn foo +set -e + +mkdir foo.svn +svnadmin create foo.svn +svn co file://`pwd`/foo.svn foo + +cd foo +mkdir trunk +mkdir branches +svn add trunk branches +svn commit -m "Setup trunk and branches" +cd trunk + +git cat-file blob 6683463e:Makefile > Makefile +svn add Makefile + +echo "Committing ANCESTOR" +svn commit -m "ancestor" +cd .. +svn cp trunk branches/left + +echo "Committing BRANCH POINT" +svn commit -m "make left branch" +cd branches/left/ + +#$sm init +#svn commit -m "init svnmerge" + +git cat-file blob 5873b67e:Makefile > Makefile +echo "Committing BRANCH UPDATE 1" +svn commit -m "left update 1" +cd ../.. + +cd trunk +git cat-file blob 75118b13:Makefile > Makefile +echo "Committing TRUNK UPDATE" +svn commit -m "trunk update" + +cd ../branches/left +git cat-file blob ff5ebe39:Makefile > Makefile +echo "Committing BRANCH UPDATE 2" +svn commit -m "left update 2" + +git cat-file blob b5039db6:Makefile > Makefile +echo "Committing BRANCH UPDATE 3" +svn commit -m "left update 3" + +# merge to trunk + +cd ../.. +svn update +cd trunk + +svn merge ../branches/left --accept postpone + +git cat-file blob b51ad431:Makefile > Makefile + +svn resolved Makefile + +svn commit -m "Merge trunk" + +cd ../.. + +svnadmin dump foo.svn > svn-mergeinfo.dump + +rm -rf foo foo.svn diff --git a/t/t9151/svn-mergeinfo.dump b/t/t9151/svn-mergeinfo.dump new file mode 100644 index 0000000000..2153187c9b --- /dev/null +++ b/t/t9151/svn-mergeinfo.dump @@ -0,0 +1,736 @@ +SVN-fs-dump-format-version: 2 + +UUID: 1ce241d1-ba54-4eb9-bded-03057fe48a33 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2009-10-20T01:33:37.692723Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 123 +Content-length: 123 + +K 7 +svn:log +V 24 +Setup trunk and branches +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:38.159933Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 106 +Content-length: 106 + +K 7 +svn:log +V 8 +ancestor +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:39.160059Z +PROPS-END + +Node-path: trunk/Makefile +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2401 +Text-content-md5: bfd8ff778d1492dc6758567373176a89 +Content-length: 2411 + +PROPS-END +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 3 +Prop-content-length: 115 +Content-length: 115 + +K 7 +svn:log +V 16 +make left branch +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:41.148192Z +PROPS-END + +Node-path: branches/left +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk +Prop-content-length: 34 +Content-length: 34 + +K 13 +svn:mergeinfo +V 0 + +PROPS-END + + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/Makefile +Text-copy-source-md5: bfd8ff778d1492dc6758567373176a89 + + +Revision-number: 4 +Prop-content-length: 112 +Content-length: 112 + +K 7 +svn:log +V 13 +left update 1 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:42.148773Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2465 +Text-content-md5: 16e38d9753b061731650561ce01b1195 +Content-length: 2465 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 5 +Prop-content-length: 111 +Content-length: 111 + +K 7 +svn:log +V 12 +trunk update +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:43.159959Z +PROPS-END + +Node-path: trunk/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2521 +Text-content-md5: 0668418a621333f4aa8b6632cd63e2a0 +Content-length: 2521 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base merge-cache + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +merge-cache: merge-cache.o read-cache.o + $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 6 +Prop-content-length: 112 +Content-length: 112 + +K 7 +svn:log +V 13 +left update 2 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:44.164175Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2529 +Text-content-md5: f6b197cc3f2e89a83e545d4bb003de73 +Content-length: 2529 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 7 +Prop-content-length: 112 +Content-length: 112 + +K 7 +svn:log +V 13 +left update 3 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:45.144214Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2593 +Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba +Content-length: 2593 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 8 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 11 +Merge trunk +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:48.176135Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 53 +Content-length: 53 + +K 13 +svn:mergeinfo +V 18 +/branches/left:2-7 +PROPS-END + + +Node-path: trunk/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2713 +Text-content-md5: 0afbe34f244cd662b1f97d708c687f90 +Content-length: 2713 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base merge-cache + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +merge-cache: merge-cache.o read-cache.o + $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + From dff589ef949e97cbec5363b105ba27f6e85594fb Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Tue, 20 Oct 2009 15:42:03 +1300 Subject: [PATCH 168/884] git-svn: convert SVN 1.5+ / svnmerge.py svn:mergeinfo props to parents This feature is long overdue; convert SVN's merge representation to git's as revisions are imported. This works by converting the list of revisions in each line of the svn:mergeinfo into git revision ranges, and then checking the latest of each of these revision ranges for A) being new and B) now being completely merged. Signed-off-by: Sam Vilain Acked-by: Eric Wong --- git-svn.perl | 93 ++++++++++++++++++++++++++++++++++++++++ t/t9151-svn-mergeinfo.sh | 21 +++++++++ 2 files changed, 114 insertions(+) create mode 100644 t/t9151-svn-mergeinfo.sh diff --git a/git-svn.perl b/git-svn.perl index 1b918aef71..6a3b501d24 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2918,6 +2918,93 @@ sub find_extra_svk_parents { } } +# note: this function should only be called if the various dirprops +# have actually changed +sub find_extra_svn_parents { + my ($self, $ed, $mergeinfo, $parents) = @_; + # aha! svk:merge property changed... + + # We first search for merged tips which are not in our + # history. Then, we figure out which git revisions are in + # that tip, but not this revision. If all of those revisions + # are now marked as merge, we can add the tip as a parent. + my @merges = split "\n", $mergeinfo; + my @merge_tips; + my @merged_commit_ranges; + my $url = $self->rewrite_root || $self->{url}; + for my $merge ( @merges ) { + my ($source, $revs) = split ":", $merge; + my $path = $source; + $path =~ s{^/}{}; + my $gs = Git::SVN->find_by_url($url.$source, $url, $path); + if ( !$gs ) { + warn "Couldn't find revmap for $url$source\n"; + next; + } + my @ranges = split ",", $revs; + my ($tip, $tip_commit); + # find the tip + for my $range ( @ranges ) { + my ($bottom, $top) = split "-", $range; + $top ||= $bottom; + my $bottom_commit = + $gs->rev_map_get($bottom, $self->ra_uuid) || + $gs->rev_map_get($bottom+1, $self->ra_uuid); + my $top_commit = + $gs->rev_map_get($top, $self->ra_uuid); + + unless ($top_commit and $bottom_commit) { + warn "W:unknown path/rev in svn:mergeinfo " + ."dirprop: $source:$range\n"; + next; + } + + push @merged_commit_ranges, + "$bottom_commit..$top_commit"; + + if ( !defined $tip or $top > $tip ) { + $tip = $top; + $tip_commit = $top_commit; + } + } + unless (!$tip_commit or + grep { $_ eq $tip_commit } @$parents ) { + push @merge_tips, $tip_commit; + } else { + push @merge_tips, undef; + } + } + for my $merge_tip ( @merge_tips ) { + my $spec = shift @merges; + next unless $merge_tip; + my @cmd = ('rev-list', "-1", $merge_tip, + "--not", @$parents ); + my ($msg_fh, $ctx) = command_output_pipe(@cmd); + my $new; + while ( <$msg_fh> ) { + $new=1;last; + } + command_close_pipe($msg_fh, $ctx); + if ( $new ) { + push @cmd, @merged_commit_ranges; + my ($msg_fh, $ctx) = command_output_pipe(@cmd); + my $unmerged; + while ( <$msg_fh> ) { + $unmerged=1;last; + } + command_close_pipe($msg_fh, $ctx); + if ( $unmerged ) { + warn "W:svn cherry-pick ignored ($spec)\n"; + } else { + warn + "Found merge parent (svn:mergeinfo prop): ", + $merge_tip, "\n"; + push @$parents, $merge_tip; + } + } + } +} + sub make_log_entry { my ($self, $rev, $parents, $ed) = @_; my $untracked = $self->get_untracked($ed); @@ -2930,6 +3017,12 @@ sub make_log_entry { $self->find_extra_svk_parents ($ed, $props->{"svk:merge"}, \@parents); } + if ( $props->{"svn:mergeinfo"} ) { + $self->find_extra_svn_parents + ($ed, + $props->{"svn:mergeinfo"}, + \@parents); + } } open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh new file mode 100644 index 0000000000..7eb36e599c --- /dev/null +++ b/t/t9151-svn-mergeinfo.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# +# Copyright (c) 2007, 2009 Sam Vilain +# + +test_description='git-svn svn mergeinfo properties' + +. ./lib-git-svn.sh + +test_expect_success 'load svn dump' " + svnadmin load -q '$rawsvnrepo' < '../t9151/svn-mergeinfo.dump' && + git svn init --minimize-url -R svnmerge \ + -T trunk -b branches '$svnrepo' && + git svn fetch --all + " + +test_expect_success 'svn merges were represented coming in' " + [ `git cat-file commit HEAD | grep parent | wc -l` -eq 2 ] + " + +test_done From 9f67d2e8279e1885ef2b4681c19cef8534259783 Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Wed, 21 Oct 2009 09:35:22 -0400 Subject: [PATCH 169/884] Teach "git describe" --dirty option With the --dirty option, git describe works on HEAD but append s"-dirty" iff the contents of the work tree differs from HEAD. E.g. $ git describe --dirty v1.6.5-15-gc274db7 $ echo >> Makefile $ git describe --dirty v1.6.5-15-gc274db7-dirty The --dirty option can also be used to specify what is appended, instead of the default string "-dirty". $ git describe --dirty=.mod v1.6.5-15-gc274db7.mod Many build scripts use `git describe` to produce a version number based on the description of HEAD (on which the work tree is based) + saying that if the build contains uncommitted changes. This patch helps the writing of such scripts since `git describe --dirty` does directly the intended thing. Three possiblities were considered while discussing this new feature: 1. Describe the work tree by default and describe HEAD only if "HEAD" is explicitly specified Pro: does the right thing by default (both for users and for scripts) Pro: other git commands that works on the work tree by default Con: breaks existing scripts used by the Linux kernel and other projects 2. Use --worktree instead of --dirty Pro: does what it says: "git describe --worktree" describes the work tree Con: other commands do not require a --worktree option when working on the work tree (it often is the default mode for them) Con: unusable with an optional value: "git describe --worktree=.mod" is quite unintuitive. 3. Use --dirty as in this patch Pro: makes sense to specify an optional value (what the dirty mark is) Pro: does not have any of the big cons of previous alternatives * does not break scripts * is not inconsistent with other git commands This patch takes the third approach. Signed-off-by: Jean Privat Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-describe.txt | 6 ++++++ builtin-describe.c | 25 ++++++++++++++++++++++++- t/t6120-describe.sh | 14 ++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index b231dbb947..5253d86118 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -9,6 +9,7 @@ git-describe - Show the most recent tag that is reachable from a commit SYNOPSIS -------- 'git describe' [--all] [--tags] [--contains] [--abbrev=] ... +'git describe' [--all] [--tags] [--contains] [--abbrev=] --dirty[=] DESCRIPTION ----------- @@ -27,6 +28,11 @@ OPTIONS ...:: Committish object names to describe. +--dirty[=]:: + Describe the working tree. + It means describe HEAD and appends (`-dirty` by + default) if the working tree is dirty. + --all:: Instead of using only the annotated tags, use any ref found in `.git/refs/`. This option enables matching diff --git a/builtin-describe.c b/builtin-describe.c index df67a733ae..7dbbee36ac 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -5,12 +5,14 @@ #include "builtin.h" #include "exec_cmd.h" #include "parse-options.h" +#include "diff.h" #define SEEN (1u<<0) #define MAX_TAGS (FLAG_BITS - 1) static const char * const describe_usage[] = { "git describe [options] *", + "git describe [options] --dirty", NULL }; @@ -23,6 +25,13 @@ static int max_candidates = 10; static int found_names; static const char *pattern; static int always; +static const char *dirty; + +/* diff-index command arguments to check if working tree is dirty. */ +static const char *diff_index_args[] = { + "diff-index", "--quiet", "HEAD", "--", NULL +}; + struct commit_name { struct tag *tag; @@ -208,6 +217,8 @@ static void describe(const char *arg, int last_one) display_name(n); if (longformat) show_suffix(0, n->tag ? n->tag->tagged->sha1 : sha1); + if (dirty) + printf("%s", dirty); printf("\n"); return; } @@ -265,7 +276,10 @@ static void describe(const char *arg, int last_one) if (!match_cnt) { const unsigned char *sha1 = cmit->object.sha1; if (always) { - printf("%s\n", find_unique_abbrev(sha1, abbrev)); + printf("%s", find_unique_abbrev(sha1, abbrev)); + if (dirty) + printf("%s", dirty); + printf("\n"); return; } die("cannot describe '%s'", sha1_to_hex(sha1)); @@ -300,6 +314,8 @@ static void describe(const char *arg, int last_one) display_name(all_matches[0].name); if (abbrev) show_suffix(all_matches[0].depth, cmit->object.sha1); + if (dirty) + printf("%s", dirty); printf("\n"); if (!last_one) @@ -324,6 +340,9 @@ int cmd_describe(int argc, const char **argv, const char *prefix) "only consider tags matching "), OPT_BOOLEAN(0, "always", &always, "show abbreviated commit object as fallback"), + {OPTION_STRING, 0, "dirty", &dirty, "mark", + "append on dirty working tree (default: \"-dirty\")", + PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"}, OPT_END(), }; @@ -360,7 +379,11 @@ int cmd_describe(int argc, const char **argv, const char *prefix) } if (argc == 0) { + if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix)) + dirty = NULL; describe("HEAD", 1); + } else if (dirty) { + die("--dirty is incompatible with committishes"); } else { while (argc-- > 0) { describe(*argv++, argc == 0); diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 8c7e081c53..100c4d99fb 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -123,6 +123,20 @@ test_expect_success 'rename tag Q back to A' ' test_expect_success 'pack tag refs' 'git pack-refs' check_describe A-* HEAD +check_describe "A-*[0-9a-f]" --dirty + +test_expect_success 'set-up dirty work tree' ' + echo >>file +' + +check_describe "A-*[0-9a-f]-dirty" --dirty + +check_describe "A-*[0-9a-f].mod" --dirty=.mod + +test_expect_success 'describe --dirty HEAD' ' + test_must_fail git describe --dirty HEAD +' + test_expect_success 'set-up matching pattern tests' ' git tag -a -m test-annotated test-annotated && echo >>file && From c591d5f311e0c2c0708b91bf5e05bd1c36da658a Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 26 Oct 2009 21:10:24 -0400 Subject: [PATCH 170/884] gitignore: root most patterns at the top-level directory Our gitignore doesn't use a preceding "/" to root its patterns in the top of the repository. This means that if you add a file or directory called "git" (for example) inside a subdirectory, it will be erroneously ignored. This patch was done mechanically with "s/^[^*]/\/&/" with one exception: instead of ignoring gitk-wish, we should gitk-git/gitk-wish (arguably, this should be done in gitk-git/.gitignore, but because that is a subtree merge from elsewhere, this is easier). Acked-by: Sverre Rabbelier Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- .gitignore | 354 ++++++++++++++++++++++++++--------------------------- 1 file changed, 177 insertions(+), 177 deletions(-) diff --git a/.gitignore b/.gitignore index 51a37b1af7..289c3d0e26 100644 --- a/.gitignore +++ b/.gitignore @@ -1,184 +1,184 @@ -GIT-BUILD-OPTIONS -GIT-CFLAGS -GIT-GUI-VARS -GIT-VERSION-FILE -git -git-add -git-add--interactive -git-am -git-annotate -git-apply -git-archimport -git-archive -git-bisect -git-bisect--helper -git-blame -git-branch -git-bundle -git-cat-file -git-check-attr -git-check-ref-format -git-checkout -git-checkout-index -git-cherry -git-cherry-pick -git-clean -git-clone -git-commit -git-commit-tree -git-config -git-count-objects -git-cvsexportcommit -git-cvsimport -git-cvsserver -git-daemon -git-diff -git-diff-files -git-diff-index -git-diff-tree -git-difftool -git-difftool--helper -git-describe -git-fast-export -git-fast-import -git-fetch -git-fetch--tool -git-fetch-pack -git-filter-branch -git-fmt-merge-msg -git-for-each-ref -git-format-patch -git-fsck -git-fsck-objects -git-gc -git-get-tar-commit-id -git-grep -git-hash-object -git-help -git-http-fetch -git-http-push -git-imap-send -git-index-pack -git-init -git-init-db -git-instaweb -git-log -git-lost-found -git-ls-files -git-ls-remote -git-ls-tree -git-mailinfo -git-mailsplit -git-merge -git-merge-base -git-merge-index -git-merge-file -git-merge-tree -git-merge-octopus -git-merge-one-file -git-merge-ours -git-merge-recursive -git-merge-resolve -git-merge-subtree -git-mergetool -git-mergetool--lib -git-mktag -git-mktree -git-name-rev -git-mv -git-pack-redundant -git-pack-objects -git-pack-refs -git-parse-remote -git-patch-id -git-peek-remote -git-prune -git-prune-packed -git-pull -git-push -git-quiltimport -git-read-tree -git-rebase -git-rebase--interactive -git-receive-pack -git-reflog -git-relink -git-remote -git-remote-curl -git-repack -git-replace -git-repo-config -git-request-pull -git-rerere -git-reset -git-rev-list -git-rev-parse -git-revert -git-rm -git-send-email -git-send-pack -git-sh-setup -git-shell -git-shortlog -git-show -git-show-branch -git-show-index -git-show-ref -git-stage -git-stash -git-status -git-stripspace -git-submodule -git-svn -git-symbolic-ref -git-tag -git-tar-tree -git-unpack-file -git-unpack-objects -git-update-index -git-update-ref -git-update-server-info -git-upload-archive -git-upload-pack -git-var -git-verify-pack -git-verify-tag -git-web--browse -git-whatchanged -git-write-tree -git-core-*/?* -gitk-wish -gitweb/gitweb.cgi -test-chmtime -test-ctype -test-date -test-delta -test-dump-cache-tree -test-genrandom -test-match-trees -test-parse-options -test-path-utils -test-sha1 -test-sigchain -common-cmds.h +/GIT-BUILD-OPTIONS +/GIT-CFLAGS +/GIT-GUI-VARS +/GIT-VERSION-FILE +/git +/git-add +/git-add--interactive +/git-am +/git-annotate +/git-apply +/git-archimport +/git-archive +/git-bisect +/git-bisect--helper +/git-blame +/git-branch +/git-bundle +/git-cat-file +/git-check-attr +/git-check-ref-format +/git-checkout +/git-checkout-index +/git-cherry +/git-cherry-pick +/git-clean +/git-clone +/git-commit +/git-commit-tree +/git-config +/git-count-objects +/git-cvsexportcommit +/git-cvsimport +/git-cvsserver +/git-daemon +/git-diff +/git-diff-files +/git-diff-index +/git-diff-tree +/git-difftool +/git-difftool--helper +/git-describe +/git-fast-export +/git-fast-import +/git-fetch +/git-fetch--tool +/git-fetch-pack +/git-filter-branch +/git-fmt-merge-msg +/git-for-each-ref +/git-format-patch +/git-fsck +/git-fsck-objects +/git-gc +/git-get-tar-commit-id +/git-grep +/git-hash-object +/git-help +/git-http-fetch +/git-http-push +/git-imap-send +/git-index-pack +/git-init +/git-init-db +/git-instaweb +/git-log +/git-lost-found +/git-ls-files +/git-ls-remote +/git-ls-tree +/git-mailinfo +/git-mailsplit +/git-merge +/git-merge-base +/git-merge-index +/git-merge-file +/git-merge-tree +/git-merge-octopus +/git-merge-one-file +/git-merge-ours +/git-merge-recursive +/git-merge-resolve +/git-merge-subtree +/git-mergetool +/git-mergetool--lib +/git-mktag +/git-mktree +/git-name-rev +/git-mv +/git-pack-redundant +/git-pack-objects +/git-pack-refs +/git-parse-remote +/git-patch-id +/git-peek-remote +/git-prune +/git-prune-packed +/git-pull +/git-push +/git-quiltimport +/git-read-tree +/git-rebase +/git-rebase--interactive +/git-receive-pack +/git-reflog +/git-relink +/git-remote +/git-remote-curl +/git-repack +/git-replace +/git-repo-config +/git-request-pull +/git-rerere +/git-reset +/git-rev-list +/git-rev-parse +/git-revert +/git-rm +/git-send-email +/git-send-pack +/git-sh-setup +/git-shell +/git-shortlog +/git-show +/git-show-branch +/git-show-index +/git-show-ref +/git-stage +/git-stash +/git-status +/git-stripspace +/git-submodule +/git-svn +/git-symbolic-ref +/git-tag +/git-tar-tree +/git-unpack-file +/git-unpack-objects +/git-update-index +/git-update-ref +/git-update-server-info +/git-upload-archive +/git-upload-pack +/git-var +/git-verify-pack +/git-verify-tag +/git-web--browse +/git-whatchanged +/git-write-tree +/git-core-*/?* +/gitk-git/gitk-wish +/gitweb/gitweb.cgi +/test-chmtime +/test-ctype +/test-date +/test-delta +/test-dump-cache-tree +/test-genrandom +/test-match-trees +/test-parse-options +/test-path-utils +/test-sha1 +/test-sigchain +/common-cmds.h *.tar.gz *.dsc *.deb -git.spec +/git.spec *.exe *.[aos] *.py[co] -config.mak -autom4te.cache -config.cache -config.log -config.status -config.mak.autogen -config.mak.append -configure -tags -TAGS -cscope* +/config.mak +/autom4te.cache +/config.cache +/config.log +/config.status +/config.mak.autogen +/config.mak.append +/configure +/tags +/TAGS +/cscope* *.obj *.lib *.sln @@ -188,5 +188,5 @@ cscope* *.user *.idb *.pdb -Debug/ -Release/ +/Debug/ +/Release/ From a29aa47da79cfd0ef7ee0ef423e7e5a9a3cf07bd Mon Sep 17 00:00:00 2001 From: Gerrit Pape Date: Tue, 27 Oct 2009 13:31:33 +0000 Subject: [PATCH 171/884] help -i: properly error out if no info viewer can be found With this commit, git help -i prints an error message and exits non-zero instead of being silent and exit code 0. Reported by Trent W. Buck through http://bugs.debian.org/537664 Signed-off-by: Gerrit Pape Signed-off-by: Junio C Hamano --- builtin-help.c | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin-help.c b/builtin-help.c index e1eba778a5..e1ade8edd3 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -372,6 +372,7 @@ static void show_info_page(const char *git_cmd) const char *page = cmd_to_page(git_cmd); setenv("INFOPATH", system_path(GIT_INFO_PATH), 1); execlp("info", "info", "gitman", page, NULL); + die("no info viewer handled the request"); } static void get_html_page_path(struct strbuf *page_path, const char *page) From f1be316ada93158507c315ee7948bb9e6007eb91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kr=C3=BCger?= Date: Tue, 27 Oct 2009 15:58:14 +0100 Subject: [PATCH 172/884] rebase -i: more graceful handling of invalid commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, when there is an invalid command, the rest of the line is still treated as if the command had been valid, i.e. rebase -i attempts to produce a patch, using the next argument as a SHA1 name. If there is no next argument or an invalid one, very confusing error messages appear (the line was '.'; path to git-rebase-todo substituted): Unknown command: . fatal: ambiguous argument 'Please fix this in the file $somefile.': unknown revision or path not in the working tree. Use '--' to separate paths from revisions fatal: Not a valid object name Please fix this in the file $somefile. fatal: bad revision 'Please fix this in the file $somefile.' Instead, verify the validity of the remaining line and error out earlier if necessary. Signed-off-by: Jan Krüger Acked-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 23ded48322..9b1e899e27 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -408,7 +408,12 @@ do_next () { ;; *) warn "Unknown command: $command $sha1 $rest" - die_with_patch $sha1 "Please fix this in the file $TODO." + if git rev-parse --verify -q "$sha1" >/dev/null + then + die_with_patch $sha1 "Please fix this in the file $TODO." + else + die "Please fix this in the file $TODO." + fi ;; esac test -s "$TODO" && return From 24ab81ae4d12c81076da256b3f9cdde45277f126 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 27 Oct 2009 20:52:57 -0400 Subject: [PATCH 173/884] add-interactive: handle deletion of empty files Usually we show deletion as a big hunk deleting all of the file's text. However, for files with no content, the diff shows just the 'deleted file mode ...' line. This patch cause "add -p" (and related commands) to recognize that line and explicitly ask about deleting the file. We only add the "stage this deletion" hunk for empty files, since other files will already ask about the big content deletion hunk. We could also change those files to simply display "stage this deletion", but showing the actual deleted content is probably what an interactive user wants. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 18 +++++++++++++----- t/t3701-add-interactive.sh | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 392efb913f..35f4ef11de 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -731,14 +731,17 @@ sub parse_diff_header { my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' }; my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' }; + my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' }; for (my $i = 0; $i < @{$src->{TEXT}}; $i++) { - my $dest = $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? - $mode : $head; + my $dest = + $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode : + $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion : + $head; push @{$dest->{TEXT}}, $src->{TEXT}->[$i]; push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i]; } - return ($head, $mode); + return ($head, $mode, $deletion); } sub hunk_splittable { @@ -1206,7 +1209,7 @@ sub patch_update_file { my ($ix, $num); my $path = shift; my ($head, @hunk) = parse_diff($path); - ($head, my $mode) = parse_diff_header($head); + ($head, my $mode, my $deletion) = parse_diff_header($head); for (@{$head->{DISPLAY}}) { print; } @@ -1214,6 +1217,9 @@ sub patch_update_file { if (@{$mode->{TEXT}}) { unshift @hunk, $mode; } + if (@{$deletion->{TEXT}} && !@hunk) { + @hunk = ($deletion); + } $num = scalar @hunk; $ix = 0; @@ -1267,7 +1273,9 @@ sub patch_update_file { print; } print colored $prompt_color, $patch_mode_flavour{VERB}, - ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : ' this hunk'), + ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : + $hunk[$ix]{TYPE} eq 'deletion' ? ' deletion' : + ' this hunk'), $patch_mode_flavour{TARGET}, " [y,n,q,a,d,/$other,?]? "; my $line = prompt_single_character; diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 62fd65e18d..aa5909b14c 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -214,4 +214,21 @@ test_expect_success 'add first line works' ' test_cmp expected diff ' +cat >expected < empty && + git add empty && + git commit -m empty && + rm empty && + echo y | git add -p empty && + git diff --cached >diff && + test_cmp expected diff +' + test_done From 73cf0822b2a4ffa7ad559d1f0772e39718fc7776 Mon Sep 17 00:00:00 2001 From: Julian Phillips Date: Sun, 25 Oct 2009 21:28:11 +0000 Subject: [PATCH 174/884] remote: Make ref_remove_duplicates faster for large numbers of refs The ref_remove_duplicates function was very slow at dealing with very large numbers of refs. This is because it was using a linear search through all remaining refs to find any duplicates of the current ref. Rewriting it to use a string list to keep track of which refs have already been seen and removing duplicates when they are found is much more efficient. Signed-off-by: Julian Phillips Signed-off-by: Junio C Hamano --- remote.c | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/remote.c b/remote.c index 73d33f2584..4f9f0ccc7b 100644 --- a/remote.c +++ b/remote.c @@ -6,6 +6,7 @@ #include "revision.h" #include "dir.h" #include "tag.h" +#include "string-list.h" static struct refspec s_tag_refspec = { 0, @@ -734,29 +735,31 @@ int for_each_remote(each_remote_fn fn, void *priv) void ref_remove_duplicates(struct ref *ref_map) { - struct ref **posn; - struct ref *next; - for (; ref_map; ref_map = ref_map->next) { + struct string_list refs = { NULL, 0, 0, 0 }; + struct string_list_item *item = NULL; + struct ref *prev = NULL, *next = NULL; + for (; ref_map; prev = ref_map, ref_map = next) { + next = ref_map->next; if (!ref_map->peer_ref) continue; - posn = &ref_map->next; - while (*posn) { - if ((*posn)->peer_ref && - !strcmp((*posn)->peer_ref->name, - ref_map->peer_ref->name)) { - if (strcmp((*posn)->name, ref_map->name)) - die("%s tracks both %s and %s", - ref_map->peer_ref->name, - (*posn)->name, ref_map->name); - next = (*posn)->next; - free((*posn)->peer_ref); - free(*posn); - *posn = next; - } else { - posn = &(*posn)->next; - } + + item = string_list_lookup(ref_map->peer_ref->name, &refs); + if (item) { + if (strcmp(((struct ref *)item->util)->name, + ref_map->name)) + die("%s tracks both %s and %s", + ref_map->peer_ref->name, + ((struct ref *)item->util)->name, + ref_map->name); + prev->next = ref_map->next; + free(ref_map->peer_ref); + free(ref_map); } + + item = string_list_insert(ref_map->peer_ref->name, &refs); + item->util = ref_map; } + string_list_clear(&refs, 0); } int remote_has_url(struct remote *remote, const char *url) From b1a01e1c0762d117da7dac009b773f310479be12 Mon Sep 17 00:00:00 2001 From: Julian Phillips Date: Sun, 25 Oct 2009 21:28:12 +0000 Subject: [PATCH 175/884] fetch: Speed up fetch of large numbers of refs When there are large numbers of refs, calling read_ref for each ref is inefficent (and infact downright slow) - so instead use for_each_ref to build up a string list of all the refs that we currently have, which significantly improves the volume. Signed-off-by: Julian Phillips Signed-off-by: Junio C Hamano --- builtin-fetch.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/builtin-fetch.c b/builtin-fetch.c index a35a6f8cb8..5c7465cfeb 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -489,7 +489,8 @@ static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata) { struct string_list *list = (struct string_list *)cbdata; - string_list_insert(refname, list); + struct string_list_item *item = string_list_insert(refname, list); + item->util = (void *)sha1; return 0; } @@ -615,9 +616,14 @@ static void check_not_current_branch(struct ref *ref_map) static int do_fetch(struct transport *transport, struct refspec *refs, int ref_count) { + struct string_list existing_refs = { NULL, 0, 0, 0 }; + struct string_list_item *peer_item = NULL; struct ref *ref_map; struct ref *rm; int autotags = (transport->remote->fetch_tags == 1); + + for_each_ref(add_existing, &existing_refs); + if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET) tags = TAGS_SET; if (transport->remote->fetch_tags == -1) @@ -640,8 +646,13 @@ static int do_fetch(struct transport *transport, check_not_current_branch(ref_map); for (rm = ref_map; rm; rm = rm->next) { - if (rm->peer_ref) - read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1); + if (rm->peer_ref) { + peer_item = string_list_lookup(rm->peer_ref->name, + &existing_refs); + if (peer_item) + hashcpy(rm->peer_ref->old_sha1, + peer_item->util); + } } if (tags == TAGS_DEFAULT && autotags) From ade2ca0ca9f8335be300d5538e1ca9cb3594ae64 Mon Sep 17 00:00:00 2001 From: Sebastian Schuberth Date: Tue, 27 Oct 2009 12:23:33 +0100 Subject: [PATCH 176/884] Do not try to remove directories when removing old links When building Git with MSVC on Windows, directories named after the Git alias are created for the output files, e.g. there is a "git-merge-index" directory next to the "git-merge-index.exe" executable in the build root. Previously, "make all" just checked if "git-merge-index" and "git-merge-index.exe" are the same file, and if not, tried to remove "git-merge-index". This fails in the case of "git-merge-index" being a directory, which is why this is checked now. Signed-off-by: Sebastian Schuberth Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 42b7d60e19..268aede566 100644 --- a/Makefile +++ b/Makefile @@ -1375,7 +1375,7 @@ SHELL = $(SHELL_PATH) all:: shell_compatibility_test $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS ifneq (,$X) - $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$p' -ef '$p$X' || $(RM) '$p';) + $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';) endif all:: From 93cf50a412e3ed988cbce07e2ba3a80841d3884b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Sat, 17 Oct 2009 11:33:38 +0200 Subject: [PATCH 177/884] bash: complete more options for 'git rebase' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete all long options for 'git rebase' except --no-verify (probably used very seldom) and the long options corresponding to -v, -q, and -f. Signed-off-by: Björn Gustavsson Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index d3fec32997..7c7318c436 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1323,8 +1323,18 @@ _git_rebase () fi __git_complete_strategy && return case "$cur" in + --whitespace=*) + __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" + return + ;; --*) - __gitcomp "--onto --merge --strategy --interactive" + __gitcomp " + --onto --merge --strategy --interactive + --preserve-merges --stat --no-stat + --committer-date-is-author-date --ignore-date + --ignore-whitespace --whitespace= + " + return esac __gitcomp "$(__git_refs)" From 7c3baa9abf0abe15f068d95688bbc27e73a69042 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Sep 2009 12:22:36 +0200 Subject: [PATCH 178/884] help -a: do not unnecessarily look for a repository Although 'git help -a' actually doesn't need to be run inside a git repository and uses no repository-specific information, it looks for a git directory. On 'git ' the bash completion runs 'git help -a' and unnecessary searching for a git directory can be annoying in auto-mount environments. With this commit, 'git help' no longer searches for a repository when run with the -a option. Reported by Vincent Danjean through http://bugs.debian.org/539273 Signed-off-by: Johannes Schindelin Signed-off-by: Gerrit Pape Signed-off-by: Junio C Hamano --- builtin-help.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builtin-help.c b/builtin-help.c index e1ade8edd3..ca08519d9d 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -417,9 +417,6 @@ int cmd_help(int argc, const char **argv, const char *prefix) const char *alias; load_command_list("git-", &main_cmds, &other_cmds); - setup_git_directory_gently(&nongit); - git_config(git_help_config, NULL); - argc = parse_options(argc, argv, prefix, builtin_help_options, builtin_help_usage, 0); @@ -430,6 +427,9 @@ int cmd_help(int argc, const char **argv, const char *prefix) return 0; } + setup_git_directory_gently(&nongit); + git_config(git_help_config, NULL); + if (!argv[0]) { printf("usage: %s\n\n", git_usage_string); list_common_cmds_help(); From f7ad96cfaaec3b32458ca69d44c50d1397d11d38 Mon Sep 17 00:00:00 2001 From: Markus Heidelberg Date: Wed, 28 Oct 2009 10:45:38 +0100 Subject: [PATCH 179/884] bash completion: difftool accepts the same options as diff So complete refs, files after the double-dash and some diff options that make sense for difftool. Signed-off-by: Markus Heidelberg Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 7c7318c436..e3ddecc995 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -958,6 +958,8 @@ __git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff _git_difftool () { + __git_has_doubledash && return + local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --tool=*) @@ -965,11 +967,15 @@ _git_difftool () return ;; --*) - __gitcomp "--tool=" + __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex + --base --ours --theirs + --no-renames --diff-filter= --find-copies-harder + --relative --ignore-submodules + --tool=" return ;; esac - COMPREPLY=() + __git_complete_file } __git_fetch_options=" From c1e01b0c5106451770ae77a62f52acfe814c3326 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 28 Oct 2009 10:13:44 -0700 Subject: [PATCH 180/884] commit: More generous accepting of RFC-2822 footer lines. 'git commit -s' will insert a blank line before the Signed-off-by line at the end of the message, unless this last line is a Signed-off-by line itself. Common use has other trailing lines at the ends of commit text, in the style of RFC2822 headers. Be more generous in considering lines to be part of this footer. If the last paragraph of the commit message reasonably resembles RFC-2822 formatted lines, don't insert that blank line. The new Signed-off-by line is still only suppressed when the author's existing Signed-off-by is the last line of the message. Signed-off-by: David Brown Signed-off-by: Junio C Hamano --- builtin-commit.c | 43 ++++++++++++++++++++++++++++++++++++++++++- t/t7501-commit.sh | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/builtin-commit.c b/builtin-commit.c index 200ffdaad4..c395cbf14f 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -414,6 +414,47 @@ static void determine_author_info(void) author_date = date; } +static int ends_rfc2822_footer(struct strbuf *sb) +{ + int ch; + int hit = 0; + int i, j, k; + int len = sb->len; + int first = 1; + const char *buf = sb->buf; + + for (i = len - 1; i > 0; i--) { + if (hit && buf[i] == '\n') + break; + hit = (buf[i] == '\n'); + } + + while (i < len - 1 && buf[i] == '\n') + i++; + + for (; i < len; i = k) { + for (k = i; k < len && buf[k] != '\n'; k++) + ; /* do nothing */ + k++; + + if ((buf[k] == ' ' || buf[k] == '\t') && !first) + continue; + + first = 0; + + for (j = 0; i + j < len; j++) { + ch = buf[i + j]; + if (ch == ':') + break; + if (isalnum(ch) || + (ch == '-')) + continue; + return 0; + } + } + return 1; +} + static int prepare_to_commit(const char *index_file, const char *prefix, struct wt_status *s) { @@ -489,7 +530,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--) ; /* do nothing */ if (prefixcmp(sb.buf + i, sob.buf)) { - if (prefixcmp(sb.buf + i, sign_off_header)) + if (!ends_rfc2822_footer(&sb)) strbuf_addch(&sb, '\n'); strbuf_addbuf(&sb, &sob); } diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index e2ef53228e..d2de57679f 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -247,6 +247,47 @@ $existing" && ' +test_expect_success 'signoff gap' ' + + echo 3 >positive && + git add positive && + alt="Alt-RFC-822-Header: Value" && + git commit -s -m "welcome + +$alt" && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + ( + echo welcome + echo + echo $alt + git var GIT_COMMITTER_IDENT | + sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'signoff gap 2' ' + + echo 4 >positive && + git add positive && + alt="fixed: 34" && + git commit -s -m "welcome + +We have now +$alt" && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + ( + echo welcome + echo + echo We have now + echo $alt + echo + git var GIT_COMMITTER_IDENT | + sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" + ) >expected && + test_cmp expected actual +' + test_expect_success 'multiple -m' ' >negative && From ad3f9a71a8200418e1da59b9712a8fde3f8c4c08 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 27 Oct 2009 11:28:07 -0700 Subject: [PATCH 181/884] Add '--bisect' revision machinery argument I personally use "git bisect visualize" all the time when I bisect, but it turns out that that is not a very flexible model. Sometimes I want to do bisection based on all commits (no pathname limiting), but then visualize the current bisection tree with just a few pathnames because I _suspect_ those pathnames are involved in the problem but am not totally sure about them. And at other times, I want to use other revision parsing logic, none of which is available with "git bisect visualize". So this adds "--bisect" as a revision parsing argument, and as a result it just works with all the normal logging tools. So now I can just do gitk --bisect --simplify-by-decoration filename-here etc. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- builtin-rev-list.c | 2 ++ builtin-rev-parse.c | 11 +++++++++++ revision.c | 19 ++++++++++++++++++- revision.h | 1 + 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 4ba1c12e0b..32bf033b5b 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -319,6 +319,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) memset(&info, 0, sizeof(info)); info.revs = &revs; + if (revs.bisect) + bisect_list = 1; quiet = DIFF_OPT_TST(&revs.diffopt, QUIET); for (i = 1 ; i < argc; i++) { diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 45bead6545..9526aafc6c 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -180,6 +180,12 @@ static int show_reference(const char *refname, const unsigned char *sha1, int fl return 0; } +static int anti_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +{ + show_rev(REVERSED, sha1, refname); + return 0; +} + static void show_datestring(const char *flag, const char *datestr) { static char buffer[100]; @@ -548,6 +554,11 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for_each_ref(show_reference, NULL); continue; } + if (!strcmp(arg, "--bisect")) { + for_each_ref_in("refs/bisect/bad", show_reference, NULL); + for_each_ref_in("refs/bisect/good", anti_reference, NULL); + continue; + } if (!strcmp(arg, "--branches")) { for_each_branch_ref(show_reference, NULL); continue; diff --git a/revision.c b/revision.c index 9fc4e8d381..a36c0d9bcd 100644 --- a/revision.c +++ b/revision.c @@ -994,7 +994,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg if (!strcmp(arg, "--all") || !strcmp(arg, "--branches") || !strcmp(arg, "--tags") || !strcmp(arg, "--remotes") || !strcmp(arg, "--reflog") || !strcmp(arg, "--not") || - !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk")) + !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") || + !strcmp(arg, "--bisect")) { unkv[(*unkc)++] = arg; return 1; @@ -1218,6 +1219,16 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, ctx->argc -= n; } +static int for_each_bad_bisect_ref(each_ref_fn fn, void *cb_data) +{ + return for_each_ref_in("refs/bisect/bad", fn, cb_data); +} + +static int for_each_good_bisect_ref(each_ref_fn fn, void *cb_data) +{ + return for_each_ref_in("refs/bisect/good", fn, cb_data); +} + /* * Parse revision information, filling in the "rev_info" structure, * and removing the used arguments from the argument list. @@ -1259,6 +1270,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch handle_refs(revs, flags, for_each_branch_ref); continue; } + if (!strcmp(arg, "--bisect")) { + handle_refs(revs, flags, for_each_bad_bisect_ref); + handle_refs(revs, flags ^ UNINTERESTING, for_each_good_bisect_ref); + revs->bisect = 1; + continue; + } if (!strcmp(arg, "--tags")) { handle_refs(revs, flags, for_each_tag_ref); continue; diff --git a/revision.h b/revision.h index b6421a6432..921656aaab 100644 --- a/revision.h +++ b/revision.h @@ -63,6 +63,7 @@ struct rev_info { reverse:1, reverse_output_stage:1, cherry_pick:1, + bisect:1, first_parent_only:1; /* Diff flags */ From 4d23660e79dbbb7e2ae37cb7193166d085a78502 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Wed, 28 Oct 2009 23:10:06 +0100 Subject: [PATCH 182/884] describe: when failing, tell the user about options that work Users seem to call git-describe without reading the manpage, and then wonder why it doesn't work with unannotated tags by default. Make a minimal effort towards seeing if there would have been unannotated tags, and tell the user. Specifically, we say that --tags could work if we found any unannotated tags. If not, we say that --always would have given results. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- builtin-describe.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/builtin-describe.c b/builtin-describe.c index eaa8a9d229..504d9b1209 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -96,8 +96,6 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void if (!all) { if (!prio) return 0; - if (!tags && prio < 2) - return 0; } add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1); return 0; @@ -184,6 +182,7 @@ static void describe(const char *arg, int last_one) struct possible_tag all_matches[MAX_TAGS]; unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; unsigned long seen_commits = 0; + unsigned int unannotated_cnt = 0; if (get_sha1(arg, sha1)) die("Not a valid object name %s", arg); @@ -217,7 +216,9 @@ static void describe(const char *arg, int last_one) seen_commits++; n = c->util; if (n) { - if (match_cnt < max_candidates) { + if (!tags && !all && n->prio < 2) { + unannotated_cnt++; + } else if (match_cnt < max_candidates) { struct possible_tag *t = &all_matches[match_cnt++]; t->name = n; t->depth = seen_commits - 1; @@ -259,7 +260,14 @@ static void describe(const char *arg, int last_one) printf("%s\n", find_unique_abbrev(sha1, abbrev)); return; } - die("cannot describe '%s'", sha1_to_hex(sha1)); + if (unannotated_cnt) + die("No annotated tags can describe '%s'.\n" + "However, there were unannotated tags: try --tags.", + sha1_to_hex(sha1)); + else + die("No tags can describe '%s'.\n" + "Try --always, or create some tags.", + sha1_to_hex(sha1)); } qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt); From c8998b4823cbccd6bd49c2034e242ae7d5873eae Mon Sep 17 00:00:00 2001 From: Scott Chacon Date: Wed, 28 Oct 2009 14:39:32 -0700 Subject: [PATCH 183/884] mergetool--lib: add p4merge as a pre-configured mergetool option Add p4merge to the set of built-in diff/merge tools, and update bash completion and documentation. Signed-off-by: Scott Chacon Signed-off-by: Junio C Hamano --- Documentation/git-difftool.txt | 2 +- Documentation/git-mergetool.txt | 2 +- Documentation/merge-config.txt | 2 +- contrib/completion/git-completion.bash | 2 +- git-mergetool--lib.sh | 17 +++++++++++++++-- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index 96a6c51a4b..8e9aed67d7 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -31,7 +31,7 @@ OPTIONS Use the diff tool specified by . Valid merge tools are: kdiff3, kompare, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, - ecmerge, diffuse, opendiff and araxis. + ecmerge, diffuse, opendiff, p4merge and araxis. + If a diff tool is not specified, 'git-difftool' will use the configuration variable `diff.tool`. If the diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 68ed6c0956..4a6f7f3a2d 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -27,7 +27,7 @@ OPTIONS Use the merge resolution program specified by . Valid merge tools are: kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, - diffuse, tortoisemerge, opendiff and araxis. + diffuse, tortoisemerge, opendiff, p4merge and araxis. + If a merge resolution program is not specified, 'git-mergetool' will use the configuration variable `merge.tool`. If the diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt index c0f96e7070..a403155052 100644 --- a/Documentation/merge-config.txt +++ b/Documentation/merge-config.txt @@ -23,7 +23,7 @@ merge.tool:: Controls which merge resolution program is used by linkgit:git-mergetool[1]. Valid built-in values are: "kdiff3", "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", - "diffuse", "ecmerge", "tortoisemerge", "araxis", and + "diffuse", "ecmerge", "tortoisemerge", "p4merge", "araxis" and "opendiff". Any other value is treated is custom merge tool and there must be a corresponding mergetool..cmd option. diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index e3ddecc995..2a9a88968f 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -953,7 +953,7 @@ _git_diff () } __git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff - tkdiff vimdiff gvimdiff xxdiff araxis + tkdiff vimdiff gvimdiff xxdiff araxis p4merge " _git_difftool () diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index bfb01f7842..f7c571e73c 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -46,7 +46,7 @@ check_unchanged () { valid_tool () { case "$1" in kdiff3 | tkdiff | xxdiff | meld | opendiff | \ - emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis) + emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis | p4merge) ;; # happy tortoisemerge) if ! merge_mode; then @@ -130,6 +130,19 @@ run_merge_tool () { "$merge_tool_path" "$LOCAL" "$REMOTE" fi ;; + p4merge) + if merge_mode; then + touch "$BACKUP" + if $base_present; then + "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED" + else + "$merge_tool_path" "$LOCAL" "$LOCAL" "$REMOTE" "$MERGED" + fi + check_unchanged + else + "$merge_tool_path" "$LOCAL" "$REMOTE" + fi + ;; meld) if merge_mode; then touch "$BACKUP" @@ -323,7 +336,7 @@ guess_merge_tool () { else tools="opendiff kdiff3 tkdiff xxdiff meld $tools" fi - tools="$tools gvimdiff diffuse ecmerge araxis" + tools="$tools gvimdiff diffuse ecmerge p4merge araxis" fi if echo "${VISUAL:-$EDITOR}" | grep emacs > /dev/null 2>&1; then # $EDITOR is emacs so add emerge as a candidate From 0fcabdeb52b79775173d009ccc179db104dfbb66 Mon Sep 17 00:00:00 2001 From: Sebastian Schuberth Date: Mon, 19 Oct 2009 18:37:05 +0200 Subject: [PATCH 184/884] Use faster byte swapping when compiling with MSVC When compiling with MSVC on x86-compatible, use an intrinsic for byte swapping. In contrast to the GCC path, we do not prefer inline assembly here as it is not supported for the x64 platform. Signed-off-by: Sebastian Schuberth Signed-off-by: Junio C Hamano --- compat/bswap.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/compat/bswap.h b/compat/bswap.h index 5cc4acbfcc..279e0b48b1 100644 --- a/compat/bswap.h +++ b/compat/bswap.h @@ -28,6 +28,16 @@ static inline uint32_t default_swab32(uint32_t val) } \ __res; }) +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + +#include + +#define bswap32(x) _byteswap_ulong(x) + +#endif + +#ifdef bswap32 + #undef ntohl #undef htonl #define ntohl(x) bswap32(x) From 3fe9747b94ca81d29894debdb371cff3b1d89af1 Mon Sep 17 00:00:00 2001 From: Sebastian Schuberth Date: Mon, 19 Oct 2009 18:40:47 +0200 Subject: [PATCH 185/884] Make the MSVC projects use PDB/IDB files named after the project Instead of having all PDB files for all projects named "vc90.pdb", name them after the respective project to make the relation more clear (and to avoid name clashes when copying files around). Signed-off-by: Sebastian Schuberth Acked-by: Marius Storm-Olsen Signed-off-by: Junio C Hamano --- contrib/buildsystems/Generators/Vcproj.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contrib/buildsystems/Generators/Vcproj.pm b/contrib/buildsystems/Generators/Vcproj.pm index be94ba18d2..cfa74adcc2 100644 --- a/contrib/buildsystems/Generators/Vcproj.pm +++ b/contrib/buildsystems/Generators/Vcproj.pm @@ -178,6 +178,7 @@ sub createLibProject { MinimalRebuild="true" RuntimeLibrary="1" UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" WarningLevel="3" DebugInformationFormat="3" /> @@ -244,6 +245,7 @@ sub createLibProject { RuntimeLibrary="0" EnableFunctionLevelLinking="true" UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" WarningLevel="3" DebugInformationFormat="3" /> @@ -401,6 +403,7 @@ sub createAppProject { MinimalRebuild="true" RuntimeLibrary="1" UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" WarningLevel="3" DebugInformationFormat="3" /> @@ -472,6 +475,7 @@ sub createAppProject { RuntimeLibrary="0" EnableFunctionLevelLinking="true" UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" WarningLevel="3" DebugInformationFormat="3" /> From 168eff3c802a47a4e8a97b2ec70d86e5f605a012 Mon Sep 17 00:00:00 2001 From: Markus Heidelberg Date: Wed, 28 Oct 2009 13:24:30 +0100 Subject: [PATCH 186/884] t4034-diff-words: add a test for word diff without context Signed-off-by: Markus Heidelberg Signed-off-by: Junio C Hamano --- t/t4034-diff-words.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index 4508effcaa..82240cf44d 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -63,6 +63,26 @@ test_expect_success 'word diff with runs of whitespace' ' ' +cat > expect <<\EOF +diff --git a/pre b/post +index 330b04f..5ed8eff 100644 +--- a/pre ++++ b/post +@@ -1 +1 @@ +h(4)h(4),hh[44] +@@ -3,0 +4,4 @@ a = b + c + +aa = a + +aeff = aeff * ( aaa ) +EOF + +test_expect_failure 'word diff without context' ' + + word_diff --color-words --unified=0 + +' + cat > expect <<\EOF diff --git a/pre b/post index 330b04f..5ed8eff 100644 From a4ca1465ec8afee798bf8f11d727179ca3da64a9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 29 Oct 2009 11:45:03 +0100 Subject: [PATCH 187/884] diff --color-words -U0: fix the location of hunk headers Colored word diff without context lines firstly printed all the hunk headers among each other and then printed the diff. This was due to the code relying on getting at least one context line at the end of each hunk, where the colored words would be flushed (it is done that way to be able to ignore rewrapped lines). Noticed by Markus Heidelberg. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- diff.c | 6 ++++++ t/t4034-diff-words.sh | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/diff.c b/diff.c index e1be189742..b7ecfe3b17 100644 --- a/diff.c +++ b/diff.c @@ -656,6 +656,12 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) for (i = 0; i < len && line[i] == '@'; i++) ; if (2 <= i && i < len && line[i] == ' ') { + /* flush --color-words even for --unified=0 */ + if (ecbdata->diff_words && + (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size)) + diff_words_show(ecbdata->diff_words); + ecbdata->nparents = i - 1; len = sane_truncate_line(ecbdata, line, len); emit_line(ecbdata->file, diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index 82240cf44d..21db6e95c4 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -77,7 +77,7 @@ cat > expect <<\EOF aeff = aeff * ( aaa ) EOF -test_expect_failure 'word diff without context' ' +test_expect_success 'word diff without context' ' word_diff --color-words --unified=0 From 0cc5691a8b05a7eabdeef520c94b1bb3bcac7874 Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Fri, 30 Oct 2009 18:20:28 +0100 Subject: [PATCH 188/884] Don't create the $GIT_DIR/branches directory on init Git itself does not even look at this directory. Any tools that actually needs it should create it itself. Signed-off-by: Robin Rosenberg Signed-off-by: Junio C Hamano --- templates/branches-- | 1 - 1 file changed, 1 deletion(-) delete mode 100644 templates/branches-- diff --git a/templates/branches-- b/templates/branches-- deleted file mode 100644 index fae88709a6..0000000000 --- a/templates/branches-- +++ /dev/null @@ -1 +0,0 @@ -: this is just to ensure the directory exists. From 500348aa6859e436a890f5f5a7e0eeea8ef6c1de Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 30 Oct 2009 15:05:52 -0400 Subject: [PATCH 189/884] ls-files: unbreak "ls-files -i" Commit b5227d8 changed the behavior of "ls-files" with respect to includes, but accidentally broke the "-i" option The original behavior was: 1. if no "-i" is given, cull all results according to --exclude* 2. if "-i" is given, show the inverse of (1) The broken behavior was: 1. if no "-i" is given: a. for "-o", cull results according to --exclude* b. for index files, always show all 2. if "-i" is given: a. for "-o", shows the inverse of (1a) b. for index files, always show all The fixed behavior keeps the new (1b) behavior introduced by b5227d8, but fixes the (2b) behavior to show only ignored files, not all files. This patch also tweaks the documentation. The original text was somewhat obscure in the first place, but it is also now inaccurate (the relationship between (1b) and (2b) is not quite a "reverse"). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-ls-files.txt | 6 ++++-- builtin-ls-files.c | 8 ++++++++ t/t3003-ls-files-exclude.sh | 8 ++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 057a021eb5..89859153f3 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -48,8 +48,10 @@ OPTIONS -i:: --ignored:: - Show ignored files in the output. - Note that this also reverses any exclude list present. + Show only ignored files in the output. When showing files in the + index, print only those matched by an exclude pattern. When + showing "other" files, show only those matched by an exclude + pattern. -s:: --stage:: diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 16a1370647..e458a491c7 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -175,6 +175,10 @@ static void show_files(struct dir_struct *dir, const char *prefix) if (show_cached | show_stage) { for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; + int dtype = ce_to_dtype(ce); + if (dir->flags & DIR_SHOW_IGNORED && + !excluded(dir, ce->name, &dtype)) + continue; if (show_unmerged && !ce_stage(ce)) continue; if (ce->ce_flags & CE_UPDATE) @@ -187,6 +191,10 @@ static void show_files(struct dir_struct *dir, const char *prefix) struct cache_entry *ce = active_cache[i]; struct stat st; int err; + int dtype = ce_to_dtype(ce); + if (dir->flags & DIR_SHOW_IGNORED && + !excluded(dir, ce->name, &dtype)) + continue; if (ce->ce_flags & CE_UPDATE) continue; err = lstat(ce->name, &st); diff --git a/t/t3003-ls-files-exclude.sh b/t/t3003-ls-files-exclude.sh index fc1e379321..d5ec333131 100755 --- a/t/t3003-ls-files-exclude.sh +++ b/t/t3003-ls-files-exclude.sh @@ -29,4 +29,12 @@ test_expect_success 'add file to gitignore' ' ' check_all_output +test_expect_success 'ls-files -i lists only tracked-but-ignored files' ' + echo content >other-file && + git add other-file && + echo file >expect && + git ls-files -i --exclude-standard >output && + test_cmp expect output +' + test_done From 492cf3f72f9d8a0308cc42a9ffc00ef782c88a20 Mon Sep 17 00:00:00 2001 From: Gisle Aas Date: Thu, 29 Oct 2009 22:29:35 +0100 Subject: [PATCH 190/884] More precise description of 'git describe --abbrev' Also adds a note about why the output in the examples might give different output today. Signed-off-by: Gisle Aas Signed-off-by: Junio C Hamano --- Documentation/git-describe.txt | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index b231dbb947..e9dbca7d87 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -44,7 +44,9 @@ OPTIONS --abbrev=:: Instead of using the default 7 hexadecimal digits as the - abbreviated object name, use digits. + abbreviated object name, use digits, or as many digits + as needed to form a unique object name. An of 0 + will suppress long format, only showing the closest tag. --candidates=:: Instead of considering only the 10 most recent tags as @@ -68,8 +70,8 @@ OPTIONS This is useful when you want to see parts of the commit object name in "describe" output, even when the commit in question happens to be a tagged version. Instead of just emitting the tag name, it will - describe such a commit as v1.2-0-deadbeef (0th commit since tag v1.2 - that points at object deadbeef....). + describe such a commit as v1.2-0-gdeadbee (0th commit since tag v1.2 + that points at object deadbee....). --match :: Only consider tags matching the given pattern (can be used to avoid @@ -108,7 +110,7 @@ the output shows the reference path as well: [torvalds@g5 git]$ git describe --all --abbrev=4 v1.0.5^2 tags/v1.0.0-21-g975b - [torvalds@g5 git]$ git describe --all HEAD^ + [torvalds@g5 git]$ git describe --all --abbrev=4 HEAD^ heads/lt/describe-7-g975b With --abbrev set to 0, the command can be used to find the @@ -117,6 +119,13 @@ closest tagname without any suffix: [torvalds@g5 git]$ git describe --abbrev=0 v1.0.5^2 tags/v1.0.0 +Note that the suffix you get if you type these commands today may be +longer than what Linus saw above when he ran this command, as your +git repository may have new commits whose object names begin with +975b that did not exist back then, and "-g975b" suffix alone may not +be sufficient to disambiguate these commits. + + SEARCH STRATEGY --------------- From ab3d175f87767c377cd65a06d96c50a316a28e30 Mon Sep 17 00:00:00 2001 From: Michael J Gruber Date: Thu, 29 Oct 2009 16:26:20 +0100 Subject: [PATCH 191/884] Make t9150 and t9151 test scripts executable so that they can be run individually as (cd t && ./t9150-svk-mergetickets.sh) etc. just like all other test scripts. Signed-off-by: Michael J Gruber Signed-off-by: Junio C Hamano --- t/t9150-svk-mergetickets.sh | 0 t/t9151-svn-mergeinfo.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 t/t9150-svk-mergetickets.sh mode change 100644 => 100755 t/t9151-svn-mergeinfo.sh diff --git a/t/t9150-svk-mergetickets.sh b/t/t9150-svk-mergetickets.sh old mode 100644 new mode 100755 diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh old mode 100644 new mode 100755 From 15c6bf0df4d9f060b5cae7a9a899dc3601c54353 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 30 Oct 2009 16:10:17 -0400 Subject: [PATCH 192/884] t915{0,1}: use $TEST_DIRECTORY Because --root can put our trash directories elsewhere, using ".." may not always work. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- t/t9150-svk-mergetickets.sh | 3 ++- t/t9151-svn-mergeinfo.sh | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/t/t9150-svk-mergetickets.sh b/t/t9150-svk-mergetickets.sh index 8000c347b4..dd0c2bad24 100755 --- a/t/t9150-svk-mergetickets.sh +++ b/t/t9150-svk-mergetickets.sh @@ -8,7 +8,8 @@ test_description='git-svn svk merge tickets' . ./lib-git-svn.sh test_expect_success 'load svk depot' " - svnadmin load -q '$rawsvnrepo' < '../t9150/svk-merge.dump' && + svnadmin load -q '$rawsvnrepo' \ + < '$TEST_DIRECTORY/t9150/svk-merge.dump' && git svn init --minimize-url -R svkmerge \ -T trunk -b branches '$svnrepo' && git svn fetch --all diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh index 7eb36e599c..9bee516358 100755 --- a/t/t9151-svn-mergeinfo.sh +++ b/t/t9151-svn-mergeinfo.sh @@ -8,7 +8,8 @@ test_description='git-svn svn mergeinfo properties' . ./lib-git-svn.sh test_expect_success 'load svn dump' " - svnadmin load -q '$rawsvnrepo' < '../t9151/svn-mergeinfo.dump' && + svnadmin load -q '$rawsvnrepo' \ + < '$TEST_DIRECTORY/t9151/svn-mergeinfo.dump' && git svn init --minimize-url -R svnmerge \ -T trunk -b branches '$svnrepo' && git svn fetch --all From f740cc25298e2a0eb7c561c40854ed568c7de657 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 30 Oct 2009 11:04:53 -0400 Subject: [PATCH 193/884] push: fix typo in usage Missing ")". Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-push.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-push.c b/builtin-push.c index b5cd2cdad0..8631c06ed6 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -181,7 +181,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL), OPT_BIT( 0 , "mirror", &flags, "mirror all refs", (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)), - OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror"), + OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"), OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE), From ebc9d420566de32dbadada5a4f700db2d974853c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 30 Oct 2009 15:15:36 -0700 Subject: [PATCH 194/884] clone: fix help on options Fix incorrect description of --recursive, and stop listing the historical synonym --naked that is not advertised anywhere. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-clone.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/builtin-clone.c b/builtin-clone.c index 5762a6f9d8..0a4f6124f5 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -51,7 +51,9 @@ static struct option builtin_clone_options[] = { OPT_BOOLEAN('n', "no-checkout", &option_no_checkout, "don't create a checkout"), OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"), - OPT_BOOLEAN(0, "naked", &option_bare, "create a bare repository"), + { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL, + "create a bare repository", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_BOOLEAN(0, "mirror", &option_mirror, "create a mirror repository (implies bare)"), OPT_BOOLEAN('l', "local", &option_local, @@ -61,7 +63,7 @@ static struct option builtin_clone_options[] = { OPT_BOOLEAN('s', "shared", &option_shared, "setup as shared repository"), OPT_BOOLEAN(0, "recursive", &option_recursive, - "setup as shared repository"), + "initialize submodules in the clone"), OPT_STRING(0, "template", &option_template, "path", "path the template repository"), OPT_STRING(0, "reference", &option_reference, "repo", From d52dc4b10b2f78dc24ea05e88ddc25ee0f46491e Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Thu, 29 Oct 2009 03:10:30 -0500 Subject: [PATCH 195/884] clone: detect extra arguments If git clone is given more than two non-option arguments, it silently throws away all but the first one. Complain instead. Discovered by comparing the new builtin clone to the old git-clone.sh. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-clone.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/builtin-clone.c b/builtin-clone.c index 0a4f6124f5..caf3025031 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -379,8 +379,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); + if (argc > 2) + usage_msg_opt("Too many arguments.", + builtin_clone_usage, builtin_clone_options); + if (argc == 0) - die("You must specify a repository to clone."); + usage_msg_opt("You must specify a repository to clone.", + builtin_clone_usage, builtin_clone_options); if (option_mirror) option_bare = 1; From 134748353b2a71a34f899c9b1326ccf7ae082412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Thu, 29 Oct 2009 23:08:31 +0100 Subject: [PATCH 196/884] Teach 'git merge' and 'git pull' the option --ff-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For convenience in scripts and aliases, add the option --ff-only to only allow fast-forwards (and up-to-date, despite the name). Disallow combining --ff-only and --no-ff, since they flatly contradict each other. Allow all other options to be combined with --ff-only (i.e. do not add any code to handle them specially), including the following options: * --strategy (one or more): As long as the chosen merge strategy results in up-to-date or fast-forward, the command will succeed. * --squash: I cannot imagine why anyone would want to squash commits only if fast-forward is possible, but I also see no reason why it should not be allowed. * --message: The message will always be ignored, but I see no need to explicitly disallow providing a redundant message. Acknowledgements: I did look at Yuval Kogman's earlier patch (107768 in gmane), mainly as shortcut to find my way in the code, but I did not copy anything directly. Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano --- Documentation/merge-options.txt | 5 ++++ builtin-merge.c | 11 ++++++++- git-pull.sh | 7 ++++-- t/t7600-merge.sh | 41 +++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index adadf8e4bf..27a9a8489c 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -60,6 +60,11 @@ a fast-forward, only update the branch pointer. This is the default behavior of git-merge. +--ff-only:: + Refuse to merge and exit with a non-zero status unless the + current `HEAD` is already up-to-date or the merge can be + resolved as a fast-forward. + -s :: --strategy=:: Use the given merge strategy; can be supplied more than diff --git a/builtin-merge.c b/builtin-merge.c index b6b84286b2..5e8c4b5e4b 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -43,6 +43,7 @@ static const char * const builtin_merge_usage[] = { static int show_diffstat = 1, option_log, squash; static int option_commit = 1, allow_fast_forward = 1; +static int fast_forward_only; static int allow_trivial = 1, have_message; static struct strbuf merge_msg; static struct commit_list *remoteheads; @@ -167,6 +168,8 @@ static struct option builtin_merge_options[] = { "perform a commit if the merge succeeds (default)"), OPT_BOOLEAN(0, "ff", &allow_fast_forward, "allow fast forward (default)"), + OPT_BOOLEAN(0, "ff-only", &fast_forward_only, + "abort if fast forward is not possible"), OPT_CALLBACK('s', "strategy", &use_strategies, "strategy", "merge strategy to use", option_parse_strategy), OPT_CALLBACK('m', "message", &merge_msg, "message", @@ -874,6 +877,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) option_commit = 0; } + if (!allow_fast_forward && fast_forward_only) + die("You cannot combine --no-ff with --ff-only."); + if (!argc) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -1040,7 +1046,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * only one common. */ refresh_cache(REFRESH_QUIET); - if (allow_trivial) { + if (allow_trivial && !fast_forward_only) { /* See if it is really trivial. */ git_committer_info(IDENT_ERROR_ON_NO_NAME); printf("Trying really trivial in-index merge...\n"); @@ -1079,6 +1085,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } } + if (fast_forward_only) + die("Not possible to fast forward, aborting."); + /* We are going to make a new commit. */ git_committer_info(IDENT_ERROR_ON_NO_NAME); diff --git a/git-pull.sh b/git-pull.sh index fc78592ae0..37f3d93017 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -16,7 +16,8 @@ cd_to_toplevel test -z "$(git ls-files -u)" || die "You are in the middle of a conflicted merge." -strategy_args= diffstat= no_commit= squash= no_ff= log_arg= verbosity= +strategy_args= diffstat= no_commit= squash= no_ff= ff_only= +log_arg= verbosity= curr_branch=$(git symbolic-ref -q HEAD) curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||") rebase=$(git config --bool branch.$curr_branch_short.rebase) @@ -45,6 +46,8 @@ do no_ff=--ff ;; --no-ff) no_ff=--no-ff ;; + --ff-only) + ff_only=--ff-only ;; -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ --strateg=*|--strategy=*|\ -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) @@ -215,5 +218,5 @@ merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit test true = "$rebase" && exec git-rebase $diffstat $strategy_args --onto $merge_head \ ${oldremoteref:-$merge_head} -exec git-merge $diffstat $no_commit $squash $no_ff $log_arg $strategy_args \ +exec git-merge $diffstat $no_commit $squash $no_ff $ff_only $log_arg $strategy_args \ "$merge_name" HEAD $merge_head $verbosity diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index e5b210bc96..57f6d2bae7 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -243,6 +243,16 @@ test_expect_success 'merge c0 with c1' ' test_debug 'gitk --all' +test_expect_success 'merge c0 with c1 with --ff-only' ' + git reset --hard c0 && + git merge --ff-only c1 && + git merge --ff-only HEAD c0 c1 && + verify_merge file result.1 && + verify_head "$c1" +' + +test_debug 'gitk --all' + test_expect_success 'merge c1 with c2' ' git reset --hard c1 && test_tick && @@ -263,6 +273,14 @@ test_expect_success 'merge c1 with c2 and c3' ' test_debug 'gitk --all' +test_expect_success 'failing merges with --ff-only' ' + git reset --hard c1 && + test_tick && + test_must_fail git merge --ff-only c2 && + test_must_fail git merge --ff-only c3 && + test_must_fail git merge --ff-only c2 c3 +' + test_expect_success 'merge c0 with c1 (no-commit)' ' git reset --hard c0 && git merge --no-commit c1 && @@ -303,6 +321,17 @@ test_expect_success 'merge c0 with c1 (squash)' ' test_debug 'gitk --all' +test_expect_success 'merge c0 with c1 (squash, ff-only)' ' + git reset --hard c0 && + git merge --squash --ff-only c1 && + verify_merge file result.1 && + verify_head $c0 && + verify_no_mergehead && + verify_diff squash.1 .git/SQUASH_MSG "[OOPS] bad squash message" +' + +test_debug 'gitk --all' + test_expect_success 'merge c1 with c2 (squash)' ' git reset --hard c1 && git merge --squash c2 && @@ -314,6 +343,13 @@ test_expect_success 'merge c1 with c2 (squash)' ' test_debug 'gitk --all' +test_expect_success 'unsuccesful merge of c1 with c2 (squash, ff-only)' ' + git reset --hard c1 && + test_must_fail git merge --squash --ff-only c2 +' + +test_debug 'gitk --all' + test_expect_success 'merge c1 with c2 and c3 (squash)' ' git reset --hard c1 && git merge --squash c2 c3 && @@ -432,6 +468,11 @@ test_expect_success 'combining --squash and --no-ff is refused' ' test_must_fail git merge --no-ff --squash c1 ' +test_expect_success 'combining --ff-only and --no-ff is refused' ' + test_must_fail git merge --ff-only --no-ff c1 && + test_must_fail git merge --no-ff --ff-only c1 +' + test_expect_success 'merge c0 with c1 (ff overrides no-ff)' ' git reset --hard c0 && git config branch.master.mergeoptions "--no-ff" && From 46e09f310567b680c03151e048bf2b7e847611e2 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 29 Oct 2009 23:07:41 +0100 Subject: [PATCH 197/884] t/gitweb-lib.sh: Split gitweb output into headers and body Save HTTP headers into gitweb.headers, and the body of message into gitweb.body in gitweb_run() Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- t/gitweb-lib.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh index 845253274b..32b841dd2e 100644 --- a/t/gitweb-lib.sh +++ b/t/gitweb-lib.sh @@ -52,10 +52,14 @@ gitweb_run () { rm -f gitweb.log && perl -- "$SCRIPT_NAME" \ >gitweb.output 2>gitweb.log && + sed -e '/^\r$/q' gitweb.headers && + sed -e '1,/^\r$/d' gitweb.body && if grep '^[[]' gitweb.log >/dev/null 2>&1; then false; else true; fi # gitweb.log is left for debugging - # gitweb.output is used to parse http output + # gitweb.output is used to parse HTTP output + # gitweb.headers contains only HTTP headers + # gitweb.body contains body of message, without headers } . ./test-lib.sh From eab58f1e8e5ef86b5075ce6dfcd6d3f1b3b888b3 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Fri, 30 Oct 2009 20:24:04 -0500 Subject: [PATCH 198/884] Handle more shell metacharacters in editor names Pass the editor name to the shell if it contains any susv3 shell special character (globs, redirections, variable substitutions, escapes, etc). This way, the meaning of some characters will not meaninglessly change when others are added, and git commands implemented in C and in shell scripts will interpret editor names in the same way. This does not make the GIT_EDITOR setting any more expressive, since one could always use single quotes to force the editor to be passed to the shell. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- editor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor.c b/editor.c index 4d469d076b..941c0b2ab6 100644 --- a/editor.c +++ b/editor.c @@ -28,7 +28,7 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en const char *args[6]; struct strbuf arg0 = STRBUF_INIT; - if (strcspn(editor, "$ \t'") != len) { + if (strcspn(editor, "|&;<>()$`\\\"' \t\n*?[#~=%") != len) { /* there are specials */ strbuf_addf(&arg0, "%s \"$@\"", editor); args[i++] = "sh"; From 609621a4ad8144e56df6e5f811eb037661967f49 Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Fri, 30 Oct 2009 17:47:20 -0700 Subject: [PATCH 199/884] http-push: fix check condition on http.c::finish_http_pack_request() Check that http.c::finish_http_pack_request() returns 0 (for success). Signed-off-by: Tay Ray Chuan Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- http-push.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-push.c b/http-push.c index 00e83dcec1..cc5d4b8c32 100644 --- a/http-push.c +++ b/http-push.c @@ -604,7 +604,7 @@ static void finish_request(struct transfer_request *request) preq = (struct http_pack_request *)request->userData; if (preq) { - if (finish_http_pack_request(preq) > 0) + if (finish_http_pack_request(preq) == 0) fail = 0; release_http_pack_request(preq); } From f5615d246770a2796e60b06dd5e17f5e79d5dd0c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:21 -0700 Subject: [PATCH 200/884] pkt-line: Add strbuf based functions These routines help to work with pkt-line values inside of a strbuf, permitting simple formatting of buffered network messages. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- pkt-line.c | 86 +++++++++++++++++++++++++++++++++++++++++++++--------- pkt-line.h | 4 +++ 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/pkt-line.c b/pkt-line.c index b691abebd7..bd603f8721 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -42,17 +42,19 @@ void packet_flush(int fd) safe_write(fd, "0000", 4); } -#define hex(a) (hexchar[(a) & 15]) -void packet_write(int fd, const char *fmt, ...) +void packet_buf_flush(struct strbuf *buf) +{ + strbuf_add(buf, "0000", 4); +} + +#define hex(a) (hexchar[(a) & 15]) +static char buffer[1000]; +static unsigned format_packet(const char *fmt, va_list args) { - static char buffer[1000]; static char hexchar[] = "0123456789abcdef"; - va_list args; unsigned n; - va_start(args, fmt); n = vsnprintf(buffer + 4, sizeof(buffer) - 4, fmt, args); - va_end(args); if (n >= sizeof(buffer)-4) die("protocol error: impossibly long line"); n += 4; @@ -60,9 +62,31 @@ void packet_write(int fd, const char *fmt, ...) buffer[1] = hex(n >> 8); buffer[2] = hex(n >> 4); buffer[3] = hex(n); + return n; +} + +void packet_write(int fd, const char *fmt, ...) +{ + va_list args; + unsigned n; + + va_start(args, fmt); + n = format_packet(fmt, args); + va_end(args); safe_write(fd, buffer, n); } +void packet_buf_write(struct strbuf *buf, const char *fmt, ...) +{ + va_list args; + unsigned n; + + va_start(args, fmt); + n = format_packet(fmt, args); + va_end(args); + strbuf_add(buf, buffer, n); +} + static void safe_read(int fd, void *buffer, unsigned size) { ssize_t ret = read_in_full(fd, buffer, size); @@ -72,15 +96,11 @@ static void safe_read(int fd, void *buffer, unsigned size) die("The remote end hung up unexpectedly"); } -int packet_read_line(int fd, char *buffer, unsigned size) +static int packet_length(const char *linelen) { int n; - unsigned len; - char linelen[4]; + int len = 0; - safe_read(fd, linelen, 4); - - len = 0; for (n = 0; n < 4; n++) { unsigned char c = linelen[n]; len <<= 4; @@ -96,8 +116,20 @@ int packet_read_line(int fd, char *buffer, unsigned size) len += c - 'A' + 10; continue; } - die("protocol error: bad line length character"); + return -1; } + return len; +} + +int packet_read_line(int fd, char *buffer, unsigned size) +{ + int len; + char linelen[4]; + + safe_read(fd, linelen, 4); + len = packet_length(linelen); + if (len < 0) + die("protocol error: bad line length character"); if (!len) return 0; len -= 4; @@ -107,3 +139,31 @@ int packet_read_line(int fd, char *buffer, unsigned size) buffer[len] = 0; return len; } + +int packet_get_line(struct strbuf *out, + char **src_buf, size_t *src_len) +{ + int len; + + if (*src_len < 4) + return -1; + len = packet_length(*src_buf); + if (len < 0) + return -1; + if (!len) { + *src_buf += 4; + *src_len -= 4; + return 0; + } + if (*src_len < len) + return -2; + + *src_buf += 4; + *src_len -= 4; + len -= 4; + + strbuf_add(out, *src_buf, len); + *src_buf += len; + *src_len -= len; + return len; +} diff --git a/pkt-line.h b/pkt-line.h index 9df653f6f5..1e5dcfe87c 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -2,14 +2,18 @@ #define PKTLINE_H #include "git-compat-util.h" +#include "strbuf.h" /* * Silly packetized line writing interface */ void packet_flush(int fd); void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); +void packet_buf_flush(struct strbuf *buf); +void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); int packet_read_line(int fd, char *buffer, unsigned size); +int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len); ssize_t safe_write(int, const void *, ssize_t); #endif From 743c4b7b0fa5422d1bb5dc59c6cb919a881dd98b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:22 -0700 Subject: [PATCH 201/884] pkt-line: Make packet_read_line easier to debug When there is an error parsing the 4 byte length component we now display it as part of the die message, this may hint as to what data was misunderstood by the application. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- pkt-line.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkt-line.c b/pkt-line.c index bd603f8721..295ba2b16c 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -129,7 +129,7 @@ int packet_read_line(int fd, char *buffer, unsigned size) safe_read(fd, linelen, 4); len = packet_length(linelen); if (len < 0) - die("protocol error: bad line length character"); + die("protocol error: bad line length character: %.4s", linelen); if (!len) return 0; len -= 4; From edace6f02eeae6f4a06ed1e4f6308703523d8535 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:23 -0700 Subject: [PATCH 202/884] fetch-pack: Use a strbuf to compose the want list This change is being offered as a refactoring to make later commits in the smart HTTP series easier. By changing the enabled capabilities to be formatted in a strbuf it is easier to add a new capability to the set of supported capabilities. By formatting the want portion of the request into a strbuf and writing it as a whole block we can later decide to hold onto the req_buf (instead of releasing it) to recycle in stateless communications. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-fetch-pack.c | 54 ++++++++++++++++++++++++++++---------------- commit.c | 10 ++++---- commit.h | 2 +- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 629735f547..783c2b0834 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -165,6 +165,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, const unsigned char *sha1; unsigned in_vain = 0; int got_continue = 0; + struct strbuf req_buf = STRBUF_INIT; if (marked) for_each_ref(clear_marks, NULL); @@ -175,6 +176,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, fetching = 0; for ( ; refs ; refs = refs->next) { unsigned char *remote = refs->old_sha1; + const char *remote_hex; struct object *o; /* @@ -192,27 +194,36 @@ static int find_common(int fd[2], unsigned char *result_sha1, continue; } - if (!fetching) - packet_write(fd[1], "want %s%s%s%s%s%s%s%s\n", - sha1_to_hex(remote), - (multi_ack ? " multi_ack" : ""), - (use_sideband == 2 ? " side-band-64k" : ""), - (use_sideband == 1 ? " side-band" : ""), - (args.use_thin_pack ? " thin-pack" : ""), - (args.no_progress ? " no-progress" : ""), - (args.include_tag ? " include-tag" : ""), - (prefer_ofs_delta ? " ofs-delta" : "")); - else - packet_write(fd[1], "want %s\n", sha1_to_hex(remote)); + remote_hex = sha1_to_hex(remote); + if (!fetching) { + struct strbuf c = STRBUF_INIT; + if (multi_ack) strbuf_addstr(&c, " multi_ack"); + if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k"); + if (use_sideband == 1) strbuf_addstr(&c, " side-band"); + if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack"); + if (args.no_progress) strbuf_addstr(&c, " no-progress"); + if (args.include_tag) strbuf_addstr(&c, " include-tag"); + if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta"); + packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf); + strbuf_release(&c); + } else + packet_buf_write(&req_buf, "want %s\n", remote_hex); fetching++; } - if (is_repository_shallow()) - write_shallow_commits(fd[1], 1); - if (args.depth > 0) - packet_write(fd[1], "deepen %d", args.depth); - packet_flush(fd[1]); - if (!fetching) + + if (!fetching) { + strbuf_release(&req_buf); + packet_flush(fd[1]); return 1; + } + + if (is_repository_shallow()) + write_shallow_commits(&req_buf, 1); + if (args.depth > 0) + packet_buf_write(&req_buf, "deepen %d", args.depth); + packet_buf_flush(&req_buf); + + safe_write(fd[1], req_buf.buf, req_buf.len); if (args.depth > 0) { char line[1024]; @@ -296,6 +307,8 @@ done: multi_ack = 0; flushes++; } + strbuf_release(&req_buf); + while (flushes || multi_ack) { int ack = get_ack(fd[0], result_sha1); if (ack) { @@ -809,6 +822,7 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, if (args.depth > 0) { struct cache_time mtime; + struct strbuf sb = STRBUF_INIT; char *shallow = git_path("shallow"); int fd; @@ -826,12 +840,14 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, fd = hold_lock_file_for_update(&lock, shallow, LOCK_DIE_ON_ERROR); - if (!write_shallow_commits(fd, 0)) { + if (!write_shallow_commits(&sb, 0) + || write_in_full(fd, sb.buf, sb.len) != sb.len) { unlink_or_warn(shallow); rollback_lock_file(&lock); } else { commit_lock_file(&lock); } + strbuf_release(&sb); } reprepare_packed_git(); diff --git a/commit.c b/commit.c index fedbd5e526..471efb0566 100644 --- a/commit.c +++ b/commit.c @@ -199,7 +199,7 @@ struct commit_graft *lookup_commit_graft(const unsigned char *sha1) return commit_graft[pos]; } -int write_shallow_commits(int fd, int use_pack_protocol) +int write_shallow_commits(struct strbuf *out, int use_pack_protocol) { int i, count = 0; for (i = 0; i < commit_graft_nr; i++) @@ -208,12 +208,10 @@ int write_shallow_commits(int fd, int use_pack_protocol) sha1_to_hex(commit_graft[i]->sha1); count++; if (use_pack_protocol) - packet_write(fd, "shallow %s", hex); + packet_buf_write(out, "shallow %s", hex); else { - if (write_in_full(fd, hex, 40) != 40) - break; - if (write_str_in_full(fd, "\n") != 1) - break; + strbuf_addstr(out, hex); + strbuf_addch(out, '\n'); } } return count; diff --git a/commit.h b/commit.h index f4fc5c5589..817c75c02f 100644 --- a/commit.h +++ b/commit.h @@ -131,7 +131,7 @@ extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); extern int register_shallow(const unsigned char *sha1); extern int unregister_shallow(const unsigned char *sha1); -extern int write_shallow_commits(int fd, int use_pack_protocol); +extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol); extern int is_repository_shallow(void); extern struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag); From 28754ab5f0ce9b4f6ca1641c3e10e2c68bd9b3fc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:24 -0700 Subject: [PATCH 203/884] Move "get_ack()" back to fetch-pack In 41cb7488 Linus moved this function to connect.c for reuse inside of the git-clone-pack command. That was 2005, but in 2006 Junio retired git-clone-pack in commit efc7fa53. Since then the only caller has been fetch-pack. Since this ACK/NAK exchange is only used by the fetch-pack/upload-pack protocol we should move it back to be a private detail of fetch-pack. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-fetch-pack.c | 21 +++++++++++++++++++++ cache.h | 1 - connect.c | 21 --------------------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 783c2b0834..7c09d466c8 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -157,6 +157,27 @@ static const unsigned char *get_rev(void) return commit->object.sha1; } +static int get_ack(int fd, unsigned char *result_sha1) +{ + static char line[1000]; + int len = packet_read_line(fd, line, sizeof(line)); + + if (!len) + die("git fetch-pack: expected ACK/NAK, got EOF"); + if (line[len-1] == '\n') + line[--len] = 0; + if (!strcmp(line, "NAK")) + return 0; + if (!prefixcmp(line, "ACK ")) { + if (!get_sha1_hex(line+4, result_sha1)) { + if (strstr(line+45, "continue")) + return 2; + return 1; + } + } + die("git fetch_pack: expected ACK/NAK, got '%s'", line); +} + static int find_common(int fd[2], unsigned char *result_sha1, struct ref *refs) { diff --git a/cache.h b/cache.h index a5eeead1e2..4e283be24e 100644 --- a/cache.h +++ b/cache.h @@ -856,7 +856,6 @@ extern struct ref *find_ref_by_name(const struct ref *list, const char *name); extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); extern int finish_connect(struct child_process *conn); extern int path_match(const char *path, int nr, char **match); -extern int get_ack(int fd, unsigned char *result_sha1); struct extra_have_objects { int nr, alloc; unsigned char (*array)[20]; diff --git a/connect.c b/connect.c index 7945e38ac1..839a103705 100644 --- a/connect.c +++ b/connect.c @@ -107,27 +107,6 @@ int server_supports(const char *feature) strstr(server_capabilities, feature) != NULL; } -int get_ack(int fd, unsigned char *result_sha1) -{ - static char line[1000]; - int len = packet_read_line(fd, line, sizeof(line)); - - if (!len) - die("git fetch-pack: expected ACK/NAK, got EOF"); - if (line[len-1] == '\n') - line[--len] = 0; - if (!strcmp(line, "NAK")) - return 0; - if (!prefixcmp(line, "ACK ")) { - if (!get_sha1_hex(line+4, result_sha1)) { - if (strstr(line+45, "continue")) - return 2; - return 1; - } - } - die("git fetch_pack: expected ACK/NAK, got '%s'", line); -} - int path_match(const char *path, int nr, char **match) { int i; From 78affc49ded26700450d25168534af02b29a6c61 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:25 -0700 Subject: [PATCH 204/884] Add multi_ack_detailed capability to fetch-pack/upload-pack When multi_ack_detailed is enabled the ACK continue messages returned by the remote upload-pack are broken out to describe the different states within the peer. This permits the client to better understand the server's in-memory state. The fetch-pack/upload-pack protocol now looks like: NAK --------------------------------- Always sent in response to "done" if there was no common base selected from the "have" lines (or no have lines were sent). * no multi_ack or multi_ack_detailed: Sent when the client has sent a pkt-line flush ("0000") and the server has not yet found a common base object. * either multi_ack or multi_ack_detailed: Always sent in response to a pkt-line flush. ACK %s ----------------------------------- * no multi_ack or multi_ack_detailed: Sent in response to "have" when the object exists on the remote side and is therefore an object in common between the peers. The argument is the SHA-1 of the common object. * either multi_ack or multi_ack_detailed: Sent in response to "done" if there are common objects. The argument is the last SHA-1 determined to be common. ACK %s continue ----------------------------------- * multi_ack only: Sent in response to "have". The remote side wants the client to consider this object as common, and immediately stop transmitting additional "have" lines for objects that are reachable from it. The reason the client should stop is not given, but is one of the two cases below available under multi_ack_detailed. ACK %s common ----------------------------------- * multi_ack_detailed only: Sent in response to "have". Both sides have this object. Like with "ACK %s continue" above the client should stop sending have lines reachable for objects from the argument. ACK %s ready ----------------------------------- * multi_ack_detailed only: Sent in response to "have". The client should stop transmitting objects which are reachable from the argument, and send "done" soon to get the objects. If the remote side has the specified object, it should first send an "ACK %s common" message prior to sending "ACK %s ready". Clients may still submit additional "have" lines if there are more side branches for the client to explore that might be added to the common set and reduce the number of objects to transfer. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-fetch-pack.c | 41 ++++++++++++++++++++++++++++++++--------- upload-pack.c | 31 ++++++++++++++++++------------- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 7c09d466c8..615f54974e 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -157,7 +157,15 @@ static const unsigned char *get_rev(void) return commit->object.sha1; } -static int get_ack(int fd, unsigned char *result_sha1) +enum ack_type { + NAK = 0, + ACK, + ACK_continue, + ACK_common, + ACK_ready +}; + +static enum ack_type get_ack(int fd, unsigned char *result_sha1) { static char line[1000]; int len = packet_read_line(fd, line, sizeof(line)); @@ -167,12 +175,16 @@ static int get_ack(int fd, unsigned char *result_sha1) if (line[len-1] == '\n') line[--len] = 0; if (!strcmp(line, "NAK")) - return 0; + return NAK; if (!prefixcmp(line, "ACK ")) { if (!get_sha1_hex(line+4, result_sha1)) { if (strstr(line+45, "continue")) - return 2; - return 1; + return ACK_continue; + if (strstr(line+45, "common")) + return ACK_common; + if (strstr(line+45, "ready")) + return ACK_ready; + return ACK; } } die("git fetch_pack: expected ACK/NAK, got '%s'", line); @@ -218,7 +230,8 @@ static int find_common(int fd[2], unsigned char *result_sha1, remote_hex = sha1_to_hex(remote); if (!fetching) { struct strbuf c = STRBUF_INIT; - if (multi_ack) strbuf_addstr(&c, " multi_ack"); + if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed"); + if (multi_ack == 1) strbuf_addstr(&c, " multi_ack"); if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k"); if (use_sideband == 1) strbuf_addstr(&c, " side-band"); if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack"); @@ -298,18 +311,23 @@ static int find_common(int fd[2], unsigned char *result_sha1, if (args.verbose && ack) fprintf(stderr, "got ack %d %s\n", ack, sha1_to_hex(result_sha1)); - if (ack == 1) { + switch (ack) { + case ACK: flushes = 0; multi_ack = 0; retval = 0; goto done; - } else if (ack == 2) { + case ACK_common: + case ACK_ready: + case ACK_continue: { struct commit *commit = lookup_commit(result_sha1); mark_common(commit, 0, 1); retval = 0; in_vain = 0; got_continue = 1; + break; + } } } while (ack); flushes--; @@ -336,7 +354,7 @@ done: if (args.verbose) fprintf(stderr, "got ack (%d) %s\n", ack, sha1_to_hex(result_sha1)); - if (ack == 1) + if (ack == ACK) return 0; multi_ack = 1; continue; @@ -618,7 +636,12 @@ static struct ref *do_fetch_pack(int fd[2], if (is_repository_shallow() && !server_supports("shallow")) die("Server does not support shallow clients"); - if (server_supports("multi_ack")) { + if (server_supports("multi_ack_detailed")) { + if (args.verbose) + fprintf(stderr, "Server supports multi_ack_detailed\n"); + multi_ack = 2; + } + else if (server_supports("multi_ack")) { if (args.verbose) fprintf(stderr, "Server supports multi_ack\n"); multi_ack = 1; diff --git a/upload-pack.c b/upload-pack.c index 38ddac2e86..f1dc3a351c 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -498,7 +498,7 @@ static int get_common_commits(void) { static char line[1000]; unsigned char sha1[20]; - char hex[41], last_hex[41]; + char last_hex[41]; save_commit_buffer = 0; @@ -515,19 +515,22 @@ static int get_common_commits(void) if (!prefixcmp(line, "have ")) { switch (got_sha1(line+5, sha1)) { case -1: /* they have what we do not */ - if (multi_ack && ok_to_give_up()) - packet_write(1, "ACK %s continue\n", - sha1_to_hex(sha1)); + if (multi_ack && ok_to_give_up()) { + const char *hex = sha1_to_hex(sha1); + if (multi_ack == 2) + packet_write(1, "ACK %s ready\n", hex); + else + packet_write(1, "ACK %s continue\n", hex); + } break; default: - memcpy(hex, sha1_to_hex(sha1), 41); - if (multi_ack) { - const char *msg = "ACK %s continue\n"; - packet_write(1, msg, hex); - memcpy(last_hex, hex, 41); - } + memcpy(last_hex, sha1_to_hex(sha1), 41); + if (multi_ack == 2) + packet_write(1, "ACK %s common\n", last_hex); + else if (multi_ack) + packet_write(1, "ACK %s continue\n", last_hex); else if (have_obj.nr == 1) - packet_write(1, "ACK %s\n", hex); + packet_write(1, "ACK %s\n", last_hex); break; } continue; @@ -587,7 +590,9 @@ static void receive_needs(void) get_sha1_hex(line+5, sha1_buf)) die("git upload-pack: protocol error, " "expected to get sha, not '%s'", line); - if (strstr(line+45, "multi_ack")) + if (strstr(line+45, "multi_ack_detailed")) + multi_ack = 2; + else if (strstr(line+45, "multi_ack")) multi_ack = 1; if (strstr(line+45, "thin-pack")) use_thin_pack = 1; @@ -681,7 +686,7 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo { static const char *capabilities = "multi_ack thin-pack side-band" " side-band-64k ofs-delta shallow no-progress" - " include-tag"; + " include-tag multi_ack_detailed"; struct object *o = parse_object(sha1); if (!o) From 37a8768f83d5932ca66202f9cc4977c20b022e17 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:26 -0700 Subject: [PATCH 205/884] remote-curl: Refactor walker initialization We will need the walker, url and remote in other functions as the code grows larger to support smart HTTP. Extract this out into a set of globals we can easily reference once configured. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano --- remote-curl.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/remote-curl.c b/remote-curl.c index 2faf1c6344..478f3ea7d3 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -5,7 +5,17 @@ #include "http.h" #include "exec_cmd.h" -static struct ref *get_refs(struct walker *walker, const char *url) +static struct remote *remote; +static const char *url; +static struct walker *walker; + +static void init_walker(void) +{ + if (!walker) + walker = get_http_walker(url, remote); +} + +static struct ref *get_refs(void) { struct strbuf buffer = STRBUF_INIT; char *data, *start, *mid; @@ -21,6 +31,7 @@ static struct ref *get_refs(struct walker *walker, const char *url) refs_url = xmalloc(strlen(url) + 11); sprintf(refs_url, "%s/info/refs", url); + init_walker(); http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); switch (http_ret) { case HTTP_OK: @@ -78,10 +89,7 @@ static struct ref *get_refs(struct walker *walker, const char *url) int main(int argc, const char **argv) { - struct remote *remote; struct strbuf buf = STRBUF_INIT; - const char *url; - struct walker *walker = NULL; git_extract_argv0_path(argv[0]); setup_git_directory(); @@ -103,8 +111,7 @@ int main(int argc, const char **argv) break; if (!prefixcmp(buf.buf, "fetch ")) { char *obj = buf.buf + strlen("fetch "); - if (!walker) - walker = get_http_walker(url, remote); + init_walker(); walker->get_all = 1; walker->get_tree = 1; walker->get_history = 1; @@ -115,11 +122,8 @@ int main(int argc, const char **argv) printf("\n"); fflush(stdout); } else if (!strcmp(buf.buf, "list")) { - struct ref *refs; + struct ref *refs = get_refs(); struct ref *posn; - if (!walker) - walker = get_http_walker(url, remote); - refs = get_refs(walker, url); for (posn = refs; posn; posn = posn->next) { if (posn->symref) printf("@%s %s\n", posn->symref, posn->name); From cff7123c11aa2b1a849a46028d60b4bc0ab54c51 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:27 -0700 Subject: [PATCH 206/884] fetch: Allow transport -v -v -v to set verbosity to 3 Helpers might want a higher level of verbosity than just +1 (the porcelain default setting) and +2 (-v -v). Expand the field to allow verbosity in the range -1..3. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano --- builtin-fetch.c | 2 +- transport.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin-fetch.c b/builtin-fetch.c index cb48c57ca3..52a9a42fa5 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -665,7 +665,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) transport = transport_get(remote, remote->url[0]); if (verbosity >= 2) - transport->verbose = 1; + transport->verbose = verbosity <= 3 ? verbosity : 3; if (verbosity < 0) transport->verbose = -1; if (upload_pack) diff --git a/transport.h b/transport.h index c14da6f1e5..e4e6177e26 100644 --- a/transport.h +++ b/transport.h @@ -25,7 +25,7 @@ struct transport { int (*disconnect)(struct transport *connection); char *pack_lockfile; - signed verbose : 2; + signed verbose : 3; /* Force progress even if the output is not a tty */ unsigned progress : 1; }; From 292ce46b60e2c12450c5c21044acf9c41bd837df Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:28 -0700 Subject: [PATCH 207/884] remote-helpers: Fetch more than one ref in a batch Some network protocols (e.g. native git://) are able to fetch more than one ref at a time and reduce the overall transfer cost by combining the requests into a single exchange. Instead of feeding each fetch request one at a time to the helper, feed all of them at once so the helper can decide whether or not it should batch them. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano --- Documentation/git-remote-helpers.txt | 14 +++-- remote-curl.c | 88 ++++++++++++++++++++++++---- transport-helper.c | 43 ++++++++++---- 3 files changed, 117 insertions(+), 28 deletions(-) diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index 173ee232f2..e44d821c7b 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -36,10 +36,16 @@ Commands are given by the caller on the helper's standard input, one per line. complete list, outputs a blank line. 'fetch' :: - Fetches the given object, writing the necessary objects to the - database. Outputs a blank line when the fetch is - complete. Only objects which were reported in the ref list - with a sha1 may be fetched this way. + Fetches the given object, writing the necessary objects + to the database. Fetch commands are sent in a batch, one + per line, and the batch is terminated with a blank line. + Outputs a single blank line when all fetch commands in the + same batch are complete. Only objects which were reported + in the ref list with a sha1 may be fetched this way. ++ +Optionally may output a 'lock ' line indicating a file under +GIT_DIR/objects/pack which is keeping a pack until refs can be +suitably updated. + Supported if the helper has the "fetch" capability. diff --git a/remote-curl.c b/remote-curl.c index 478f3ea7d3..22cd5c5fd2 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -87,6 +87,81 @@ static struct ref *get_refs(void) return refs; } +static int fetch_dumb(int nr_heads, struct ref **to_fetch) +{ + char **targets = xmalloc(nr_heads * sizeof(char*)); + int ret, i; + + for (i = 0; i < nr_heads; i++) + targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1)); + + init_walker(); + walker->get_all = 1; + walker->get_tree = 1; + walker->get_history = 1; + walker->get_verbosely = 0; + walker->get_recover = 0; + ret = walker_fetch(walker, nr_heads, targets, NULL, NULL); + + for (i = 0; i < nr_heads; i++) + free(targets[i]); + free(targets); + + return ret ? error("Fetch failed.") : 0; +} + +static void parse_fetch(struct strbuf *buf) +{ + struct ref **to_fetch = NULL; + struct ref *list_head = NULL; + struct ref **list = &list_head; + int alloc_heads = 0, nr_heads = 0; + + do { + if (!prefixcmp(buf->buf, "fetch ")) { + char *p = buf->buf + strlen("fetch "); + char *name; + struct ref *ref; + unsigned char old_sha1[20]; + + if (strlen(p) < 40 || get_sha1_hex(p, old_sha1)) + die("protocol error: expected sha/ref, got %s'", p); + if (p[40] == ' ') + name = p + 41; + else if (!p[40]) + name = ""; + else + die("protocol error: expected sha/ref, got %s'", p); + + ref = alloc_ref(name); + hashcpy(ref->old_sha1, old_sha1); + + *list = ref; + list = &ref->next; + + ALLOC_GROW(to_fetch, nr_heads + 1, alloc_heads); + to_fetch[nr_heads++] = ref; + } + else + die("http transport does not support %s", buf->buf); + + strbuf_reset(buf); + if (strbuf_getline(buf, stdin, '\n') == EOF) + return; + if (!*buf->buf) + break; + } while (1); + + if (fetch_dumb(nr_heads, to_fetch)) + exit(128); /* error already reported */ + free_refs(list_head); + free(to_fetch); + + printf("\n"); + fflush(stdout); + strbuf_reset(buf); +} + int main(int argc, const char **argv) { struct strbuf buf = STRBUF_INIT; @@ -110,17 +185,8 @@ int main(int argc, const char **argv) if (strbuf_getline(&buf, stdin, '\n') == EOF) break; if (!prefixcmp(buf.buf, "fetch ")) { - char *obj = buf.buf + strlen("fetch "); - init_walker(); - walker->get_all = 1; - walker->get_tree = 1; - walker->get_history = 1; - walker->get_verbosely = 0; - walker->get_recover = 0; - if (walker_fetch(walker, 1, &obj, NULL, NULL)) - die("Fetch failed."); - printf("\n"); - fflush(stdout); + parse_fetch(&buf); + } else if (!strcmp(buf.buf, "list")) { struct ref *refs = get_refs(); struct ref *posn; diff --git a/transport-helper.c b/transport-helper.c index f57e84c676..9de3408947 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -10,6 +10,7 @@ struct helper_data { const char *name; struct child_process *helper; + FILE *out; unsigned fetch : 1; }; @@ -18,7 +19,6 @@ static struct child_process *get_helper(struct transport *transport) struct helper_data *data = transport->data; struct strbuf buf = STRBUF_INIT; struct child_process *helper; - FILE *file; if (data->helper) return data->helper; @@ -39,9 +39,9 @@ static struct child_process *get_helper(struct transport *transport) write_str_in_full(helper->in, "capabilities\n"); - file = xfdopen(helper->out, "r"); + data->out = xfdopen(helper->out, "r"); while (1) { - if (strbuf_getline(&buf, file, '\n') == EOF) + if (strbuf_getline(&buf, data->out, '\n') == EOF) exit(128); /* child died, message supplied already */ if (!*buf.buf) @@ -58,6 +58,7 @@ static int disconnect_helper(struct transport *transport) if (data->helper) { write_str_in_full(data->helper->in, "\n"); close(data->helper->in); + fclose(data->out); finish_command(data->helper); free((char *)data->helper->argv[0]); free(data->helper->argv); @@ -70,8 +71,7 @@ static int disconnect_helper(struct transport *transport) static int fetch_with_fetch(struct transport *transport, int nr_heads, const struct ref **to_fetch) { - struct child_process *helper = get_helper(transport); - FILE *file = xfdopen(helper->out, "r"); + struct helper_data *data = transport->data; int i; struct strbuf buf = STRBUF_INIT; @@ -82,12 +82,30 @@ static int fetch_with_fetch(struct transport *transport, strbuf_addf(&buf, "fetch %s %s\n", sha1_to_hex(posn->old_sha1), posn->name); - write_in_full(helper->in, buf.buf, buf.len); - strbuf_reset(&buf); - - if (strbuf_getline(&buf, file, '\n') == EOF) - exit(128); /* child died, message supplied already */ } + + strbuf_addch(&buf, '\n'); + if (write_in_full(data->helper->in, buf.buf, buf.len) != buf.len) + die_errno("cannot send fetch to %s", data->name); + + while (1) { + strbuf_reset(&buf); + if (strbuf_getline(&buf, data->out, '\n') == EOF) + exit(128); /* child died, message supplied already */ + + if (!prefixcmp(buf.buf, "lock ")) { + const char *name = buf.buf + 5; + if (transport->pack_lockfile) + warning("%s also locked %s", data->name, name); + else + transport->pack_lockfile = xstrdup(name); + } + else if (!buf.len) + break; + else + warning("%s unexpectedly said: '%s'", data->name, buf.buf); + } + strbuf_release(&buf); return 0; } @@ -113,21 +131,20 @@ static int fetch(struct transport *transport, static struct ref *get_refs_list(struct transport *transport, int for_push) { + struct helper_data *data = transport->data; struct child_process *helper; struct ref *ret = NULL; struct ref **tail = &ret; struct ref *posn; struct strbuf buf = STRBUF_INIT; - FILE *file; helper = get_helper(transport); write_str_in_full(helper->in, "list\n"); - file = xfdopen(helper->out, "r"); while (1) { char *eov, *eon; - if (strbuf_getline(&buf, file, '\n') == EOF) + if (strbuf_getline(&buf, data->out, '\n') == EOF) exit(128); /* child died, message supplied already */ if (!*buf.buf) From ef08ef9ea0a271e5be5844408d2496a946d6e8d9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:29 -0700 Subject: [PATCH 208/884] remote-helpers: Support custom transport options Some transports, like the native pack transport implemented by fetch-pack, support useful features like depth or include tags. These should be exposed if the underlying helper knows how to use them. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano --- Documentation/git-remote-helpers.txt | 38 ++++++++++++ remote-curl.c | 74 ++++++++++++++++++++++- transport-helper.c | 88 +++++++++++++++++++++++++++- 3 files changed, 198 insertions(+), 2 deletions(-) diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index e44d821c7b..1133f047bf 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -35,6 +35,16 @@ Commands are given by the caller on the helper's standard input, one per line. the name; unrecognized attributes are ignored. After the complete list, outputs a blank line. +'option' :: + Set the transport helper option to . Outputs a + single line containing one of 'ok' (option successfully set), + 'unsupported' (option not recognized) or 'error ' + (option is supported but is not correct + for it). Options should be set before other commands, + and may how those commands behave. ++ +Supported if the helper has the "option" capability. + 'fetch' :: Fetches the given object, writing the necessary objects to the database. Fetch commands are sent in a batch, one @@ -63,11 +73,39 @@ CAPABILITIES 'fetch':: This helper supports the 'fetch' command. +'option':: + This helper supports the option command. + REF LIST ATTRIBUTES ------------------- None are defined yet, but the caller must accept any which are supplied. +OPTIONS +------- +'option verbosity' :: + Change the level of messages displayed by the helper. + When N is 0 the end-user has asked the process to be + quiet, and the helper should produce only error output. + N of 1 is the default level of verbosity, higher values + of N correspond to the number of -v flags passed on the + command line. + +'option progress' \{'true'|'false'\}:: + Enable (or disable) progress messages displayed by the + transport helper during a command. + +'option depth' :: + Deepen the history of a shallow repository. + +'option followtags' \{'true'|'false'\}:: + If enabled the helper should automatically fetch annotated + tag objects if the object the tag points at was transferred + during the fetch command. If the tag is not fetched by + the helper a second fetch command will usually be sent to + ask for the tag specifically. Some helpers may be able to + use this option to avoid a second network connection. + Documentation ------------- Documentation by Daniel Barkalow. diff --git a/remote-curl.c b/remote-curl.c index 22cd5c5fd2..0951f11615 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -9,12 +9,61 @@ static struct remote *remote; static const char *url; static struct walker *walker; +struct options { + int verbosity; + unsigned long depth; + unsigned progress : 1, + followtags : 1; +}; +static struct options options; + static void init_walker(void) { if (!walker) walker = get_http_walker(url, remote); } +static int set_option(const char *name, const char *value) +{ + if (!strcmp(name, "verbosity")) { + char *end; + int v = strtol(value, &end, 10); + if (value == end || *end) + return -1; + options.verbosity = v; + return 0; + } + else if (!strcmp(name, "progress")) { + if (!strcmp(value, "true")) + options.progress = 1; + else if (!strcmp(value, "false")) + options.progress = 0; + else + return -1; + return 1 /* TODO implement later */; + } + else if (!strcmp(name, "depth")) { + char *end; + unsigned long v = strtoul(value, &end, 10); + if (value == end || *end) + return -1; + options.depth = v; + return 1 /* TODO implement later */; + } + else if (!strcmp(name, "followtags")) { + if (!strcmp(value, "true")) + options.followtags = 1; + else if (!strcmp(value, "false")) + options.followtags = 0; + else + return -1; + return 1 /* TODO implement later */; + } + else { + return 1 /* unsupported */; + } +} + static struct ref *get_refs(void) { struct strbuf buffer = STRBUF_INIT; @@ -99,7 +148,7 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch) walker->get_all = 1; walker->get_tree = 1; walker->get_history = 1; - walker->get_verbosely = 0; + walker->get_verbosely = options.verbosity >= 3; walker->get_recover = 0; ret = walker_fetch(walker, nr_heads, targets, NULL, NULL); @@ -173,6 +222,9 @@ int main(int argc, const char **argv) return 1; } + options.verbosity = 1; + options.progress = !!isatty(2); + remote = remote_get(argv[1]); if (argc > 2) { @@ -198,8 +250,28 @@ int main(int argc, const char **argv) } printf("\n"); fflush(stdout); + } else if (!prefixcmp(buf.buf, "option ")) { + char *name = buf.buf + strlen("option "); + char *value = strchr(name, ' '); + int result; + + if (value) + *value++ = '\0'; + else + value = "true"; + + result = set_option(name, value); + if (!result) + printf("ok\n"); + else if (result < 0) + printf("error invalid value\n"); + else + printf("unsupported\n"); + fflush(stdout); + } else if (!strcmp(buf.buf, "capabilities")) { printf("fetch\n"); + printf("option\n"); printf("\n"); fflush(stdout); } else { diff --git a/transport-helper.c b/transport-helper.c index 9de3408947..577abc638e 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -5,13 +5,15 @@ #include "commit.h" #include "diff.h" #include "revision.h" +#include "quote.h" struct helper_data { const char *name; struct child_process *helper; FILE *out; - unsigned fetch : 1; + unsigned fetch : 1, + option : 1; }; static struct child_process *get_helper(struct transport *transport) @@ -48,6 +50,8 @@ static struct child_process *get_helper(struct transport *transport) break; if (!strcmp(buf.buf, "fetch")) data->fetch = 1; + if (!strcmp(buf.buf, "option")) + data->option = 1; } return data->helper; } @@ -65,9 +69,88 @@ static int disconnect_helper(struct transport *transport) free(data->helper); data->helper = NULL; } + free(data); return 0; } +static const char *unsupported_options[] = { + TRANS_OPT_UPLOADPACK, + TRANS_OPT_RECEIVEPACK, + TRANS_OPT_THIN, + TRANS_OPT_KEEP + }; +static const char *boolean_options[] = { + TRANS_OPT_THIN, + TRANS_OPT_KEEP, + TRANS_OPT_FOLLOWTAGS + }; + +static int set_helper_option(struct transport *transport, + const char *name, const char *value) +{ + struct helper_data *data = transport->data; + struct child_process *helper = get_helper(transport); + struct strbuf buf = STRBUF_INIT; + int i, ret, is_bool = 0; + + if (!data->option) + return 1; + + for (i = 0; i < ARRAY_SIZE(unsupported_options); i++) { + if (!strcmp(name, unsupported_options[i])) + return 1; + } + + for (i = 0; i < ARRAY_SIZE(boolean_options); i++) { + if (!strcmp(name, boolean_options[i])) { + is_bool = 1; + break; + } + } + + strbuf_addf(&buf, "option %s ", name); + if (is_bool) + strbuf_addstr(&buf, value ? "true" : "false"); + else + quote_c_style(value, &buf, NULL, 0); + strbuf_addch(&buf, '\n'); + + if (write_in_full(helper->in, buf.buf, buf.len) != buf.len) + die_errno("cannot send option to %s", data->name); + + strbuf_reset(&buf); + if (strbuf_getline(&buf, data->out, '\n') == EOF) + exit(128); /* child died, message supplied already */ + + if (!strcmp(buf.buf, "ok")) + ret = 0; + else if (!prefixcmp(buf.buf, "error")) { + ret = -1; + } else if (!strcmp(buf.buf, "unsupported")) + ret = 1; + else { + warning("%s unexpectedly said: '%s'", data->name, buf.buf); + ret = 1; + } + strbuf_release(&buf); + return ret; +} + +static void standard_options(struct transport *t) +{ + char buf[16]; + int n; + int v = t->verbose; + int no_progress = v < 0 || (!t->progress && !isatty(1)); + + set_helper_option(t, "progress", !no_progress ? "true" : "false"); + + n = snprintf(buf, sizeof(buf), "%d", v + 1); + if (n >= sizeof(buf)) + die("impossibly large verbosity value"); + set_helper_option(t, "verbosity", buf); +} + static int fetch_with_fetch(struct transport *transport, int nr_heads, const struct ref **to_fetch) { @@ -75,6 +158,8 @@ static int fetch_with_fetch(struct transport *transport, int i; struct strbuf buf = STRBUF_INIT; + standard_options(transport); + for (i = 0; i < nr_heads; i++) { const struct ref *posn = to_fetch[i]; if (posn->status & REF_STATUS_UPTODATE) @@ -178,6 +263,7 @@ int transport_helper_init(struct transport *transport, const char *name) data->name = name; transport->data = data; + transport->set_option = set_helper_option; transport->get_refs_list = get_refs_list; transport->fetch = fetch; transport->disconnect = disconnect_helper; From ae4efe195752c27cb25fca9451852c0f4eebdb28 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:30 -0700 Subject: [PATCH 209/884] Move WebDAV HTTP push under remote-curl The remote helper interface now supports the push capability, which can be used to ask the implementation to push one or more specs to the remote repository. For remote-curl we implement this by calling the existing WebDAV based git-http-push executable. Internally the helper interface uses the push_refs transport hook so that the complexity of the refspec parsing and matching can be reused between remote implementations. When possible however the helper protocol uses source ref name rather than the source SHA-1, thereby allowing the helper to access this name if it is useful. >From Clemens Buchacher : update http tests according to remote-curl capabilities o Pushing packed refs is now fixed. o The transport helper fails if refs are already up-to-date. Add a test for that. o The transport helper will notice if refs are already up-to-date. We therefore need to update server info in the unpacked-refs test. o The transport helper will purge deleted branches automatically. o Use a variable ($ORIG_HEAD) instead of full SHA-1 name. Signed-off-by: Tay Ray Chuan Signed-off-by: Clemens Buchacher Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow CC: Mike Hommey Signed-off-by: Junio C Hamano --- Documentation/git-remote-helpers.txt | 33 ++++++- http-push.c | 29 +++++- remote-curl.c | 97 ++++++++++++++++--- t/t5540-http-push.sh | 14 ++- transport-helper.c | 137 ++++++++++++++++++++++++++- transport.c | 31 ------ 6 files changed, 287 insertions(+), 54 deletions(-) diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index 1133f047bf..8beb42dbb9 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -34,6 +34,10 @@ Commands are given by the caller on the helper's standard input, one per line. value of the ref. A space-separated list of attributes follows the name; unrecognized attributes are ignored. After the complete list, outputs a blank line. ++ +If 'push' is supported this may be called as 'list for-push' +to obtain the current refs prior to sending one or more 'push' +commands to the helper. 'option' :: Set the transport helper option to . Outputs a @@ -59,6 +63,22 @@ suitably updated. + Supported if the helper has the "fetch" capability. +'push' +::: + Pushes the given commit or branch locally to the + remote branch described by . A batch sequence of + one or more push commands is terminated with a blank line. ++ +Zero or more protocol options may be entered after the last 'push' +command, before the batch's terminating blank line. ++ +When the push is complete, outputs one or more 'ok ' or +'error ?' lines to indicate success or failure of +each pushed ref. The status report output is terminated by +a blank line. The option field may be quoted in a C +style string if it contains an LF. ++ +Supported if the helper has the "push" capability. + If a fatal error occurs, the program writes the error message to stderr and exits. The caller should expect that a suitable error message has been printed if the child closes the connection without @@ -76,10 +96,16 @@ CAPABILITIES 'option':: This helper supports the option command. +'push':: + This helper supports the 'push' command. + REF LIST ATTRIBUTES ------------------- -None are defined yet, but the caller must accept any which are supplied. +'for-push':: + The caller wants to use the ref list to prepare push + commands. A helper might chose to acquire the ref list by + opening a different type of connection to the destination. OPTIONS ------- @@ -106,6 +132,11 @@ OPTIONS ask for the tag specifically. Some helpers may be able to use this option to avoid a second network connection. +'option dry-run' \{'true'|'false'\}: + If true, pretend the operation completed successfully, + but don't actually change any repository data. For most + helpers this only applies to the 'push', if supported. + Documentation ------------- Documentation by Daniel Barkalow. diff --git a/http-push.c b/http-push.c index cc5d4b8c32..f10803a389 100644 --- a/http-push.c +++ b/http-push.c @@ -78,6 +78,7 @@ static int push_verbosely; static int push_all = MATCH_REFS_NONE; static int force_all; static int dry_run; +static int helper_status; static struct object_list *objects; @@ -1813,6 +1814,10 @@ int main(int argc, char **argv) dry_run = 1; continue; } + if (!strcmp(arg, "--helper-status")) { + helper_status = 1; + continue; + } if (!strcmp(arg, "--verbose")) { push_verbosely = 1; http_is_verbose = 1; @@ -1911,9 +1916,12 @@ int main(int argc, char **argv) /* Remove a remote branch if -d or -D was specified */ if (delete_branch) { - if (delete_remote_branch(refspec[0], force_delete) == -1) + if (delete_remote_branch(refspec[0], force_delete) == -1) { fprintf(stderr, "Unable to delete remote branch %s\n", refspec[0]); + if (helper_status) + printf("error %s cannot remove\n", refspec[0]); + } goto cleanup; } @@ -1925,6 +1933,8 @@ int main(int argc, char **argv) } if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n"); + if (helper_status) + printf("error null no match\n"); rc = 0; goto cleanup; } @@ -1942,8 +1952,12 @@ int main(int argc, char **argv) if (is_null_sha1(ref->peer_ref->new_sha1)) { if (delete_remote_branch(ref->name, 1) == -1) { error("Could not remove %s", ref->name); + if (helper_status) + printf("error %s cannot remove\n", ref->name); rc = -4; } + else if (helper_status) + printf("ok %s\n", ref->name); new_refs++; continue; } @@ -1951,6 +1965,8 @@ int main(int argc, char **argv) if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (push_verbosely || 1) fprintf(stderr, "'%s': up-to-date\n", ref->name); + if (helper_status) + printf("ok %s up to date\n", ref->name); continue; } @@ -1974,6 +1990,8 @@ int main(int argc, char **argv) "need to pull first?", ref->name, ref->peer_ref->name); + if (helper_status) + printf("error %s non-fast forward\n", ref->name); rc = -2; continue; } @@ -1987,14 +2005,19 @@ int main(int argc, char **argv) if (strcmp(ref->name, ref->peer_ref->name)) fprintf(stderr, " using '%s'", ref->peer_ref->name); fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex); - if (dry_run) + if (dry_run) { + if (helper_status) + printf("ok %s\n", ref->name); continue; + } /* Lock remote branch ref */ ref_lock = lock_remote(ref->name, LOCK_TIME); if (ref_lock == NULL) { fprintf(stderr, "Unable to lock remote branch %s\n", ref->name); + if (helper_status) + printf("error %s lock error\n", ref->name); rc = 1; continue; } @@ -2045,6 +2068,8 @@ int main(int argc, char **argv) if (!rc) fprintf(stderr, " done\n"); + if (helper_status) + printf("%s %s\n", !rc ? "ok" : "error", ref->name); unlock_remote(ref_lock); check_locks(); } diff --git a/remote-curl.c b/remote-curl.c index 0951f11615..5c9dd97d1d 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -4,6 +4,7 @@ #include "walker.h" #include "http.h" #include "exec_cmd.h" +#include "run-command.h" static struct remote *remote; static const char *url; @@ -13,7 +14,8 @@ struct options { int verbosity; unsigned long depth; unsigned progress : 1, - followtags : 1; + followtags : 1, + dry_run : 1; }; static struct options options; @@ -59,6 +61,15 @@ static int set_option(const char *name, const char *value) return -1; return 1 /* TODO implement later */; } + else if (!strcmp(name, "dry-run")) { + if (!strcmp(value, "true")) + options.dry_run = 1; + else if (!strcmp(value, "false")) + options.dry_run = 0; + else + return -1; + return 0; + } else { return 1 /* unsupported */; } @@ -136,6 +147,20 @@ static struct ref *get_refs(void) return refs; } +static void output_refs(struct ref *refs) +{ + struct ref *posn; + for (posn = refs; posn; posn = posn->next) { + if (posn->symref) + printf("@%s %s\n", posn->symref, posn->name); + else + printf("%s %s\n", sha1_to_hex(posn->old_sha1), posn->name); + } + printf("\n"); + fflush(stdout); + free_refs(refs); +} + static int fetch_dumb(int nr_heads, struct ref **to_fetch) { char **targets = xmalloc(nr_heads * sizeof(char*)); @@ -211,6 +236,58 @@ static void parse_fetch(struct strbuf *buf) strbuf_reset(buf); } +static int push_dav(int nr_spec, char **specs) +{ + const char **argv = xmalloc((10 + nr_spec) * sizeof(char*)); + int argc = 0, i; + + argv[argc++] = "http-push"; + argv[argc++] = "--helper-status"; + if (options.dry_run) + argv[argc++] = "--dry-run"; + if (options.verbosity > 1) + argv[argc++] = "--verbose"; + argv[argc++] = url; + for (i = 0; i < nr_spec; i++) + argv[argc++] = specs[i]; + argv[argc++] = NULL; + + if (run_command_v_opt(argv, RUN_GIT_CMD)) + die("git-%s failed", argv[0]); + free(argv); + return 0; +} + +static void parse_push(struct strbuf *buf) +{ + char **specs = NULL; + int alloc_spec = 0, nr_spec = 0, i; + + do { + if (!prefixcmp(buf->buf, "push ")) { + ALLOC_GROW(specs, nr_spec + 1, alloc_spec); + specs[nr_spec++] = xstrdup(buf->buf + 5); + } + else + die("http transport does not support %s", buf->buf); + + strbuf_reset(buf); + if (strbuf_getline(buf, stdin, '\n') == EOF) + return; + if (!*buf->buf) + break; + } while (1); + + if (push_dav(nr_spec, specs)) + exit(128); /* error already reported */ + for (i = 0; i < nr_spec; i++) + free(specs[i]); + free(specs); + + printf("\n"); + fflush(stdout); +} + int main(int argc, const char **argv) { struct strbuf buf = STRBUF_INIT; @@ -239,17 +316,12 @@ int main(int argc, const char **argv) if (!prefixcmp(buf.buf, "fetch ")) { parse_fetch(&buf); - } else if (!strcmp(buf.buf, "list")) { - struct ref *refs = get_refs(); - struct ref *posn; - for (posn = refs; posn; posn = posn->next) { - if (posn->symref) - printf("@%s %s\n", posn->symref, posn->name); - else - printf("%s %s\n", sha1_to_hex(posn->old_sha1), posn->name); - } - printf("\n"); - fflush(stdout); + } else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) { + output_refs(get_refs()); + + } else if (!prefixcmp(buf.buf, "push ")) { + parse_push(&buf); + } else if (!prefixcmp(buf.buf, "option ")) { char *name = buf.buf + strlen("option "); char *value = strchr(name, ' '); @@ -272,6 +344,7 @@ int main(int argc, const char **argv) } else if (!strcmp(buf.buf, "capabilities")) { printf("fetch\n"); printf("option\n"); + printf("push\n"); printf("\n"); fflush(stdout); } else { diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index f4a2cf6c17..09edd2387b 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -36,6 +36,7 @@ test_expect_success 'setup remote repository' ' cd test_repo.git && git --bare update-server-info && mv hooks/post-update.sample hooks/post-update && + ORIG_HEAD=$(git rev-parse --verify HEAD) && cd - && mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH" ' @@ -45,7 +46,7 @@ test_expect_success 'clone remote repository' ' git clone $HTTPD_URL/test_repo.git test_repo_clone ' -test_expect_failure 'push to remote repository with packed refs' ' +test_expect_success 'push to remote repository with packed refs' ' cd "$ROOT_PATH"/test_repo_clone && : >path2 && git add path2 && @@ -57,11 +58,15 @@ test_expect_failure 'push to remote repository with packed refs' ' test $HEAD = $(git rev-parse --verify HEAD)) ' -test_expect_success ' push to remote repository with unpacked refs' ' +test_expect_failure 'push already up-to-date' ' + git push +' + +test_expect_success 'push to remote repository with unpacked refs' ' (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && rm packed-refs && - git update-ref refs/heads/master \ - 0c973ae9bd51902a28466f3850b543fa66a6aaf4) && + git update-ref refs/heads/master $ORIG_HEAD && + git --bare update-server-info) && git push && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && test $HEAD = $(git rev-parse --verify HEAD)) @@ -113,7 +118,6 @@ test_expect_success 'create and delete remote branch' ' git push origin dev && git fetch && git push origin :dev && - git branch -d -r origin/dev && git fetch && test_must_fail git show-ref --verify refs/remotes/origin/dev ' diff --git a/transport-helper.c b/transport-helper.c index 577abc638e..16c6641f03 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -1,6 +1,6 @@ #include "cache.h" #include "transport.h" - +#include "quote.h" #include "run-command.h" #include "commit.h" #include "diff.h" @@ -13,7 +13,8 @@ struct helper_data struct child_process *helper; FILE *out; unsigned fetch : 1, - option : 1; + option : 1, + push : 1; }; static struct child_process *get_helper(struct transport *transport) @@ -52,6 +53,8 @@ static struct child_process *get_helper(struct transport *transport) data->fetch = 1; if (!strcmp(buf.buf, "option")) data->option = 1; + if (!strcmp(buf.buf, "push")) + data->push = 1; } return data->helper; } @@ -214,6 +217,130 @@ static int fetch(struct transport *transport, return -1; } +static int push_refs(struct transport *transport, + struct ref *remote_refs, int flags) +{ + int force_all = flags & TRANSPORT_PUSH_FORCE; + int mirror = flags & TRANSPORT_PUSH_MIRROR; + struct helper_data *data = transport->data; + struct strbuf buf = STRBUF_INIT; + struct child_process *helper; + struct ref *ref; + + if (!remote_refs) + return 0; + + helper = get_helper(transport); + if (!data->push) + return 1; + + for (ref = remote_refs; ref; ref = ref->next) { + if (ref->peer_ref) + hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); + else if (!mirror) + continue; + + ref->deletion = is_null_sha1(ref->new_sha1); + if (!ref->deletion && + !hashcmp(ref->old_sha1, ref->new_sha1)) { + ref->status = REF_STATUS_UPTODATE; + continue; + } + + if (force_all) + ref->force = 1; + + strbuf_addstr(&buf, "push "); + if (!ref->deletion) { + if (ref->force) + strbuf_addch(&buf, '+'); + if (ref->peer_ref) + strbuf_addstr(&buf, ref->peer_ref->name); + else + strbuf_addstr(&buf, sha1_to_hex(ref->new_sha1)); + } + strbuf_addch(&buf, ':'); + strbuf_addstr(&buf, ref->name); + strbuf_addch(&buf, '\n'); + } + + transport->verbose = flags & TRANSPORT_PUSH_VERBOSE ? 1 : 0; + standard_options(transport); + + if (flags & TRANSPORT_PUSH_DRY_RUN) { + if (set_helper_option(transport, "dry-run", "true") != 0) + die("helper %s does not support dry-run", data->name); + } + + strbuf_addch(&buf, '\n'); + if (write_in_full(helper->in, buf.buf, buf.len) != buf.len) + exit(128); + + ref = remote_refs; + while (1) { + char *refname, *msg; + int status; + + strbuf_reset(&buf); + if (strbuf_getline(&buf, data->out, '\n') == EOF) + exit(128); /* child died, message supplied already */ + if (!buf.len) + break; + + if (!prefixcmp(buf.buf, "ok ")) { + status = REF_STATUS_OK; + refname = buf.buf + 3; + } else if (!prefixcmp(buf.buf, "error ")) { + status = REF_STATUS_REMOTE_REJECT; + refname = buf.buf + 6; + } else + die("expected ok/error, helper said '%s'\n", buf.buf); + + msg = strchr(refname, ' '); + if (msg) { + struct strbuf msg_buf = STRBUF_INIT; + const char *end; + + *msg++ = '\0'; + if (!unquote_c_style(&msg_buf, msg, &end)) + msg = strbuf_detach(&msg_buf, NULL); + else + msg = xstrdup(msg); + strbuf_release(&msg_buf); + + if (!strcmp(msg, "no match")) { + status = REF_STATUS_NONE; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "up to date")) { + status = REF_STATUS_UPTODATE; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "non-fast forward")) { + status = REF_STATUS_REJECT_NONFASTFORWARD; + free(msg); + msg = NULL; + } + } + + if (ref) + ref = find_ref_by_name(ref, refname); + if (!ref) + ref = find_ref_by_name(remote_refs, refname); + if (!ref) { + warning("helper reported unexpected status of %s", refname); + continue; + } + + ref->status = status; + ref->remote_status = msg; + } + strbuf_release(&buf); + return 0; +} + static struct ref *get_refs_list(struct transport *transport, int for_push) { struct helper_data *data = transport->data; @@ -225,7 +352,10 @@ static struct ref *get_refs_list(struct transport *transport, int for_push) helper = get_helper(transport); - write_str_in_full(helper->in, "list\n"); + if (data->push && for_push) + write_str_in_full(helper->in, "list for-push\n"); + else + write_str_in_full(helper->in, "list\n"); while (1) { char *eov, *eon; @@ -266,6 +396,7 @@ int transport_helper_init(struct transport *transport, const char *name) transport->set_option = set_helper_option; transport->get_refs_list = get_refs_list; transport->fetch = fetch; + transport->push_refs = push_refs; transport->disconnect = disconnect_helper; return 0; } diff --git a/transport.c b/transport.c index 644a30a0b2..6d9652db94 100644 --- a/transport.c +++ b/transport.c @@ -349,35 +349,6 @@ static int rsync_transport_push(struct transport *transport, return result; } -#ifndef NO_CURL -static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) -{ - const char **argv; - int argc; - - if (flags & TRANSPORT_PUSH_MIRROR) - return error("http transport does not support mirror mode"); - - argv = xmalloc((refspec_nr + 12) * sizeof(char *)); - argv[0] = "http-push"; - argc = 1; - if (flags & TRANSPORT_PUSH_ALL) - argv[argc++] = "--all"; - if (flags & TRANSPORT_PUSH_FORCE) - argv[argc++] = "--force"; - if (flags & TRANSPORT_PUSH_DRY_RUN) - argv[argc++] = "--dry-run"; - if (flags & TRANSPORT_PUSH_VERBOSE) - argv[argc++] = "--verbose"; - argv[argc++] = transport->url; - while (refspec_nr--) - argv[argc++] = *refspec++; - argv[argc] = NULL; - return !!run_command_v_opt(argv, RUN_GIT_CMD); -} - -#endif - struct bundle_transport_data { int fd; struct bundle_header header; @@ -826,8 +797,6 @@ struct transport *transport_get(struct remote *remote, const char *url) transport_helper_init(ret, "curl"); #ifdef NO_CURL error("git was compiled without libcurl support."); -#else - ret->push = curl_transport_push; #endif } else if (is_local(url) && is_file(url)) { From d8f67d205eb98ea6dab915c29122158054218128 Mon Sep 17 00:00:00 2001 From: Clemens Buchacher Date: Fri, 30 Oct 2009 17:47:31 -0700 Subject: [PATCH 210/884] remote-helpers: return successfully if everything up-to-date Signed-off-by: Clemens Buchacher Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/t5540-http-push.sh | 2 +- transport-helper.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index 09edd2387b..2ece6612e1 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -58,7 +58,7 @@ test_expect_success 'push to remote repository with packed refs' ' test $HEAD = $(git rev-parse --verify HEAD)) ' -test_expect_failure 'push already up-to-date' ' +test_expect_success 'push already up-to-date' ' git push ' diff --git a/transport-helper.c b/transport-helper.c index 16c6641f03..5078c7100f 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -263,6 +263,8 @@ static int push_refs(struct transport *transport, strbuf_addstr(&buf, ref->name); strbuf_addch(&buf, '\n'); } + if (buf.len == 0) + return 0; transport->verbose = flags & TRANSPORT_PUSH_VERBOSE ? 1 : 0; standard_options(transport); From c8a58ac5a52b0850fbca87898d1c6aa44cf5626f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 31 Oct 2009 11:16:50 -0700 Subject: [PATCH 211/884] Revert "Don't create the $GIT_DIR/branches directory on init" This reverts commit 0cc5691a8b05a7eabdeef520c94b1bb3bcac7874. There is not enough justification for doing this. We do not update things in .git/branches and .git/remotes anymore, but still do read information from there and will keep doing so. Besides, this breaks quite a lot of tests in t55?? series. --- templates/branches-- | 1 + 1 file changed, 1 insertion(+) create mode 100644 templates/branches-- diff --git a/templates/branches-- b/templates/branches-- new file mode 100644 index 0000000000..fae88709a6 --- /dev/null +++ b/templates/branches-- @@ -0,0 +1 @@ +: this is just to ensure the directory exists. From 76fd28283f7eeea246a06994edd43ab60e59d853 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 30 Oct 2009 10:09:06 -0700 Subject: [PATCH 212/884] diff --color-words: bit of clean-up When we introduced the "word diff" mode, we could have done one of three things: * change fn_out_consume() to "this is called every time a line worth of diff becomes ready from the lower-level diff routine. This function knows two sets of helpers (one for line-oriented diff, another for word diff), and each set has various functions to be called at certain places (e.g. hunk header, context, ...). The function's role is to inspect the incoming line, and dispatch appropriate helpers to produce either line- or word- oriented diff output." * introduce fn_out_consume_word_diff() that is "this is called every time a line worth of diff becomes ready from the lower-level diff routine, and here is what we do to prepare word oriented diff using that line." without touching fn_out_consume() at all. * Do neither of the above, and keep fn_out_consume() to "this is called every time a line worth of diff becomes ready from the lower-level diff routine, and here is what we do to output line oriented diff using that line." but sprinkle a handful of 'are we in word-diff mode? if so do this totally different thing' at random places. This patch is to at least abstract the details of "this totally different thing" out from the main codepath, in order to improve readability. We can later refactor it by introducing fn_out_consume_word_diff(), taking the second route above, but that is a separate topic. Signed-off-by: Junio C Hamano --- diff.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/diff.c b/diff.c index b7ecfe3b17..8c66e4a0d4 100644 --- a/diff.c +++ b/diff.c @@ -541,14 +541,18 @@ struct emit_callback { FILE *file; }; +/* In "color-words" mode, show word-diff of words accumulated in the buffer */ +static void diff_words_flush(struct emit_callback *ecbdata) +{ + if (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size) + diff_words_show(ecbdata->diff_words); +} + static void free_diff_words_data(struct emit_callback *ecbdata) { if (ecbdata->diff_words) { - /* flush buffers */ - if (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size) - diff_words_show(ecbdata->diff_words); - + diff_words_flush(ecbdata); free (ecbdata->diff_words->minus.text.ptr); free (ecbdata->diff_words->minus.orig); free (ecbdata->diff_words->plus.text.ptr); @@ -656,12 +660,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) for (i = 0; i < len && line[i] == '@'; i++) ; if (2 <= i && i < len && line[i] == ' ') { - /* flush --color-words even for --unified=0 */ - if (ecbdata->diff_words && - (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size)) - diff_words_show(ecbdata->diff_words); - + if (ecbdata->diff_words) + diff_words_flush(ecbdata); ecbdata->nparents = i - 1; len = sane_truncate_line(ecbdata, line, len); emit_line(ecbdata->file, @@ -691,9 +691,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) &ecbdata->diff_words->plus); return; } - if (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size) - diff_words_show(ecbdata->diff_words); + diff_words_flush(ecbdata); line++; len--; emit_line(ecbdata->file, plain, reset, line, len); From 1e380ddcd29f1e25bfb58b7068fdafb038c8dd9a Mon Sep 17 00:00:00 2001 From: Vietor Liu Date: Sat, 31 Oct 2009 14:36:03 +0800 Subject: [PATCH 213/884] imap-send.c: fix compiler warnings for OpenSSL 1.0 The openssl/CHANGES file says: Let the TLSv1_method() etc. functions return a 'const' SSL_METHOD pointer and make the SSL_METHOD parameter in SSL_CTX_new, SSL_CTX_set_ssl_version and SSL_set_ssl_method 'const'. In older versions, unqualified pointers were used, so we unfortunately cannot unconditionally update the type of the variable we use. Signed-off-by: Vietor Liu Signed-off-by: Junio C Hamano --- imap-send.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/imap-send.c b/imap-send.c index 3847fd151d..f805c6ed81 100644 --- a/imap-send.c +++ b/imap-send.c @@ -272,8 +272,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve #ifdef NO_OPENSSL fprintf(stderr, "SSL requested but SSL support not compiled in\n"); return -1; +#else +#if (OPENSSL_VERSION_NUMBER >= 0x10000000L) + const SSL_METHOD *meth; #else SSL_METHOD *meth; +#endif SSL_CTX *ctx; int ret; From 754571261a87064434441dc7978153df52a874e7 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" Date: Mon, 2 Nov 2009 02:09:05 +0300 Subject: [PATCH 214/884] Makefile: add compat/bswap.h to LIB_H Starting with commit 51ea55190b6e72c77c96754c1bf2f149a4714848, git-compat-util.h includes compat/bswap.h Signed-off-by: Dmitry V. Levin Signed-off-by: Junio C Hamano --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 268aede566..3bca8d42cc 100644 --- a/Makefile +++ b/Makefile @@ -412,6 +412,7 @@ LIB_H += builtin.h LIB_H += cache.h LIB_H += cache-tree.h LIB_H += commit.h +LIB_H += compat/bswap.h LIB_H += compat/cygwin.h LIB_H += compat/mingw.h LIB_H += csum-file.h From 74de2781130822c2d7948f77270c2898caf1bf66 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Mon, 2 Nov 2009 01:30:05 -0800 Subject: [PATCH 215/884] t1402: Make test executable Signed-off-by: Stephen Boyd Signed-off-by: Junio C Hamano --- t/t1402-check-ref-format.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 t/t1402-check-ref-format.sh diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh old mode 100644 new mode 100755 From 5497f7a23ac11f9b230892220d5ed80263eedd1f Mon Sep 17 00:00:00 2001 From: "Guillermo S. Romero" Date: Thu, 15 Oct 2009 18:51:49 +0200 Subject: [PATCH 216/884] gitk: Add configuration for UI colour scheme This adds an option to control the global colour scheme in the Edit > Preferences dialog so that the whole interface can have a non-default main colour. Signed-off-by: Guillermo S. Romero Signed-off-by: Paul Mackerras --- gitk | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/gitk b/gitk index a0214b7004..d40a735489 100755 --- a/gitk +++ b/gitk @@ -2519,7 +2519,7 @@ proc savestuff {w} { global maxwidth showneartags showlocalchanges global viewname viewfiles viewargs viewargscmd viewperm nextviewnum global cmitmode wrapcomment datetimeformat limitdiffs - global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor + global colors uicolor bgcolor fgcolor diffcolors diffcontext selectbgcolor global autoselect extdifftool perfile_attrs markbgcolor global hideremotes @@ -2546,6 +2546,7 @@ proc savestuff {w} { puts $f [list set showlocalchanges $showlocalchanges] puts $f [list set datetimeformat $datetimeformat] puts $f [list set limitdiffs $limitdiffs] + puts $f [list set uicolor $uicolor] puts $f [list set bgcolor $bgcolor] puts $f [list set fgcolor $fgcolor] puts $f [list set colors $colors] @@ -10425,7 +10426,7 @@ proc chg_fontparam {v sub op} { proc doprefs {} { global maxwidth maxgraphpct global oldprefs prefstop showneartags showlocalchanges - global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor + global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor global tabstop limitdiffs autoselect extdifftool perfile_attrs global hideremotes @@ -10489,6 +10490,10 @@ proc doprefs {} { label $top.cdisp -text [mc "Colors: press to choose"] grid $top.cdisp - -sticky w -pady 10 + label $top.ui -padx 40 -relief sunk -background $uicolor + button $top.uibut -text [mc "Interface"] -font optionfont \ + -command [list choosecolor uicolor {} $top.ui [mc "interface"] setui] + grid x $top.uibut $top.ui -sticky w label $top.bg -padx 40 -relief sunk -background $bgcolor button $top.bgbut -text [mc "Background"] -font optionfont \ -command [list choosecolor bgcolor {} $top.bg [mc "background"] setbg] @@ -10572,6 +10577,10 @@ proc setselbg {c} { allcanvs itemconf secsel -fill $c } +proc setui {c} { + tk_setPalette $c +} + proc setbg {c} { global bglist @@ -11079,6 +11088,7 @@ if {[tk windowingsystem] eq "aqua"} { } set colors {green red blue magenta darkgrey brown orange} +set uicolor grey85 set bgcolor white set fgcolor black set diffcolors {red "#00a000" blue} @@ -11130,6 +11140,8 @@ eval font create textfontbold [fontflags textfont 1] parsefont uifont $uifont eval font create uifont [fontflags uifont] +tk_setPalette $uicolor + setoptions # check that we can find a .git directory somewhere... From 90a77925417646041ac408e591f969d7477be0a9 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Tue, 27 Oct 2009 18:13:42 +0100 Subject: [PATCH 217/884] gitk: Fix diffing committed -> staged (typo in diffcmd) When highlighting a commit, using the context menu over the staged changes and then selecting "Diff this -> selected" the diff was empty. The same happened when highlighting the staged changes and using "Diff selected -> this" over a commit. The reason was a copy/paste error in [diffcmd]. This fixes it. Signed-off-by: Jens Lehmann Signed-off-by: Paul Mackerras --- gitk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitk b/gitk index d40a735489..785afd2235 100755 --- a/gitk +++ b/gitk @@ -7225,7 +7225,7 @@ proc diffcmd {ids flags} { set cmd [concat | git diff-index --cached $flags] if {[llength $ids] > 1} { # comparing index with specific revision - if {$i == 0} { + if {$j == 0} { lappend cmd -R [lindex $ids 1] } else { lappend cmd [lindex $ids 0] From 5c838d23aa02476cfe91b0eea047b6d5857da770 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Wed, 28 Oct 2009 12:40:45 +0100 Subject: [PATCH 218/884] gitk: Use the --submodule option for displaying diffs when available When displaying diffs in a submodule, this makes gitk display the headlines of the commits being diffed, instead of just showing not-quite-helpful SHA-1 pairs, if the underlying git installation supports this. That makes it much easier to evaluate the changes, as it eliminates the need to start a gitk inside the submodule and use the superprojects hashes there to find out what the commits are about. Since the --submodule option of git diff is new in git version 1.6.6, this only uses the --submodule option when a git version of 1.6.6 or higher is detected. Signed-off-by: Jens Lehmann Signed-off-by: Paul Mackerras --- gitk | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/gitk b/gitk index 785afd2235..59d5925199 100755 --- a/gitk +++ b/gitk @@ -7344,7 +7344,11 @@ proc getblobdiffs {ids} { if {[package vcompare $git_version "1.6.1"] >= 0} { set textconv "--textconv" } - set cmd [diffcmd $ids "-p $textconv -C --cc --no-commit-id -U$diffcontext"] + set submodule {} + if {[package vcompare $git_version "1.6.6"] >= 0} { + set submodule "--submodule" + } + set cmd [diffcmd $ids "-p $textconv $submodule -C --cc --no-commit-id -U$diffcontext"] if {$ignorespace} { append cmd " -w" } @@ -7482,6 +7486,21 @@ proc getblobdiffline {bdf ids} { set diffnparents [expr {[string length $ats] - 1}] set diffinhdr 0 + } elseif {![string compare -length 10 "Submodule " $line]} { + # start of a new submodule + if {[string compare [$ctext get "end - 4c" end] "\n \n\n"]} { + $ctext insert end "\n"; # Add newline after commit message + } + set curdiffstart [$ctext index "end - 1c"] + lappend ctext_file_names "" + set fname [string range $line 10 [expr [string last " " $line] - 1]] + lappend ctext_file_lines $fname + makediffhdr $fname $ids + $ctext insert end "\n$line\n" filesep + } elseif {![string compare -length 3 " >" $line]} { + $ctext insert end "$line\n" dresult + } elseif {![string compare -length 3 " <" $line]} { + $ctext insert end "$line\n" d0 } elseif {$diffinhdr} { if {![string compare -length 12 "rename from " $line]} { set fname [string range $line [expr 6 + [string first " from " $line] ] end] From d40bc70ab88ab0d8fde20650c6ca132d3ca3cd2c Mon Sep 17 00:00:00 2001 From: Mizar Date: Fri, 30 Oct 2009 01:22:26 +0900 Subject: [PATCH 219/884] gitk: Add Japanese translation Signed-off-by: Paul Mackerras --- po/ja.po | 1245 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1245 insertions(+) create mode 100644 po/ja.po diff --git a/po/ja.po b/po/ja.po new file mode 100644 index 0000000000..59d44a4e45 --- /dev/null +++ b/po/ja.po @@ -0,0 +1,1245 @@ +# Japanese translations for gitk package. +# Copyright (C) 2005-2009 Paul Mackerras +# This file is distributed under the same license as the gitk package. +# +# Mizar , 2009. +# Junio C Hamano , 2009. +msgid "" +msgstr "" +"Project-Id-Version: gitk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-10-19 02:45+0900\n" +"PO-Revision-Date: 2009-10-19 17:03+0900\n" +"Last-Translator: Mizar \n" +"Language-Team: Japanese\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: gitk:113 +msgid "Couldn't get list of unmerged files:" +msgstr "マージされていないファイルのリストを取得できません:" + +#: gitk:269 +msgid "Error parsing revisions:" +msgstr "リビジョン解析エラー:" + +#: gitk:324 +msgid "Error executing --argscmd command:" +msgstr "--argscmd コマンド実行エラー:" + +#: gitk:337 +msgid "No files selected: --merge specified but no files are unmerged." +msgstr "ファイル未選択: --merge が指定されましたが、マージされていないファイルはありません。" + +#: gitk:340 +msgid "" +"No files selected: --merge specified but no unmerged files are within file " +"limit." +msgstr "ファイル未選択: --merge が指定されましたが、" +"ファイル制限内にマージされていないファイルはありません。" + +#: gitk:362 gitk:509 +msgid "Error executing git log:" +msgstr "git log 実行エラー:" + +#: gitk:380 gitk:525 +msgid "Reading" +msgstr "読み込み中" + +#: gitk:440 gitk:4131 +msgid "Reading commits..." +msgstr "コミット読み込み中..." + +#: gitk:443 gitk:1561 gitk:4134 +msgid "No commits selected" +msgstr "コミットが選択されていません" + +#: gitk:1437 +msgid "Can't parse git log output:" +msgstr "git log の出力を解析できません:" + +#: gitk:1657 +msgid "No commit information available" +msgstr "有効なコミットの情報がありません" + +#: gitk:1793 gitk:1817 gitk:3924 gitk:8822 gitk:10358 gitk:10534 +msgid "OK" +msgstr "OK" + +#: gitk:1819 gitk:3926 gitk:8419 gitk:8493 gitk:8603 gitk:8652 gitk:8824 +#: gitk:10359 gitk:10535 +msgid "Cancel" +msgstr "キャンセル" + +#: gitk:1919 +msgid "Update" +msgstr "更新" + +#: gitk:1920 +msgid "Reload" +msgstr "リロード" + +#: gitk:1921 +msgid "Reread references" +msgstr "リファレンスを再読み込み" + +#: gitk:1922 +msgid "List references" +msgstr "リファレンスリストを表示" + +#: gitk:1924 +msgid "Start git gui" +msgstr "git gui の開始" + +#: gitk:1926 +msgid "Quit" +msgstr "終了" + +#: gitk:1918 +msgid "File" +msgstr "ファイル" + +#: gitk:1930 +msgid "Preferences" +msgstr "設定" + +#: gitk:1929 +msgid "Edit" +msgstr "編集" + +#: gitk:1934 +msgid "New view..." +msgstr "新規ビュー..." + +#: gitk:1935 +msgid "Edit view..." +msgstr "ビュー編集..." + +#: gitk:1936 +msgid "Delete view" +msgstr "ビュー削除" + +#: gitk:1938 +msgid "All files" +msgstr "全てのファイル" + +#: gitk:1933 gitk:3678 +msgid "View" +msgstr "ビュー" + +#: gitk:1943 gitk:1953 gitk:2655 +msgid "About gitk" +msgstr "gitk について" + +#: gitk:1944 gitk:1958 +msgid "Key bindings" +msgstr "キーバインディング" + +#: gitk:1942 gitk:1957 +msgid "Help" +msgstr "ヘルプ" + +#: gitk:2018 +msgid "SHA1 ID: " +msgstr "SHA1 ID: " + +#: gitk:2049 +msgid "Row" +msgstr "行" + +#: gitk:2080 +msgid "Find" +msgstr "検索" + +#: gitk:2081 +msgid "next" +msgstr "次" + +#: gitk:2082 +msgid "prev" +msgstr "前" + +#: gitk:2083 +msgid "commit" +msgstr "コミット" + +#: gitk:2086 gitk:2088 gitk:4292 gitk:4315 gitk:4339 gitk:6280 gitk:6352 +#: gitk:6436 +msgid "containing:" +msgstr "含む:" + +#: gitk:2089 gitk:3163 gitk:3168 gitk:4367 +msgid "touching paths:" +msgstr "パスの一部:" + +#: gitk:2090 gitk:4372 +msgid "adding/removing string:" +msgstr "追加/除去する文字列:" + +#: gitk:2099 gitk:2101 +msgid "Exact" +msgstr "英字の大小を区別する" + +#: gitk:2101 gitk:4447 gitk:6248 +msgid "IgnCase" +msgstr "英字の大小を区別しない" + +#: gitk:2101 gitk:4341 gitk:4445 gitk:6244 +msgid "Regexp" +msgstr "正規表現" + +#: gitk:2103 gitk:2104 gitk:4466 gitk:4496 gitk:4503 gitk:6372 gitk:6440 +msgid "All fields" +msgstr "全ての項目" + +#: gitk:2104 gitk:4464 gitk:4496 gitk:6311 +msgid "Headline" +msgstr "ヘッドライン" + +#: gitk:2105 gitk:4464 gitk:6311 gitk:6440 gitk:6874 +msgid "Comments" +msgstr "コメント" + +#: gitk:2105 gitk:4464 gitk:4468 gitk:4503 gitk:6311 gitk:6809 gitk:8071 +#: gitk:8086 +msgid "Author" +msgstr "作成者" + +#: gitk:2105 gitk:4464 gitk:6311 gitk:6811 +msgid "Committer" +msgstr "コミットした人" + +#: gitk:2134 +msgid "Search" +msgstr "検索" + +#: gitk:2141 +msgid "Diff" +msgstr "Diff" + +#: gitk:2143 +msgid "Old version" +msgstr "旧バージョン" + +#: gitk:2145 +msgid "New version" +msgstr "新バージョン" + +#: gitk:2147 +msgid "Lines of context" +msgstr "文脈行数" + +#: gitk:2157 +msgid "Ignore space change" +msgstr "空白の違いを無視" + +#: gitk:2215 +msgid "Patch" +msgstr "パッチ" + +#: gitk:2217 +msgid "Tree" +msgstr "ツリー" + +#: gitk:2361 gitk:2378 +msgid "Diff this -> selected" +msgstr "これと選択したコミットのdiffを見る" + +#: gitk:2362 gitk:2379 +msgid "Diff selected -> this" +msgstr "選択したコミットとこれのdiffを見る" + +#: gitk:2363 gitk:2380 +msgid "Make patch" +msgstr "パッチ作成" + +#: gitk:2364 gitk:8477 +msgid "Create tag" +msgstr "タグ生成" + +#: gitk:2365 gitk:8583 +msgid "Write commit to file" +msgstr "コミットをファイルに書き込む" + +#: gitk:2366 gitk:8640 +msgid "Create new branch" +msgstr "新規ブランチ生成" + +#: gitk:2367 +msgid "Cherry-pick this commit" +msgstr "このコミットをチェリーピックする" + +#: gitk:2368 +msgid "Reset HEAD branch to here" +msgstr "ブランチのHEADをここにリセットする" + +#: gitk:2369 +msgid "Mark this commit" +msgstr "このコミットにマークをつける" + +#: gitk:2370 +msgid "Return to mark" +msgstr "マークを付けた所に戻る" + +#: gitk:2371 +msgid "Find descendant of this and mark" +msgstr "この子孫を見つけてマークする" + +#: gitk:2372 +msgid "Compare with marked commit" +msgstr "マークを付けたコミットと比較する" + +#: gitk:2386 +msgid "Check out this branch" +msgstr "このブランチをチェックアウトする" + +#: gitk:2387 +msgid "Remove this branch" +msgstr "このブランチを除去する" + +#: gitk:2394 +msgid "Highlight this too" +msgstr "これもハイライトさせる" + +#: gitk:2395 +msgid "Highlight this only" +msgstr "これだけをハイライトさせる" + +#: gitk:2396 +msgid "External diff" +msgstr "外部diffツール" + +#: gitk:2397 +msgid "Blame parent commit" +msgstr "親コミットから blame をかける" + +#: gitk:2404 +msgid "Show origin of this line" +msgstr "この行の出自を表示する" + +#: gitk:2405 +msgid "Run git gui blame on this line" +msgstr "この行に git gui で blame をかける" + +#: gitk:2657 +msgid "" +"\n" +"Gitk - a commit viewer for git\n" +"\n" +"Copyright c 2005-2008 Paul Mackerras\n" +"\n" +"Use and redistribute under the terms of the GNU General Public License" +msgstr "" +"\n" +"Gitk - gitコミットビューア\n" +"\n" +"Copyright c 2005-2008 Paul Mackerras\n" +"\n" +"使用および再配布は GNU General Public License に従ってください" + +#: gitk:2665 gitk:2727 gitk:9005 +msgid "Close" +msgstr "閉じる" + +#: gitk:2684 +msgid "Gitk key bindings" +msgstr "Gitk キーバインディング" + +#: gitk:2687 +msgid "Gitk key bindings:" +msgstr "Gitk キーバインディング:" + +#: gitk:2689 +#, tcl-format +msgid "<%s-Q>\t\tQuit" +msgstr "<%s-Q>\t\t終了" + +#: gitk:2690 +msgid "\t\tMove to first commit" +msgstr "\t\t最初のコミットに移動" + +#: gitk:2691 +msgid "\t\tMove to last commit" +msgstr "\t\t最後のコミットに移動" + +#: gitk:2692 +msgid ", p, i\tMove up one commit" +msgstr ", p, i\t一つ上のコミットに移動" + +#: gitk:2693 +msgid ", n, k\tMove down one commit" +msgstr ", n, k\t一つ下のコミットに移動" + +#: gitk:2694 +msgid ", z, j\tGo back in history list" +msgstr ", z, j\t履歴の前に戻る" + +#: gitk:2695 +msgid ", x, l\tGo forward in history list" +msgstr ", x, l\t履歴の次へ進む" + +#: gitk:2696 +msgid "\tMove up one page in commit list" +msgstr "\tコミットリストの一つ上のページに移動" + +#: gitk:2697 +msgid "\tMove down one page in commit list" +msgstr "\tコミットリストの一つ下のページに移動" + +#: gitk:2698 +#, tcl-format +msgid "<%s-Home>\tScroll to top of commit list" +msgstr "<%s-Home>\tコミットリストの一番上にスクロールする" + +#: gitk:2699 +#, tcl-format +msgid "<%s-End>\tScroll to bottom of commit list" +msgstr "<%s-End>\tコミットリストの一番下にスクロールする" + +#: gitk:2700 +#, tcl-format +msgid "<%s-Up>\tScroll commit list up one line" +msgstr "<%s-Up>\tコミットリストの一つ下の行にスクロールする" + +#: gitk:2701 +#, tcl-format +msgid "<%s-Down>\tScroll commit list down one line" +msgstr "<%s-Down>\tコミットリストの一つ下の行にスクロールする" + +#: gitk:2702 +#, tcl-format +msgid "<%s-PageUp>\tScroll commit list up one page" +msgstr "<%s-PageUp>\tコミットリストの上のページにスクロールする" + +#: gitk:2703 +#, tcl-format +msgid "<%s-PageDown>\tScroll commit list down one page" +msgstr "<%s-PageDown>\tコミットリストの下のページにスクロールする" + +#: gitk:2704 +msgid "\tFind backwards (upwards, later commits)" +msgstr "\t後ろを検索 (上方・後のコミット)" + +#: gitk:2705 +msgid "\tFind forwards (downwards, earlier commits)" +msgstr "\t前を検索(下方・前のコミット)" + +#: gitk:2706 +msgid ", b\tScroll diff view up one page" +msgstr ", b\tdiff画面を上のページにスクロールする" + +#: gitk:2707 +msgid "\tScroll diff view up one page" +msgstr "\tdiff画面を上のページにスクロールする" + +#: gitk:2708 +msgid "\t\tScroll diff view down one page" +msgstr "\t\tdiff画面を下のページにスクロールする" + +#: gitk:2709 +msgid "u\t\tScroll diff view up 18 lines" +msgstr "u\t\tdiff画面を上に18行スクロールする" + +#: gitk:2710 +msgid "d\t\tScroll diff view down 18 lines" +msgstr "d\t\tdiff画面を下に18行スクロールする" + +#: gitk:2711 +#, tcl-format +msgid "<%s-F>\t\tFind" +msgstr "<%s-F>\t\t検索" + +#: gitk:2712 +#, tcl-format +msgid "<%s-G>\t\tMove to next find hit" +msgstr "<%s-G>\t\t次を検索して移動" + +#: gitk:2713 +msgid "\tMove to next find hit" +msgstr "\t次を検索して移動" + +#: gitk:2714 +msgid "/\t\tFocus the search box" +msgstr "/\t\t検索ボックスにフォーカス" + +#: gitk:2715 +msgid "?\t\tMove to previous find hit" +msgstr "?\t\t前を検索して移動" + +#: gitk:2716 +msgid "f\t\tScroll diff view to next file" +msgstr "f\t\t次のファイルにdiff画面をスクロールする" + +#: gitk:2717 +#, tcl-format +msgid "<%s-S>\t\tSearch for next hit in diff view" +msgstr "<%s-S>\t\tdiff画面の次を検索" + +#: gitk:2718 +#, tcl-format +msgid "<%s-R>\t\tSearch for previous hit in diff view" +msgstr "<%s-R>\t\tdiff画面の前を検索" + +#: gitk:2719 +#, tcl-format +msgid "<%s-KP+>\tIncrease font size" +msgstr "<%s-KP+>\t文字サイズを拡大" + +#: gitk:2720 +#, tcl-format +msgid "<%s-plus>\tIncrease font size" +msgstr "<%s-plus>\t文字サイズを拡大" + +#: gitk:2721 +#, tcl-format +msgid "<%s-KP->\tDecrease font size" +msgstr "<%s-KP->\t文字サイズを縮小" + +#: gitk:2722 +#, tcl-format +msgid "<%s-minus>\tDecrease font size" +msgstr "<%s-minus>\t文字サイズを縮小" + +#: gitk:2723 +msgid "\t\tUpdate" +msgstr "\t\t更新" + +#: gitk:3178 gitk:3187 +#, tcl-format +msgid "Error creating temporary directory %s:" +msgstr "一時ディレクトリ %s 生成時エラー:" + +#: gitk:3200 +#, tcl-format +msgid "Error getting \"%s\" from %s:" +msgstr "\"%s\" のエラーが %s に発生:" + +#: gitk:3263 +msgid "command failed:" +msgstr "コマンド失敗:" + +#: gitk:3409 +msgid "No such commit" +msgstr "そのようなコミットはありません" + +#: gitk:3423 +msgid "git gui blame: command failed:" +msgstr "git gui blame: コマンド失敗:" + +#: gitk:3454 +#, tcl-format +msgid "Couldn't read merge head: %s" +msgstr "マージする HEAD を読み込めません: %s" + +#: gitk:3462 +#, tcl-format +msgid "Error reading index: %s" +msgstr "インデックス読み込みエラー: %s" + +#: gitk:3487 +#, tcl-format +msgid "Couldn't start git blame: %s" +msgstr "git blame を始められません: %s" + +#: gitk:3490 gitk:6279 +msgid "Searching" +msgstr "検索中" + +#: gitk:3522 +#, tcl-format +msgid "Error running git blame: %s" +msgstr "git blame 実行エラー: %s" + +#: gitk:3550 +#, tcl-format +msgid "That line comes from commit %s, which is not in this view" +msgstr "コミット %s に由来するその行は、このビューに表示されていません" + +#: gitk:3564 +msgid "External diff viewer failed:" +msgstr "外部diffビューアが失敗:" + +#: gitk:3682 +msgid "Gitk view definition" +msgstr "Gitk ビュー定義" + +#: gitk:3686 +msgid "Remember this view" +msgstr "このビューを記憶する" + +#: gitk:3687 +msgid "References (space separated list):" +msgstr "リファレンス(スペース区切りのリスト):" + +#: gitk:3688 +msgid "Branches & tags:" +msgstr "ブランチ&タグ:" + +#: gitk:3689 +msgid "All refs" +msgstr "全てのリファレンス" + +#: gitk:3690 +msgid "All (local) branches" +msgstr "全ての(ローカルな)ブランチ" + +#: gitk:3691 +msgid "All tags" +msgstr "全てのタグ" + +#: gitk:3692 +msgid "All remote-tracking branches" +msgstr "全てのリモート追跡ブランチ" + +#: gitk:3693 +msgid "Commit Info (regular expressions):" +msgstr "コミット情報(正規表現):" + +#: gitk:3694 +msgid "Author:" +msgstr "作成者:" + +#: gitk:3695 +msgid "Committer:" +msgstr "コミットした人:" + +#: gitk:3696 +msgid "Commit Message:" +msgstr "コミットメッセージ:" + +#: gitk:3697 +msgid "Matches all Commit Info criteria" +msgstr "コミット情報の全ての条件に一致" + +#: gitk:3698 +msgid "Changes to Files:" +msgstr "変更したファイル:" + +#: gitk:3699 +msgid "Fixed String" +msgstr "固定文字列" + +#: gitk:3700 +msgid "Regular Expression" +msgstr "正規表現" + +#: gitk:3701 +msgid "Search string:" +msgstr "検索文字列:" + +#: gitk:3702 +msgid "" +"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" +msgstr "コミット日時 (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" + +#: gitk:3703 +msgid "Since:" +msgstr "期間の始め:" + +#: gitk:3704 +msgid "Until:" +msgstr "期間の終わり:" + +#: gitk:3705 +msgid "Limit and/or skip a number of revisions (positive integer):" +msgstr "制限・省略するリビジョンの数(正の整数):" + +#: gitk:3706 +msgid "Number to show:" +msgstr "表示する数:" + +#: gitk:3707 +msgid "Number to skip:" +msgstr "省略する数:" + +#: gitk:3708 +msgid "Miscellaneous options:" +msgstr "その他のオプション:" + +#: gitk:3709 +msgid "Strictly sort by date" +msgstr "厳密に日付順で並び替え" + +#: gitk:3710 +msgid "Mark branch sides" +msgstr "側枝マーク" + +#: gitk:3711 +msgid "Limit to first parent" +msgstr "最初の親に制限" + +#: gitk:3712 +msgid "Simple history" +msgstr "簡易な履歴" + +#: gitk:3713 +msgid "Additional arguments to git log:" +msgstr "git log への追加の引数:" + +#: gitk:3714 +msgid "Enter files and directories to include, one per line:" +msgstr "含まれるファイル・ディレクトリを一行ごとに入力:" + +#: gitk:3715 +msgid "Command to generate more commits to include:" +msgstr "コミット追加コマンド:" + +#: gitk:3837 +msgid "Gitk: edit view" +msgstr "Gitk: ビュー編集" + +#: gitk:3845 +msgid "-- criteria for selecting revisions" +msgstr "― リビジョンの選択条件" + +#: gitk:3850 +msgid "View Name:" +msgstr "ビュー名:" + +#: gitk:3925 +msgid "Apply (F5)" +msgstr "適用 (F5)" + +#: gitk:3963 +msgid "Error in commit selection arguments:" +msgstr "コミット選択引数のエラー:" + +#: gitk:4016 gitk:4068 gitk:4516 gitk:4530 gitk:5791 gitk:11232 gitk:11233 +msgid "None" +msgstr "無し" + +#: gitk:4464 gitk:6311 gitk:8073 gitk:8088 +msgid "Date" +msgstr "日付" + +#: gitk:4464 gitk:6311 +msgid "CDate" +msgstr "作成日" + +#: gitk:4613 gitk:4618 +msgid "Descendant" +msgstr "子孫" + +#: gitk:4614 +msgid "Not descendant" +msgstr "非子孫" + +#: gitk:4621 gitk:4626 +msgid "Ancestor" +msgstr "祖先" + +#: gitk:4622 +msgid "Not ancestor" +msgstr "非祖先" + +#: gitk:4912 +msgid "Local changes checked in to index but not committed" +msgstr "ステージされた、コミット前のローカルな変更" + +#: gitk:4948 +msgid "Local uncommitted changes, not checked in to index" +msgstr "ステージされていない、コミット前のローカルな変更" + +#: gitk:6629 +msgid "many" +msgstr "多数" + +#: gitk:6813 +msgid "Tags:" +msgstr "タグ:" + +#: gitk:6830 gitk:6836 gitk:8066 +msgid "Parent" +msgstr "親" + +#: gitk:6841 +msgid "Child" +msgstr "子" + +#: gitk:6850 +msgid "Branch" +msgstr "ブランチ" + +#: gitk:6853 +msgid "Follows" +msgstr "下位" + +#: gitk:6856 +msgid "Precedes" +msgstr "上位" + +#: gitk:7354 +#, tcl-format +msgid "Error getting diffs: %s" +msgstr "diff取得エラー: %s" + +#: gitk:7894 +msgid "Goto:" +msgstr "Goto:" + +#: gitk:7896 +msgid "SHA1 ID:" +msgstr "SHA1 ID:" + +#: gitk:7915 +#, tcl-format +msgid "Short SHA1 id %s is ambiguous" +msgstr "%s を含む SHA1 ID は複数存在します" + +#: gitk:7922 +#, tcl-format +msgid "Revision %s is not known" +msgstr "リビジョン %s は不明です" + +#: gitk:7932 +#, tcl-format +msgid "SHA1 id %s is not known" +msgstr "SHA1 id %s は不明です" + +#: gitk:7934 +#, tcl-format +msgid "Revision %s is not in the current view" +msgstr "リビジョン %s は現在のビューにはありません" + +#: gitk:8076 +msgid "Children" +msgstr "子供達" + +#: gitk:8133 +#, tcl-format +msgid "Reset %s branch to here" +msgstr "%s ブランチをここにリセットする" + +#: gitk:8135 +msgid "Detached head: can't reset" +msgstr "切り離されたHEAD: リセットできません" + +#: gitk:8244 gitk:8250 +msgid "Skipping merge commit " +msgstr "コミットマージをスキップ: " + +#: gitk:8259 gitk:8264 +msgid "Error getting patch ID for " +msgstr "パッチ取得エラー: ID " + +#: gitk:8260 gitk:8265 +msgid " - stopping\n" +msgstr " - 停止\n" + +#: gitk:8270 gitk:8273 gitk:8281 gitk:8294 gitk:8303 +msgid "Commit " +msgstr "コミット " + +#: gitk:8274 +msgid "" +" is the same patch as\n" +" " +msgstr "" +" は下記のパッチと同等\n" +" " + +#: gitk:8282 +msgid "" +" differs from\n" +" " +msgstr "" +" 下記からのdiff\n" +" " + +#: gitk:8284 +msgid "" +"Diff of commits:\n" +"\n" +msgstr "" +"コミットのdiff:\n" +"\n" + +#: gitk:8295 gitk:8304 +#, tcl-format +msgid " has %s children - stopping\n" +msgstr " には %s の子があります - 停止\n" + +#: gitk:8324 +#, tcl-format +msgid "Error writing commit to file: %s" +msgstr "ファイルへのコミット書き込みエラー: %s" + +#: gitk:8330 +#, tcl-format +msgid "Error diffing commits: %s" +msgstr "コミットのdiff実行エラー: %s" + +#: gitk:8360 +msgid "Top" +msgstr "Top" + +#: gitk:8361 +msgid "From" +msgstr "From" + +#: gitk:8366 +msgid "To" +msgstr "To" + +#: gitk:8390 +msgid "Generate patch" +msgstr "パッチ生成" + +#: gitk:8392 +msgid "From:" +msgstr "From:" + +#: gitk:8401 +msgid "To:" +msgstr "To:" + +#: gitk:8410 +msgid "Reverse" +msgstr "逆" + +#: gitk:8412 gitk:8597 +msgid "Output file:" +msgstr "出力ファイル:" + +#: gitk:8418 +msgid "Generate" +msgstr "生成" + +#: gitk:8456 +msgid "Error creating patch:" +msgstr "パッチ生成エラー:" + +#: gitk:8479 gitk:8585 gitk:8642 +msgid "ID:" +msgstr "ID:" + +#: gitk:8488 +msgid "Tag name:" +msgstr "タグ名:" + +#: gitk:8492 gitk:8651 +msgid "Create" +msgstr "生成" + +#: gitk:8509 +msgid "No tag name specified" +msgstr "タグの名称が指定されていません" + +#: gitk:8513 +#, tcl-format +msgid "Tag \"%s\" already exists" +msgstr "タグ \"%s\" は既に存在します" + +#: gitk:8519 +msgid "Error creating tag:" +msgstr "タグ生成エラー:" + +#: gitk:8594 +msgid "Command:" +msgstr "コマンド:" + +#: gitk:8602 +msgid "Write" +msgstr "書き込み" + +#: gitk:8620 +msgid "Error writing commit:" +msgstr "コミット書き込みエラー:" + +#: gitk:8647 +msgid "Name:" +msgstr "名前:" + +#: gitk:8670 +msgid "Please specify a name for the new branch" +msgstr "新しいブランチの名前を指定してください" + +#: gitk:8675 +#, tcl-format +msgid "Branch '%s' already exists. Overwrite?" +msgstr "ブランチ '%s' は既に存在します。上書きしますか?" + +#: gitk:8741 +#, tcl-format +msgid "Commit %s is already included in branch %s -- really re-apply it?" +msgstr "コミット %s は既にブランチ %s に含まれています ― 本当にこれを再適用しますか?" + +#: gitk:8746 +msgid "Cherry-picking" +msgstr "チェリーピック中" + +#: gitk:8755 +#, tcl-format +msgid "" +"Cherry-pick failed because of local changes to file '%s'.\n" +"Please commit, reset or stash your changes and try again." +msgstr "" +"ファイル '%s' のローカルな変更のためにチェリーピックは失敗しました。\n" +"あなたの変更に commit, reset, stash のいずれかを行ってからやり直してください。" + +#: gitk:8761 +msgid "" +"Cherry-pick failed because of merge conflict.\n" +"Do you wish to run git citool to resolve it?" +msgstr "" +"マージの衝突によってチェリーピックは失敗しました。\n" +"この解決のために git citool を実行したいですか?" + +#: gitk:8777 +msgid "No changes committed" +msgstr "何の変更もコミットされていません" + +#: gitk:8803 +msgid "Confirm reset" +msgstr "確認を取り消す" + +#: gitk:8805 +#, tcl-format +msgid "Reset branch %s to %s?" +msgstr "ブランチ %s を %s にリセットしますか?" + +#: gitk:8809 +msgid "Reset type:" +msgstr "Reset タイプ:" + +#: gitk:8813 +msgid "Soft: Leave working tree and index untouched" +msgstr "Soft: 作業ツリーもインデックスもそのままにする" + +#: gitk:8816 +msgid "Mixed: Leave working tree untouched, reset index" +msgstr "Mixed: 作業ツリーをそのままにして、インデックスをリセット" + +#: gitk:8819 +msgid "" +"Hard: Reset working tree and index\n" +"(discard ALL local changes)" +msgstr "" +"Hard: 作業ツリーやインデックスをリセット\n" +"(「全ての」ローカルな変更を破棄)" + +#: gitk:8836 +msgid "Resetting" +msgstr "リセット中" + +#: gitk:8893 +msgid "Checking out" +msgstr "チェックアウト" + +#: gitk:8946 +msgid "Cannot delete the currently checked-out branch" +msgstr "現在チェックアウトされているブランチを削除することはできません" + +#: gitk:8952 +#, tcl-format +msgid "" +"The commits on branch %s aren't on any other branch.\n" +"Really delete branch %s?" +msgstr "" +"ブランチ %s には他のブランチに存在しないコミットがあります。\n" +"本当にブランチ %s を削除しますか?" + +#: gitk:8983 +#, tcl-format +msgid "Tags and heads: %s" +msgstr "タグとHEAD: %s" + +#: gitk:8998 +msgid "Filter" +msgstr "フィルター" + +#: gitk:9293 +msgid "" +"Error reading commit topology information; branch and preceding/following " +"tag information will be incomplete." +msgstr "" +"コミット構造情報読み込みエラー; ブランチ及び上位/下位の" +"タグ情報が不完全であるようです。" + +#: gitk:10279 +msgid "Tag" +msgstr "タグ" + +#: gitk:10279 +msgid "Id" +msgstr "ID" + +#: gitk:10327 +msgid "Gitk font chooser" +msgstr "Gitk フォント選択" + +#: gitk:10344 +msgid "B" +msgstr "B" + +#: gitk:10347 +msgid "I" +msgstr "I" + +#: gitk:10443 +msgid "Gitk preferences" +msgstr "Gitk 設定" + +#: gitk:10445 +msgid "Commit list display options" +msgstr "コミットリスト表示オプション" + +#: gitk:10448 +msgid "Maximum graph width (lines)" +msgstr "最大グラフ幅(線の本数)" + +#: gitk:10452 +#, tcl-format +msgid "Maximum graph width (% of pane)" +msgstr "最大グラフ幅(ペインに対する%)" + +#: gitk:10456 +msgid "Show local changes" +msgstr "ローカルな変更を表示" + +#: gitk:10459 +msgid "Auto-select SHA1" +msgstr "SHA1 の自動選択" + +#: gitk:10463 +msgid "Diff display options" +msgstr "diff表示オプション" + +#: gitk:10465 +msgid "Tab spacing" +msgstr "タブ空白幅" + +#: gitk:10468 +msgid "Display nearby tags" +msgstr "近くのタグを表示する" + +#: gitk:10471 +msgid "Hide remote refs" +msgstr "リモートリファレンスを隠す" + +#: gitk:10474 +msgid "Limit diffs to listed paths" +msgstr "diff をリストのパスに制限" + +#: gitk:10477 +msgid "Support per-file encodings" +msgstr "ファイルごとのエンコーディングのサポート" + +#: gitk:10483 gitk:10548 +msgid "External diff tool" +msgstr "外部diffツール" + +#: gitk:10485 +msgid "Choose..." +msgstr "選択..." + +#: gitk:10490 +msgid "Colors: press to choose" +msgstr "色: ボタンを押して選択" + +#: gitk:10493 +msgid "Background" +msgstr "背景" + +#: gitk:10494 gitk:10524 +msgid "background" +msgstr "背景" + +#: gitk:10497 +msgid "Foreground" +msgstr "前景" + +#: gitk:10498 +msgid "foreground" +msgstr "前景" + +#: gitk:10501 +msgid "Diff: old lines" +msgstr "Diff: 旧バージョン" + +#: gitk:10502 +msgid "diff old lines" +msgstr "diff 旧バージョン" + +#: gitk:10506 +msgid "Diff: new lines" +msgstr "Diff: 新バージョン" + +#: gitk:10507 +msgid "diff new lines" +msgstr "diff 新バージョン" + +#: gitk:10511 +msgid "Diff: hunk header" +msgstr "Diff: hunkヘッダ" + +#: gitk:10513 +msgid "diff hunk header" +msgstr "diff hunkヘッダ" + +#: gitk:10517 +msgid "Marked line bg" +msgstr "マーク行の背景" + +#: gitk:10519 +msgid "marked line background" +msgstr "マーク行の背景" + +#: gitk:10523 +msgid "Select bg" +msgstr "選択の背景" + +#: gitk:10527 +msgid "Fonts: press to choose" +msgstr "フォント: ボタンを押して選択" + +#: gitk:10529 +msgid "Main font" +msgstr "主フォント" + +#: gitk:10530 +msgid "Diff display font" +msgstr "Diff表示用フォント" + +#: gitk:10531 +msgid "User interface font" +msgstr "UI用フォント" + +#: gitk:10558 +#, tcl-format +msgid "Gitk: choose color for %s" +msgstr "Gitk: 「%s」 の色を選択" + +#: gitk:11009 +msgid "" +"Sorry, gitk cannot run with this version of Tcl/Tk.\n" +" Gitk requires at least Tcl/Tk 8.4." +msgstr "" +"申し訳ありませんが、このバージョンの Tcl/Tk では gitk を実行できません。\n" +" Gitk は最低でも Tcl/Tk 8.4 を必要とします。" + +#: gitk:11137 +msgid "Cannot find a git repository here." +msgstr "ここにはgitリポジトリがありません。" + +#: gitk:11141 +#, tcl-format +msgid "Cannot find the git directory \"%s\"." +msgstr "gitディレクトリ \"%s\" を見つけられません。" + +#: gitk:11188 +#, tcl-format +msgid "Ambiguous argument '%s': both revision and filename" +msgstr "あいまいな引数 '%s': リビジョンとファイル名の両方に解釈できます" + +#: gitk:11200 +msgid "Bad arguments to gitk:" +msgstr "gitkへの不正な引数:" + +#: gitk:11285 +msgid "Command line" +msgstr "コマンド行" From 8d849957d81fc0480a52570d66cc3c2a688ecb1b Mon Sep 17 00:00:00 2001 From: Bernt Hansen Date: Mon, 2 Nov 2009 07:41:00 -0500 Subject: [PATCH 220/884] gitk: Skip translation of "wrong Tcl version" message We check the required Tcl version number before we setup msgcat for language translation. If the Tcl version is too old just display the untranslated error text. The caller of show_error can now pass an alternative function for mc. The Tcl list function turns the translation into a no-op. This fixes the error: Error in startup script: invalid command name "mc" when attempting to start gitk with Tcl 8.3. Tested with both Tcl 8.3 and 8.4. Signed-off-by: Bernt Hansen Signed-off-by: Paul Mackerras --- gitk | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gitk b/gitk index 59d5925199..db5ec54be8 100755 --- a/gitk +++ b/gitk @@ -1787,10 +1787,10 @@ proc make_transient {window origin} { } } -proc show_error {w top msg} { +proc show_error {w top msg {mc mc}} { message $w.m -text $msg -justify center -aspect 400 pack $w.m -side top -fill x -padx 20 -pady 20 - button $w.ok -text [mc OK] -command "destroy $top" + button $w.ok -text [$mc OK] -command "destroy $top" pack $w.ok -side bottom -fill x bind $top "grab $top; focus $top" bind $top "destroy $top" @@ -11034,8 +11034,8 @@ proc get_path_encoding {path} { # First check that Tcl/Tk is recent enough if {[catch {package require Tk 8.4} err]} { - show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\ - Gitk requires at least Tcl/Tk 8.4."] + show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\ + Gitk requires at least Tcl/Tk 8.4." list exit 1 } From c1d45cf7b0c1953eed72a3018b5e557dbcd538e0 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Tue, 3 Nov 2009 21:38:51 -0500 Subject: [PATCH 221/884] Require a struct remote in transport_get() cmd_ls_remote() was calling transport_get() with a NULL remote and a non-NULL url in the case where it was run outside a git repository. This involved a bunch of ill-tested special cases. Instead, simply get the struct remote for the URL with remote_get(), which works fine outside a git repository, and can also take global options into account. This fixes a tiny and obscure bug where "git ls-remote" without a repo didn't support global url.*.insteadOf, even though "git clone" and "git ls-remote" in any repo did. Also, enforce that all callers provide a struct remote to transport_get(). Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- builtin-ls-remote.c | 6 +++--- transport.c | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c index 78a88f7476..b5bad0c184 100644 --- a/builtin-ls-remote.c +++ b/builtin-ls-remote.c @@ -86,10 +86,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) pattern[j - i] = p; } } - remote = nongit ? NULL : remote_get(dest); - if (remote && !remote->url_nr) + remote = remote_get(dest); + if (!remote->url_nr) die("remote %s has no configured URL", dest); - transport = transport_get(remote, remote ? remote->url[0] : dest); + transport = transport_get(remote, remote->url[0]); if (uploadpack != NULL) transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); diff --git a/transport.c b/transport.c index 644a30a0b2..298dc46ec5 100644 --- a/transport.c +++ b/transport.c @@ -812,6 +812,9 @@ struct transport *transport_get(struct remote *remote, const char *url) { struct transport *ret = xcalloc(1, sizeof(*ret)); + if (!remote) + die("No remote provided to transport_get()"); + ret->remote = remote; ret->url = url; @@ -849,10 +852,10 @@ struct transport *transport_get(struct remote *remote, const char *url) data->thin = 1; data->conn = NULL; data->uploadpack = "git-upload-pack"; - if (remote && remote->uploadpack) + if (remote->uploadpack) data->uploadpack = remote->uploadpack; data->receivepack = "git-receive-pack"; - if (remote && remote->receivepack) + if (remote->receivepack) data->receivepack = remote->receivepack; } From a45d3d7effdfb4b6b9fe75c9206a79d3cdcb3922 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Tue, 3 Nov 2009 21:52:35 -0500 Subject: [PATCH 222/884] Allow curl helper to work without a local repository It's okay to use the curl helper without a local repository, so long as you don't use "fetch". There aren't any git programs that would try to use it, and it doesn't make sense to try it (since there's nowhere to write the results), but we may as well be clear. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- remote-curl.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/remote-curl.c b/remote-curl.c index 2faf1c6344..ebdab3603e 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -82,9 +82,10 @@ int main(int argc, const char **argv) struct strbuf buf = STRBUF_INIT; const char *url; struct walker *walker = NULL; + int nongit; git_extract_argv0_path(argv[0]); - setup_git_directory(); + setup_git_directory_gently(&nongit); if (argc < 2) { fprintf(stderr, "Remote needed\n"); return 1; @@ -103,6 +104,8 @@ int main(int argc, const char **argv) break; if (!prefixcmp(buf.buf, "fetch ")) { char *obj = buf.buf + strlen("fetch "); + if (nongit) + die("Fetch attempted without a local repo"); if (!walker) walker = get_http_walker(url, remote); walker->get_all = 1; From d79d9e13375c43b8774b193ce725ac6c05b3865e Mon Sep 17 00:00:00 2001 From: Ben Walton Date: Wed, 4 Nov 2009 13:05:59 -0500 Subject: [PATCH 223/884] configure: add macro to set arbitrary make variables Add macro GIT_PARSE_WITH_SET_MAKE_VAR to configure.ac to allow --with style options that set values for variables used during the make process. Arguments are the $name part of --with-$name, the name of the variable to set in the Makefile (config.mak.autogen) and the help text for the option. Signed-off-by: Ben Walton Signed-off-by: Junio C Hamano --- configure.ac | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/configure.ac b/configure.ac index b09b8e446f..c48e7135e8 100644 --- a/configure.ac +++ b/configure.ac @@ -68,6 +68,26 @@ else \ GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \ fi \ ])# GIT_PARSE_WITH +# +# GIT_PARSE_WITH_SET_MAKE_VAR(WITHNAME, VAR, HELP_TEXT) +# --------------------- +# Set VAR to the value specied by --with-WITHNAME. +# No verification of arguments is performed, but warnings are issued +# if either 'yes' or 'no' is specified. +# HELP_TEXT is presented when --help is called. +# This is a direct way to allow setting variables in the Makefile. +AC_DEFUN([GIT_PARSE_WITH_SET_MAKE_VAR], +[AC_ARG_WITH([$1], + [AS_HELP_STRING([--with-$1=VALUE], $3)], + if test -n "$withval"; then \ + if test "$withval" = "yes" -o "$withval" = "no"; then \ + AC_MSG_WARN([You likely do not want either 'yes' or 'no' as] + [a value for $1 ($2). Maybe you do...?]); \ + fi; \ + \ + AC_MSG_NOTICE([Setting $2 to $withval]); \ + GIT_CONF_APPEND_LINE($2=$withval); \ + fi)])# GIT_PARSE_WITH_SET_MAKE_VAR dnl dnl GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE) From 5ca5377da017311a08f625e32c58a0d118e7fda6 Mon Sep 17 00:00:00 2001 From: Ben Walton Date: Wed, 4 Nov 2009 13:06:00 -0500 Subject: [PATCH 224/884] configure: add settings for gitconfig, editor and pager Use the new GIT_PARSE_WITH_SET_MAKE_VAR macro to allow configuration settings for ETC_GITCONFIG, DEFAULT_PAGER and DEFAULT_EDITOR. Signed-off-by: Ben Walton Signed-off-by: Junio C Hamano --- configure.ac | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/configure.ac b/configure.ac index c48e7135e8..4625b8672b 100644 --- a/configure.ac +++ b/configure.ac @@ -246,6 +246,29 @@ GIT_PARSE_WITH(iconv)) # Define USE_STDEV below if you want git to care about the underlying device # change being considered an inode change from the update-index perspective. +# +# Allow user to set ETC_GITCONFIG variable +GIT_PARSE_WITH_SET_MAKE_VAR(gitconfig, ETC_GITCONFIG, + Use VALUE instead of /etc/gitconfig as the + global git configuration file. + If VALUE is not fully qualified it will be interpretted + as a path relative to the computed prefix at runtime.) + +# +# Allow user to set the default pager +GIT_PARSE_WITH_SET_MAKE_VAR(pager, DEFAULT_PAGER, + Use VALUE as the fall-back pager instead of 'less'. + This is used by things like 'git log' when the user + does not specify a pager to use through alternate + methods. eg: /usr/bin/pager) +# +# Allow user to set the default editor +GIT_PARSE_WITH_SET_MAKE_VAR(editor, DEFAULT_EDITOR, + Use VALUE as the fall-back editor instead of 'vi'. + This is used by things like 'git commit' when the user + does not specify a preferred editor through other + methods. eg: /usr/bin/editor) + # # Define SHELL_PATH to provide path to shell. GIT_ARG_SET_PATH(shell) From 0a565de4a50032339d3786d366e70912a4dcf572 Mon Sep 17 00:00:00 2001 From: Gisle Aas Date: Wed, 4 Nov 2009 22:57:46 +0100 Subject: [PATCH 225/884] Fix documentation grammar typo Introduced in 492cf3f (More precise description of 'git describe --abbrev', 2009-10-29) Signed-off-by: Gisle Aas Signed-off-by: Junio C Hamano --- Documentation/git-describe.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index e9dbca7d87..2f97916781 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -120,7 +120,7 @@ closest tagname without any suffix: tags/v1.0.0 Note that the suffix you get if you type these commands today may be -longer than what Linus saw above when he ran this command, as your +longer than what Linus saw above when he ran these commands, as your git repository may have new commits whose object names begin with 975b that did not exist back then, and "-g975b" suffix alone may not be sufficient to disambiguate these commits. From c51f6ceed6a9a436f16f8b4f17eab1a3d17cffed Mon Sep 17 00:00:00 2001 From: Erick Mattos Date: Wed, 4 Nov 2009 01:20:11 -0200 Subject: [PATCH 226/884] commit -c/-C/--amend: reset timestamp and authorship to committer with --reset-author When we use -c, -C, or --amend, we are trying one of two things: using the source as a template or modifying a commit with corrections. When these options are used, the authorship and timestamp recorded in the newly created commit are always taken from the original commit. This is inconvenient when we just want to borrow the commit log message or when our change to the code is so significant that we should take over the authorship (with the blame for bugs we introduce, of course). The new --reset-author option is meant to solve this need by regenerating the timestamp and setting the committer as the new author. Signed-off-by: Erick Mattos Signed-off-by: Junio C Hamano --- Documentation/git-commit.txt | 7 ++- builtin-commit.c | 12 +++- t/t7509-commit.sh | 114 +++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 4 deletions(-) create mode 100755 t/t7509-commit.sh diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 0578a40d84..f89db9a0ff 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git commit' [-a | --interactive] [-s] [-v] [-u] [--amend] [--dry-run] - [(-c | -C) ] [-F | -m ] + [(-c | -C) ] [-F | -m ] [--reset-author] [--allow-empty] [--no-verify] [-e] [--author=] [--cleanup=] [--] [[-i | -o ]...] @@ -69,6 +69,11 @@ OPTIONS Like '-C', but with '-c' the editor is invoked, so that the user can further edit the commit message. +--reset-author:: + When used with -C/-c/--amend options, declare that the + authorship of the resulting commit now belongs of the committer. + This also renews the author timestamp. + -F :: --file=:: Take the commit message from the given file. Use '-' to diff --git a/builtin-commit.c b/builtin-commit.c index beddf01dd3..764f4fdaac 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -51,7 +51,7 @@ static const char *template_file; static char *edit_message, *use_message; static char *author_name, *author_email, *author_date; static int all, edit_flag, also, interactive, only, amend, signoff; -static int quiet, verbose, no_verify, allow_empty, dry_run; +static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static char *untracked_files_arg; /* * The default commit message cleanup mode will remove the lines @@ -91,8 +91,9 @@ static struct option builtin_commit_options[] = { OPT_FILENAME('F', "file", &logfile, "read log from file"), OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"), OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m), - OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit "), + OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"), OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"), + OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"), OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_FILENAME('t', "template", &template_file, "use specified template file"), OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"), @@ -381,7 +382,7 @@ static void determine_author_info(void) email = getenv("GIT_AUTHOR_EMAIL"); date = getenv("GIT_AUTHOR_DATE"); - if (use_message) { + if (use_message && !renew_authorship) { const char *a, *lb, *rb, *eol; a = strstr(use_message_buffer, "\nauthor "); @@ -747,6 +748,9 @@ static int parse_and_validate_options(int argc, const char *argv[], if (force_author && !strchr(force_author, '>')) force_author = find_author_by_nickname(force_author); + if (force_author && renew_authorship) + die("Using both --reset-author and --author does not make sense"); + if (logfile || message.len || use_message) use_editor = 0; if (edit_flag) @@ -780,6 +784,8 @@ static int parse_and_validate_options(int argc, const char *argv[], use_message = edit_message; if (amend && !use_message) use_message = "HEAD"; + if (!use_message && renew_authorship) + die("--reset-author can be used only with -C, -c or --amend."); if (use_message) { unsigned char sha1[20]; static char utf8[] = "UTF-8"; diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh new file mode 100755 index 0000000000..d52c060b06 --- /dev/null +++ b/t/t7509-commit.sh @@ -0,0 +1,114 @@ +#!/bin/sh +# +# Copyright (c) 2009 Erick Mattos +# + +test_description='git commit --reset-author' + +. ./test-lib.sh + +author_header () { + git cat-file commit "$1" | + sed -n -e '/^$/q' -e '/^author /p' +} + +message_body () { + git cat-file commit "$1" | + sed -e '1,/^$/d' +} + +test_expect_success '-C option copies authorship and message' ' + echo "Initial" >foo && + git add foo && + test_tick && + git commit -m "Initial Commit" --author Frigate\ \ && + git tag Initial && + echo "Test 1" >>foo && + test_tick && + git commit -a -C Initial && + author_header Initial >expect && + author_header HEAD >actual && + test_cmp expect actual && + + message_body Initial >expect && + message_body HEAD >actual && + test_cmp expect actual +' + +test_expect_success '-C option copies only the message with --reset-author' ' + echo "Test 2" >>foo && + test_tick && + git commit -a -C Initial --reset-author && + echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect && + author_header HEAD >actual + test_cmp expect actual && + + message_body Initial >expect && + message_body HEAD >actual && + test_cmp expect actual +' + +test_expect_success '-c option copies authorship and message' ' + echo "Test 3" >>foo && + test_tick && + EDITOR=: VISUAL=: git commit -a -c Initial && + author_header Initial >expect && + author_header HEAD >actual && + test_cmp expect actual +' + +test_expect_success '-c option copies only the message with --reset-author' ' + echo "Test 4" >>foo && + test_tick && + EDITOR=: VISUAL=: git commit -a -c Initial --reset-author && + echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect && + author_header HEAD >actual && + test_cmp expect actual && + + message_body Initial >expect && + message_body HEAD >actual && + test_cmp expect actual +' + +test_expect_success '--amend option copies authorship' ' + git checkout Initial && + echo "Test 5" >>foo && + test_tick && + git commit -a --amend -m "amend test" && + author_header Initial >expect && + author_header HEAD >actual && + + echo "amend test" >expect && + message_body HEAD >actual && + test_cmp expect actual +' + +test_expect_success '--reset-author makes the commit ours even with --amend option' ' + git checkout Initial && + echo "Test 6" >>foo && + test_tick && + git commit -a --reset-author -m "Changed again" --amend && + echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect && + author_header HEAD >actual && + test_cmp expect actual && + + echo "Changed again" >expect && + message_body HEAD >actual && + test_cmp expect actual +' + +test_expect_success '--reset-author and --author are mutually exclusive' ' + git checkout Initial && + echo "Test 7" >>foo && + test_tick && + test_must_fail git commit -a --reset-author --author="Xyzzy " +' + +test_expect_success '--reset-author should be rejected without -c/-C/--amend' ' + git checkout Initial && + echo "Test 7" >>foo && + test_tick && + test_must_fail git commit -a --reset-author -m done +' + +test_done From 2f4038ab337e55017d4ff21ddbae9427544ca02c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:32 -0700 Subject: [PATCH 227/884] Git-aware CGI to provide dumb HTTP transport The git-http-backend CGI can be configured into any Apache server using ScriptAlias, such as with the following configuration: LoadModule cgi_module /usr/libexec/apache2/mod_cgi.so LoadModule alias_module /usr/libexec/apache2/mod_alias.so ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ Repositories are accessed via the translated PATH_INFO. The CGI is backwards compatible with the dumb client, allowing all older HTTP clients to continue to download repositories which are managed by the CGI. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- .gitignore | 1 + Documentation/git-http-backend.txt | 105 +++++++++++ Makefile | 1 + http-backend.c | 289 +++++++++++++++++++++++++++++ 4 files changed, 396 insertions(+) create mode 100644 Documentation/git-http-backend.txt create mode 100644 http-backend.c diff --git a/.gitignore b/.gitignore index 51a37b1af7..353d22fc9a 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ git-get-tar-commit-id git-grep git-hash-object git-help +git-http-backend git-http-fetch git-http-push git-imap-send diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt new file mode 100644 index 0000000000..867675fcec --- /dev/null +++ b/Documentation/git-http-backend.txt @@ -0,0 +1,105 @@ +git-http-backend(1) +=================== + +NAME +---- +git-http-backend - Server side implementation of Git over HTTP + +SYNOPSIS +-------- +[verse] +'git-http-backend' + +DESCRIPTION +----------- +A simple CGI program to serve the contents of a Git repository to Git +clients accessing the repository over http:// and https:// protocols. + +By default, only the `upload-pack` service is enabled, which serves +'git-fetch-pack' and 'git-ls-remote' clients, which are invoked from +'git-fetch', 'git-pull', and 'git-clone'. + +This is ideally suited for read-only updates, i.e., pulling from +git repositories. + +URL TRANSLATION +--------------- +'git-http-backend' relies on the invoking web server to perform +URL to path translation, and store the repository path into the +PATH_TRANSLATED environment variable. Most web servers will do +this translation automatically, resolving the suffix after the +CGI name relative to the server's document root. + +EXAMPLES +-------- + +Apache 2.x:: + To serve all Git repositories contained within the '/git/' + subdirectory of the DocumentRoot, ensure mod_cgi and + mod_alias are enabled, and create a ScriptAlias to the CGI: ++ +---------------------------------------------------------------- +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/ + + + Options None + + + Options ExecCGI + +---------------------------------------------------------------- ++ +To require authentication for reads, use a Directory +directive around the repository, or one of its parent directories: ++ +---------------------------------------------------------------- + + AuthType Basic + AuthName "Private Git Access" + Require group committers + ... + +---------------------------------------------------------------- + +Accelerated static Apache 2.x:: + Similar to the above, but Apache can be used to return static + files that are stored on disk. On many systems this may + be more efficient as Apache can ask the kernel to copy the + file contents from the file system directly to the network: ++ +---------------------------------------------------------------- +DocumentRoot /var/www + +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/ +Alias /git_static/ /var/www/git/ + +RewriteEngine on +RewriteRule ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /git_static/$1 [PT] +RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.pack)$ /git_static/$1 [PT] +RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.idx)$ /git_static/$1 [PT] +---------------------------------------------------------------- + + +ENVIRONMENT +----------- +'git-http-backend' relies upon the CGI environment variables set +by the invoking web server, including: + +* PATH_TRANSLATED +* REMOTE_USER +* REMOTE_ADDR +* CONTENT_TYPE +* QUERY_STRING +* REQUEST_METHOD + +Author +------ +Written by Shawn O. Pearce . + +Documentation +-------------- +Documentation by Shawn O. Pearce . + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Makefile b/Makefile index fea237bc80..271c290171 100644 --- a/Makefile +++ b/Makefile @@ -365,6 +365,7 @@ PROGRAMS += git-show-index$X PROGRAMS += git-unpack-file$X PROGRAMS += git-upload-pack$X PROGRAMS += git-var$X +PROGRAMS += git-http-backend$X # List built-in command $C whose implementation cmd_$C() is not in # builtin-$C.o but is linked in as part of some other command. diff --git a/http-backend.c b/http-backend.c new file mode 100644 index 0000000000..22bec56b98 --- /dev/null +++ b/http-backend.c @@ -0,0 +1,289 @@ +#include "cache.h" +#include "refs.h" +#include "pkt-line.h" +#include "object.h" +#include "tag.h" +#include "exec_cmd.h" + +static const char content_type[] = "Content-Type"; +static const char content_length[] = "Content-Length"; +static const char last_modified[] = "Last-Modified"; + +static void format_write(int fd, const char *fmt, ...) +{ + static char buffer[1024]; + + va_list args; + unsigned n; + + va_start(args, fmt); + n = vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + if (n >= sizeof(buffer)) + die("protocol error: impossibly long line"); + + safe_write(fd, buffer, n); +} + +static void http_status(unsigned code, const char *msg) +{ + format_write(1, "Status: %u %s\r\n", code, msg); +} + +static void hdr_str(const char *name, const char *value) +{ + format_write(1, "%s: %s\r\n", name, value); +} + +static void hdr_int(const char *name, size_t value) +{ + format_write(1, "%s: %" PRIuMAX "\r\n", name, value); +} + +static void hdr_date(const char *name, unsigned long when) +{ + const char *value = show_date(when, 0, DATE_RFC2822); + hdr_str(name, value); +} + +static void hdr_nocache(void) +{ + hdr_str("Expires", "Fri, 01 Jan 1980 00:00:00 GMT"); + hdr_str("Pragma", "no-cache"); + hdr_str("Cache-Control", "no-cache, max-age=0, must-revalidate"); +} + +static void hdr_cache_forever(void) +{ + unsigned long now = time(NULL); + hdr_date("Date", now); + hdr_date("Expires", now + 31536000); + hdr_str("Cache-Control", "public, max-age=31536000"); +} + +static void end_headers(void) +{ + safe_write(1, "\r\n", 2); +} + +static NORETURN void not_found(const char *err, ...) +{ + va_list params; + + http_status(404, "Not Found"); + hdr_nocache(); + end_headers(); + + va_start(params, err); + if (err && *err) + vfprintf(stderr, err, params); + va_end(params); + exit(0); +} + +static void send_strbuf(const char *type, struct strbuf *buf) +{ + hdr_int(content_length, buf->len); + hdr_str(content_type, type); + end_headers(); + safe_write(1, buf->buf, buf->len); +} + +static void send_file(const char *the_type, const char *name) +{ + const char *p = git_path("%s", name); + size_t buf_alloc = 8192; + char *buf = xmalloc(buf_alloc); + int fd; + struct stat sb; + size_t size; + + fd = open(p, O_RDONLY); + if (fd < 0) + not_found("Cannot open '%s': %s", p, strerror(errno)); + if (fstat(fd, &sb) < 0) + die_errno("Cannot stat '%s'", p); + + size = xsize_t(sb.st_size); + + hdr_int(content_length, size); + hdr_str(content_type, the_type); + hdr_date(last_modified, sb.st_mtime); + end_headers(); + + while (size) { + ssize_t n = xread(fd, buf, buf_alloc); + if (n < 0) + die_errno("Cannot read '%s'", p); + if (!n) + break; + safe_write(1, buf, n); + } + close(fd); + free(buf); +} + +static void get_text_file(char *name) +{ + hdr_nocache(); + send_file("text/plain", name); +} + +static void get_loose_object(char *name) +{ + hdr_cache_forever(); + send_file("application/x-git-loose-object", name); +} + +static void get_pack_file(char *name) +{ + hdr_cache_forever(); + send_file("application/x-git-packed-objects", name); +} + +static void get_idx_file(char *name) +{ + hdr_cache_forever(); + send_file("application/x-git-packed-objects-toc", name); +} + +static int show_text_ref(const char *name, const unsigned char *sha1, + int flag, void *cb_data) +{ + struct strbuf *buf = cb_data; + struct object *o = parse_object(sha1); + if (!o) + return 0; + + strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name); + if (o->type == OBJ_TAG) { + o = deref_tag(o, name, 0); + if (!o) + return 0; + strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1), name); + } + return 0; +} + +static void get_info_refs(char *arg) +{ + struct strbuf buf = STRBUF_INIT; + + for_each_ref(show_text_ref, &buf); + hdr_nocache(); + send_strbuf("text/plain", &buf); + strbuf_release(&buf); +} + +static void get_info_packs(char *arg) +{ + size_t objdirlen = strlen(get_object_directory()); + struct strbuf buf = STRBUF_INIT; + struct packed_git *p; + size_t cnt = 0; + + prepare_packed_git(); + for (p = packed_git; p; p = p->next) { + if (p->pack_local) + cnt++; + } + + strbuf_grow(&buf, cnt * 53 + 2); + for (p = packed_git; p; p = p->next) { + if (p->pack_local) + strbuf_addf(&buf, "P %s\n", p->pack_name + objdirlen + 6); + } + strbuf_addch(&buf, '\n'); + + hdr_nocache(); + send_strbuf("text/plain; charset=utf-8", &buf); + strbuf_release(&buf); +} + +static NORETURN void die_webcgi(const char *err, va_list params) +{ + char buffer[1000]; + + http_status(500, "Internal Server Error"); + hdr_nocache(); + end_headers(); + + vsnprintf(buffer, sizeof(buffer), err, params); + fprintf(stderr, "fatal: %s\n", buffer); + exit(0); +} + +static struct service_cmd { + const char *method; + const char *pattern; + void (*imp)(char *); +} services[] = { + {"GET", "/HEAD$", get_text_file}, + {"GET", "/info/refs$", get_info_refs}, + {"GET", "/objects/info/alternates$", get_text_file}, + {"GET", "/objects/info/http-alternates$", get_text_file}, + {"GET", "/objects/info/packs$", get_info_packs}, + {"GET", "/objects/[0-9a-f]{2}/[0-9a-f]{38}$", get_loose_object}, + {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.pack$", get_pack_file}, + {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file} +}; + +int main(int argc, char **argv) +{ + char *method = getenv("REQUEST_METHOD"); + char *dir = getenv("PATH_TRANSLATED"); + struct service_cmd *cmd = NULL; + char *cmd_arg = NULL; + int i; + + git_extract_argv0_path(argv[0]); + set_die_routine(die_webcgi); + + if (!method) + die("No REQUEST_METHOD from server"); + if (!strcmp(method, "HEAD")) + method = "GET"; + if (!dir) + die("No PATH_TRANSLATED from server"); + + for (i = 0; i < ARRAY_SIZE(services); i++) { + struct service_cmd *c = &services[i]; + regex_t re; + regmatch_t out[1]; + + if (regcomp(&re, c->pattern, REG_EXTENDED)) + die("Bogus regex in service table: %s", c->pattern); + if (!regexec(&re, dir, 1, out, 0)) { + size_t n = out[0].rm_eo - out[0].rm_so; + + if (strcmp(method, c->method)) { + const char *proto = getenv("SERVER_PROTOCOL"); + if (proto && !strcmp(proto, "HTTP/1.1")) + http_status(405, "Method Not Allowed"); + else + http_status(400, "Bad Request"); + hdr_nocache(); + end_headers(); + return 0; + } + + cmd = c; + cmd_arg = xmalloc(n); + strncpy(cmd_arg, dir + out[0].rm_so + 1, n); + cmd_arg[n] = '\0'; + dir[out[0].rm_so] = 0; + break; + } + regfree(&re); + } + + if (!cmd) + not_found("Request not supported: '%s'", dir); + + setup_path(); + if (!enter_repo(dir, 0)) + not_found("Not a git repository: '%s'", dir); + + cmd->imp(cmd_arg); + return 0; +} From 42526b478e369d7e8c9a95186ad87fae9930eea5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:33 -0700 Subject: [PATCH 228/884] Add stateless RPC options to upload-pack, receive-pack When --stateless-rpc is passed as a command line parameter to upload-pack or receive-pack the programs now assume they may perform only a single read-write cycle with stdin and stdout. This fits with the HTTP POST request processing model where a program may read the request, write a response, and must exit. When --advertise-refs is passed as a command line parameter only the initial ref advertisement is output, and the program exits immediately. This fits with the HTTP GET request model, where no request content is received but a response must be produced. HTTP headers and/or environment are not processed here, but instead are assumed to be handled by the program invoking either service backend. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-receive-pack.c | 26 ++++++++++++++++++++------ upload-pack.c | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index b771fe9b20..70ff8c5d75 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -615,6 +615,8 @@ static void add_alternate_refs(void) int cmd_receive_pack(int argc, const char **argv, const char *prefix) { + int advertise_refs = 0; + int stateless_rpc = 0; int i; char *dir = NULL; @@ -623,7 +625,15 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) const char *arg = *argv++; if (*arg == '-') { - /* Do flag handling here */ + if (!strcmp(arg, "--advertise-refs")) { + advertise_refs = 1; + continue; + } + if (!strcmp(arg, "--stateless-rpc")) { + stateless_rpc = 1; + continue; + } + usage(receive_pack_usage); } if (dir) @@ -652,12 +662,16 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) " report-status delete-refs ofs-delta " : " report-status delete-refs "; - add_alternate_refs(); - write_head_info(); - clear_extra_refs(); + if (advertise_refs || !stateless_rpc) { + add_alternate_refs(); + write_head_info(); + clear_extra_refs(); - /* EOF */ - packet_flush(1); + /* EOF */ + packet_flush(1); + } + if (advertise_refs) + return 0; read_head_info(); if (commands) { diff --git a/upload-pack.c b/upload-pack.c index f1dc3a351c..70badcf6f7 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -39,6 +39,8 @@ static unsigned int timeout; */ static int use_sideband; static int debug_fd; +static int advertise_refs; +static int stateless_rpc; static void reset_timeout(void) { @@ -509,6 +511,8 @@ static int get_common_commits(void) if (!len) { if (have_obj.nr == 0 || multi_ack) packet_write(1, "NAK\n"); + if (stateless_rpc) + exit(0); continue; } strip(line, len); @@ -710,12 +714,32 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo return 0; } +static int mark_our_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +{ + struct object *o = parse_object(sha1); + if (!o) + die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1)); + if (!(o->flags & OUR_REF)) { + o->flags |= OUR_REF; + nr_our_refs++; + } + return 0; +} + static void upload_pack(void) { - reset_timeout(); - head_ref(send_ref, NULL); - for_each_ref(send_ref, NULL); - packet_flush(1); + if (advertise_refs || !stateless_rpc) { + reset_timeout(); + head_ref(send_ref, NULL); + for_each_ref(send_ref, NULL); + packet_flush(1); + } else { + head_ref(mark_our_ref, NULL); + for_each_ref(mark_our_ref, NULL); + } + if (advertise_refs) + return; + receive_needs(); if (want_obj.nr) { get_common_commits(); @@ -737,6 +761,14 @@ int main(int argc, char **argv) if (arg[0] != '-') break; + if (!strcmp(arg, "--advertise-refs")) { + advertise_refs = 1; + continue; + } + if (!strcmp(arg, "--stateless-rpc")) { + stateless_rpc = 1; + continue; + } if (!strcmp(arg, "--strict")) { strict = 1; continue; From 556cfa3b6d316074d41cd73c4659402fdb6207c8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:34 -0700 Subject: [PATCH 229/884] Smart fetch and push over HTTP: server side Requests for $GIT_URL/git-receive-pack and $GIT_URL/git-upload-pack are forwarded to the corresponding backend process by directly executing it and leaving stdin and stdout connected to the invoking web server. Prior to starting the backend process the HTTP response headers are sent, thereby freeing the backend from needing to know about the HTTP protocol. Requests that are encoded with Content-Encoding: gzip are automatically inflated before being streamed into the backend. This is primarily useful for the git-upload-pack backend, which receives highly repetitive text data from clients that easily compresses to 50% of its original size. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-http-backend.txt | 39 +++- http-backend.c | 324 ++++++++++++++++++++++++++++- 2 files changed, 359 insertions(+), 4 deletions(-) diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index 867675fcec..022a2433a8 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -22,6 +22,23 @@ By default, only the `upload-pack` service is enabled, which serves This is ideally suited for read-only updates, i.e., pulling from git repositories. +SERVICES +-------- +These services can be enabled/disabled using the per-repository +configuration file: + +http.uploadpack:: + This serves 'git-fetch-pack' and 'git-ls-remote' clients. + It is enabled by default, but a repository can disable it + by setting this configuration item to `false`. + +http.receivepack:: + This serves 'git-send-pack' clients, allowing push. It is + disabled by default for anonymous users, and enabled by + default for users authenticated by the web server. It can be + disabled by setting this item to `false`, or enabled for all + users, including anonymous users, by setting it to `true`. + URL TRANSLATION --------------- 'git-http-backend' relies on the invoking web server to perform @@ -49,7 +66,19 @@ ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/ ---------------------------------------------------------------- + -To require authentication for reads, use a Directory +To enable anonymous read access but authenticated write access, +require authorization with a LocationMatch directive: ++ +---------------------------------------------------------------- + + AuthType Basic + AuthName "Git Access" + Require group committers + ... + +---------------------------------------------------------------- ++ +To require authentication for both reads and writes, use a Directory directive around the repository, or one of its parent directories: + ---------------------------------------------------------------- @@ -92,6 +121,14 @@ by the invoking web server, including: * QUERY_STRING * REQUEST_METHOD +The backend process sets GIT_COMMITTER_NAME to '$REMOTE_USER' and +GIT_COMMITTER_EMAIL to '$\{REMOTE_USER}@http.$\{REMOTE_ADDR\}', +ensuring that any reflogs created by 'git-receive-pack' contain some +identifying information of the remote user who performed the push. + +All CGI environment variables are available to each of the hooks +invoked by the 'git-receive-pack'. + Author ------ Written by Shawn O. Pearce . diff --git a/http-backend.c b/http-backend.c index 22bec56b98..bfce52063f 100644 --- a/http-backend.c +++ b/http-backend.c @@ -4,11 +4,109 @@ #include "object.h" #include "tag.h" #include "exec_cmd.h" +#include "run-command.h" +#include "string-list.h" static const char content_type[] = "Content-Type"; static const char content_length[] = "Content-Length"; static const char last_modified[] = "Last-Modified"; +static struct string_list *query_params; + +struct rpc_service { + const char *name; + const char *config_name; + signed enabled : 2; +}; + +static struct rpc_service rpc_service[] = { + { "upload-pack", "uploadpack", 1 }, + { "receive-pack", "receivepack", -1 }, +}; + +static int decode_char(const char *q) +{ + int i; + unsigned char val = 0; + for (i = 0; i < 2; i++) { + unsigned char c = *q++; + val <<= 4; + if (c >= '0' && c <= '9') + val += c - '0'; + else if (c >= 'a' && c <= 'f') + val += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val += c - 'A' + 10; + else + return -1; + } + return val; +} + +static char *decode_parameter(const char **query, int is_name) +{ + const char *q = *query; + struct strbuf out; + + strbuf_init(&out, 16); + do { + unsigned char c = *q; + + if (!c) + break; + if (c == '&' || (is_name && c == '=')) { + q++; + break; + } + + if (c == '%') { + int val = decode_char(q + 1); + if (0 <= val) { + strbuf_addch(&out, val); + q += 3; + continue; + } + } + + if (c == '+') + strbuf_addch(&out, ' '); + else + strbuf_addch(&out, c); + q++; + } while (1); + *query = q; + return strbuf_detach(&out, NULL); +} + +static struct string_list *get_parameters(void) +{ + if (!query_params) { + const char *query = getenv("QUERY_STRING"); + + query_params = xcalloc(1, sizeof(*query_params)); + while (query && *query) { + char *name = decode_parameter(&query, 1); + char *value = decode_parameter(&query, 0); + struct string_list_item *i; + + i = string_list_lookup(name, query_params); + if (!i) + i = string_list_insert(name, query_params); + else + free(i->util); + i->util = value; + } + } + return query_params; +} + +static const char *get_parameter(const char *name) +{ + struct string_list_item *i; + i = string_list_lookup(name, get_parameters()); + return i ? i->util : NULL; +} + static void format_write(int fd, const char *fmt, ...) { static char buffer[1024]; @@ -81,6 +179,21 @@ static NORETURN void not_found(const char *err, ...) exit(0); } +static NORETURN void forbidden(const char *err, ...) +{ + va_list params; + + http_status(403, "Forbidden"); + hdr_nocache(); + end_headers(); + + va_start(params, err); + if (err && *err) + vfprintf(stderr, err, params); + va_end(params); + exit(0); +} + static void send_strbuf(const char *type, struct strbuf *buf) { hdr_int(content_length, buf->len); @@ -147,6 +260,145 @@ static void get_idx_file(char *name) send_file("application/x-git-packed-objects-toc", name); } +static int http_config(const char *var, const char *value, void *cb) +{ + struct rpc_service *svc = cb; + + if (!prefixcmp(var, "http.") && + !strcmp(var + 5, svc->config_name)) { + svc->enabled = git_config_bool(var, value); + return 0; + } + + /* we are not interested in parsing any other configuration here */ + return 0; +} + +static struct rpc_service *select_service(const char *name) +{ + struct rpc_service *svc = NULL; + int i; + + if (prefixcmp(name, "git-")) + forbidden("Unsupported service: '%s'", name); + + for (i = 0; i < ARRAY_SIZE(rpc_service); i++) { + struct rpc_service *s = &rpc_service[i]; + if (!strcmp(s->name, name + 4)) { + svc = s; + break; + } + } + + if (!svc) + forbidden("Unsupported service: '%s'", name); + + git_config(http_config, svc); + if (svc->enabled < 0) { + const char *user = getenv("REMOTE_USER"); + svc->enabled = (user && *user) ? 1 : 0; + } + if (!svc->enabled) + forbidden("Service not enabled: '%s'", svc->name); + return svc; +} + +static void inflate_request(const char *prog_name, int out) +{ + z_stream stream; + unsigned char in_buf[8192]; + unsigned char out_buf[8192]; + unsigned long cnt = 0; + int ret; + + memset(&stream, 0, sizeof(stream)); + ret = inflateInit2(&stream, (15 + 16)); + if (ret != Z_OK) + die("cannot start zlib inflater, zlib err %d", ret); + + while (1) { + ssize_t n = xread(0, in_buf, sizeof(in_buf)); + if (n <= 0) + die("request ended in the middle of the gzip stream"); + + stream.next_in = in_buf; + stream.avail_in = n; + + while (0 < stream.avail_in) { + int ret; + + stream.next_out = out_buf; + stream.avail_out = sizeof(out_buf); + + ret = inflate(&stream, Z_NO_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END) + die("zlib error inflating request, result %d", ret); + + n = stream.total_out - cnt; + if (write_in_full(out, out_buf, n) != n) + die("%s aborted reading request", prog_name); + cnt += n; + + if (ret == Z_STREAM_END) + goto done; + } + } + +done: + inflateEnd(&stream); + close(out); +} + +static void run_service(const char **argv) +{ + const char *encoding = getenv("HTTP_CONTENT_ENCODING"); + const char *user = getenv("REMOTE_USER"); + const char *host = getenv("REMOTE_ADDR"); + char *env[3]; + struct strbuf buf = STRBUF_INIT; + int gzipped_request = 0; + struct child_process cld; + + if (encoding && !strcmp(encoding, "gzip")) + gzipped_request = 1; + else if (encoding && !strcmp(encoding, "x-gzip")) + gzipped_request = 1; + + if (!user || !*user) + user = "anonymous"; + if (!host || !*host) + host = "(none)"; + + memset(&env, 0, sizeof(env)); + strbuf_addf(&buf, "GIT_COMMITTER_NAME=%s", user); + env[0] = strbuf_detach(&buf, NULL); + + strbuf_addf(&buf, "GIT_COMMITTER_EMAIL=%s@http.%s", user, host); + env[1] = strbuf_detach(&buf, NULL); + env[2] = NULL; + + memset(&cld, 0, sizeof(cld)); + cld.argv = argv; + cld.env = (const char *const *)env; + if (gzipped_request) + cld.in = -1; + cld.git_cmd = 1; + if (start_command(&cld)) + exit(1); + + close(1); + if (gzipped_request) + inflate_request(argv[0], cld.in); + else + close(0); + + if (finish_command(&cld)) + exit(1); + free(env[0]); + free(env[1]); + strbuf_release(&buf); +} + static int show_text_ref(const char *name, const unsigned char *sha1, int flag, void *cb_data) { @@ -167,11 +419,32 @@ static int show_text_ref(const char *name, const unsigned char *sha1, static void get_info_refs(char *arg) { + const char *service_name = get_parameter("service"); struct strbuf buf = STRBUF_INIT; - for_each_ref(show_text_ref, &buf); hdr_nocache(); - send_strbuf("text/plain", &buf); + + if (service_name) { + const char *argv[] = {NULL /* service name */, + "--stateless-rpc", "--advertise-refs", + ".", NULL}; + struct rpc_service *svc = select_service(service_name); + + strbuf_addf(&buf, "application/x-git-%s-advertisement", + svc->name); + hdr_str(content_type, buf.buf); + end_headers(); + + packet_write(1, "# service=git-%s\n", svc->name); + packet_flush(1); + + argv[0] = svc->name; + run_service(argv); + + } else { + for_each_ref(show_text_ref, &buf); + send_strbuf("text/plain", &buf); + } strbuf_release(&buf); } @@ -200,6 +473,48 @@ static void get_info_packs(char *arg) strbuf_release(&buf); } +static void check_content_type(const char *accepted_type) +{ + const char *actual_type = getenv("CONTENT_TYPE"); + + if (!actual_type) + actual_type = ""; + + if (strcmp(actual_type, accepted_type)) { + http_status(415, "Unsupported Media Type"); + hdr_nocache(); + end_headers(); + format_write(1, + "Expected POST with Content-Type '%s'," + " but received '%s' instead.\n", + accepted_type, actual_type); + exit(0); + } +} + +static void service_rpc(char *service_name) +{ + const char *argv[] = {NULL, "--stateless-rpc", ".", NULL}; + struct rpc_service *svc = select_service(service_name); + struct strbuf buf = STRBUF_INIT; + + strbuf_reset(&buf); + strbuf_addf(&buf, "application/x-git-%s-request", svc->name); + check_content_type(buf.buf); + + hdr_nocache(); + + strbuf_reset(&buf); + strbuf_addf(&buf, "application/x-git-%s-result", svc->name); + hdr_str(content_type, buf.buf); + + end_headers(); + + argv[0] = svc->name; + run_service(argv); + strbuf_release(&buf); +} + static NORETURN void die_webcgi(const char *err, va_list params) { char buffer[1000]; @@ -225,7 +540,10 @@ static struct service_cmd { {"GET", "/objects/info/packs$", get_info_packs}, {"GET", "/objects/[0-9a-f]{2}/[0-9a-f]{38}$", get_loose_object}, {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.pack$", get_pack_file}, - {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file} + {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file}, + + {"POST", "/git-upload-pack$", service_rpc}, + {"POST", "/git-receive-pack$", service_rpc} }; int main(int argc, char **argv) From 917adc036086f52b0277ff03d10b7044c9d9d0d2 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Fri, 30 Oct 2009 17:47:35 -0700 Subject: [PATCH 230/884] http-backend: add GIT_PROJECT_ROOT environment var Add a new environment variable, GIT_PROJECT_ROOT, to override the method of using PATH_TRANSLATED to find the git repository on disk. This makes it much easier to configure the web server, especially when the web server's DocumentRoot does not contain the git repositories, which is the usual case. Signed-off-by: Mark Lodato Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-http-backend.txt | 39 +++++++++++++----------------- http-backend.c | 25 ++++++++++++++++--- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index 022a2433a8..99dbbfb966 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -41,29 +41,24 @@ http.receivepack:: URL TRANSLATION --------------- -'git-http-backend' relies on the invoking web server to perform -URL to path translation, and store the repository path into the -PATH_TRANSLATED environment variable. Most web servers will do -this translation automatically, resolving the suffix after the -CGI name relative to the server's document root. +To determine the location of the repository on disk, 'git-http-backend' +concatenates the environment variables PATH_INFO, which is set +automatically by the web server, and GIT_PROJECT_ROOT, which must be set +manually in the web server configuration. If GIT_PROJECT_ROOT is not +set, 'git-http-backend' reads PATH_TRANSLATED, which is also set +automatically by the web server. EXAMPLES -------- Apache 2.x:: - To serve all Git repositories contained within the '/git/' - subdirectory of the DocumentRoot, ensure mod_cgi and - mod_alias are enabled, and create a ScriptAlias to the CGI: + Ensure mod_cgi, mod_alias, and mod_env are enabled, set + GIT_PROJECT_ROOT (or DocumentRoot) appropriately, and + create a ScriptAlias to the CGI: + ---------------------------------------------------------------- -ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/ - - - Options None - - - Options ExecCGI - +SetEnv GIT_PROJECT_ROOT /var/www/git +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ ---------------------------------------------------------------- + To enable anonymous read access but authenticated write access, @@ -78,16 +73,16 @@ require authorization with a LocationMatch directive: ---------------------------------------------------------------- + -To require authentication for both reads and writes, use a Directory +To require authentication for both reads and writes, use a Location directive around the repository, or one of its parent directories: + ---------------------------------------------------------------- - + AuthType Basic AuthName "Private Git Access" Require group committers ... - + ---------------------------------------------------------------- Accelerated static Apache 2.x:: @@ -97,9 +92,9 @@ Accelerated static Apache 2.x:: file contents from the file system directly to the network: + ---------------------------------------------------------------- -DocumentRoot /var/www +SetEnv GIT_PROJECT_ROOT /var/www/git -ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/ +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ Alias /git_static/ /var/www/git/ RewriteEngine on @@ -114,7 +109,7 @@ ENVIRONMENT 'git-http-backend' relies upon the CGI environment variables set by the invoking web server, including: -* PATH_TRANSLATED +* PATH_INFO (if GIT_PROJECT_ROOT is set, otherwise PATH_TRANSLATED) * REMOTE_USER * REMOTE_ADDR * CONTENT_TYPE diff --git a/http-backend.c b/http-backend.c index bfce52063f..7900cda69a 100644 --- a/http-backend.c +++ b/http-backend.c @@ -528,6 +528,26 @@ static NORETURN void die_webcgi(const char *err, va_list params) exit(0); } +static char* getdir(void) +{ + struct strbuf buf = STRBUF_INIT; + char *pathinfo = getenv("PATH_INFO"); + char *root = getenv("GIT_PROJECT_ROOT"); + char *path = getenv("PATH_TRANSLATED"); + + if (root && *root) { + if (!pathinfo || !*pathinfo) + die("GIT_PROJECT_ROOT is set but PATH_INFO is not"); + strbuf_addstr(&buf, root); + strbuf_addstr(&buf, pathinfo); + return strbuf_detach(&buf, NULL); + } else if (path && *path) { + return xstrdup(path); + } else + die("No GIT_PROJECT_ROOT or PATH_TRANSLATED from server"); + return NULL; +} + static struct service_cmd { const char *method; const char *pattern; @@ -549,7 +569,7 @@ static struct service_cmd { int main(int argc, char **argv) { char *method = getenv("REQUEST_METHOD"); - char *dir = getenv("PATH_TRANSLATED"); + char *dir; struct service_cmd *cmd = NULL; char *cmd_arg = NULL; int i; @@ -561,8 +581,7 @@ int main(int argc, char **argv) die("No REQUEST_METHOD from server"); if (!strcmp(method, "HEAD")) method = "GET"; - if (!dir) - die("No PATH_TRANSLATED from server"); + dir = getdir(); for (i = 0; i < ARRAY_SIZE(services); i++) { struct service_cmd *c = &services[i]; From b9af4ab3cd33f699ea2a978acb83dbddf07900e6 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Fri, 30 Oct 2009 17:47:36 -0700 Subject: [PATCH 231/884] http-backend: reword some documentation Clarify some of the git-http-backend documentation, particularly: * In the Description, state that smart/dumb HTTP fetch and smart HTTP push are supported, state that authenticated clients allow push, and remove the note that this is only suited for read-only updates. * At the start of Examples, state explicitly what URL is mapping to what location on disk. Signed-off-by: Mark Lodato Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-http-backend.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index 99dbbfb966..0b5e9515a1 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -14,13 +14,15 @@ DESCRIPTION ----------- A simple CGI program to serve the contents of a Git repository to Git clients accessing the repository over http:// and https:// protocols. +The program supports clients fetching using both the smart HTTP protcol +and the backwards-compatible dumb HTTP protocol, as well as clients +pushing using the smart HTTP protocol. By default, only the `upload-pack` service is enabled, which serves 'git-fetch-pack' and 'git-ls-remote' clients, which are invoked from -'git-fetch', 'git-pull', and 'git-clone'. - -This is ideally suited for read-only updates, i.e., pulling from -git repositories. +'git-fetch', 'git-pull', and 'git-clone'. If the client is authenticated, +the `receive-pack` service is enabled, which serves 'git-send-pack' +clients, which is invoked from 'git-push'. SERVICES -------- @@ -50,6 +52,8 @@ automatically by the web server. EXAMPLES -------- +All of the following examples map 'http://$hostname/git/foo/bar.git' +to '/var/www/git/foo/bar.git'. Apache 2.x:: Ensure mod_cgi, mod_alias, and mod_env are enabled, set From 0ebb1fa78eb9d6b27ae79c9e48e9306bcfa2a4ac Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Fri, 30 Oct 2009 17:47:37 -0700 Subject: [PATCH 232/884] http-backend: use mod_alias instead of mod_rewrite In the git-http-backend documentation, use mod_alias exlusively, instead of using a combination of mod_alias and mod_rewrite. This makes the example slightly shorted and a bit more clear. Signed-off-by: Mark Lodato Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-http-backend.txt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index 0b5e9515a1..e67519db91 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -98,13 +98,9 @@ Accelerated static Apache 2.x:: ---------------------------------------------------------------- SetEnv GIT_PROJECT_ROOT /var/www/git -ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ -Alias /git_static/ /var/www/git/ - -RewriteEngine on -RewriteRule ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /git_static/$1 [PT] -RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.pack)$ /git_static/$1 [PT] -RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.idx)$ /git_static/$1 [PT] +AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/git/$1 +AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1 +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ ---------------------------------------------------------------- From 8127f778a0f6495a0b8484a21b5591e56d873de8 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Fri, 30 Oct 2009 17:47:38 -0700 Subject: [PATCH 233/884] http-backend: add example for gitweb on same URL In the git-http-backend documentation, add an example of how to set up gitweb and git-http-backend on the same URL by using a series of mod_alias commands. Signed-off-by: Mark Lodato Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-http-backend.txt | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index e67519db91..2989c9f226 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -88,6 +88,23 @@ directive around the repository, or one of its parent directories: ... ---------------------------------------------------------------- ++ +To serve gitweb at the same url, use a ScriptAliasMatch to only +those URLs that 'git-http-backend' can handle, and forward the +rest to gitweb: ++ +---------------------------------------------------------------- +ScriptAliasMatch \ + "(?x)^/git/(.*/(HEAD | \ + info/refs | \ + objects/(info/[^/]+ | \ + [0-9a-f]{2}/[0-9a-f]{38} | \ + pack/pack-[0-9a-f]{40}\.(pack|idx)) | \ + git-(upload|receive)-pack))$" \ + /usr/libexec/git-core/git-http-backend/$1 + +ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/ +---------------------------------------------------------------- Accelerated static Apache 2.x:: Similar to the above, but Apache can be used to return static @@ -102,6 +119,22 @@ AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/git/$1 AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1 ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ ---------------------------------------------------------------- ++ +This can be combined with the gitweb configuration: ++ +---------------------------------------------------------------- +SetEnv GIT_PROJECT_ROOT /var/www/git + +AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/git/$1 +AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1 +ScriptAliasMatch \ + "(?x)^/git/(.*/(HEAD | \ + info/refs | \ + objects/info/[^/]+ | \ + git-(upload|receive)-pack))$" \ + /usr/libexec/git-core/git-http-backend/$1 +ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/ +---------------------------------------------------------------- ENVIRONMENT From f5ba2d18f96037749f370c1386935e60f034c87e Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Fri, 30 Oct 2009 17:47:39 -0700 Subject: [PATCH 234/884] http-backend: more explict LocationMatch In the git-http-backend examples, only match git-receive-pack within /git/. Signed-off-by: Mark Lodato Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-http-backend.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index 2989c9f226..f17251ab9d 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -69,7 +69,7 @@ To enable anonymous read access but authenticated write access, require authorization with a LocationMatch directive: + ---------------------------------------------------------------- - + AuthType Basic AuthName "Git Access" Require group committers From 97cc7bc45c2e076184b84749952b216036a6ecd6 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:40 -0700 Subject: [PATCH 235/884] Discover refs via smart HTTP server when available Instead of loading the cached info/refs, try to use the smart HTTP version when the server supports it. Since the smart variant is actually the pkt-line stream from the start of either upload-pack or receive-pack we need to parse these through get_remote_heads, which requires a background thread to feed its pipe. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano --- remote-curl.c | 148 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 131 insertions(+), 17 deletions(-) diff --git a/remote-curl.c b/remote-curl.c index 5c9dd97d1d..3917d45deb 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -5,6 +5,7 @@ #include "http.h" #include "exec_cmd.h" #include "run-command.h" +#include "pkt-line.h" static struct remote *remote; static const char *url; @@ -75,21 +76,46 @@ static int set_option(const char *name, const char *value) } } -static struct ref *get_refs(void) +struct discovery { + const char *service; + char *buf_alloc; + char *buf; + size_t len; + unsigned proto_git : 1; +}; +static struct discovery *last_discovery; + +static void free_discovery(struct discovery *d) +{ + if (d) { + if (d == last_discovery) + last_discovery = NULL; + free(d->buf_alloc); + free(d); + } +} + +static struct discovery* discover_refs(const char *service) { struct strbuf buffer = STRBUF_INIT; - char *data, *start, *mid; - char *ref_name; + struct discovery *last = last_discovery; char *refs_url; - int i = 0; - int http_ret; + int http_ret, is_http = 0; - struct ref *refs = NULL; - struct ref *ref = NULL; - struct ref *last_ref = NULL; + if (last && !strcmp(service, last->service)) + return last; + free_discovery(last); - refs_url = xmalloc(strlen(url) + 11); - sprintf(refs_url, "%s/info/refs", url); + strbuf_addf(&buffer, "%s/info/refs", url); + if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) { + is_http = 1; + if (!strchr(url, '?')) + strbuf_addch(&buffer, '?'); + else + strbuf_addch(&buffer, '&'); + strbuf_addf(&buffer, "service=%s", service); + } + refs_url = strbuf_detach(&buffer, NULL); init_walker(); http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); @@ -104,10 +130,86 @@ static struct ref *get_refs(void) die("HTTP request failed"); } - data = buffer.buf; + last= xcalloc(1, sizeof(*last_discovery)); + last->service = service; + last->buf_alloc = strbuf_detach(&buffer, &last->len); + last->buf = last->buf_alloc; + + if (is_http && 5 <= last->len && last->buf[4] == '#') { + /* smart HTTP response; validate that the service + * pkt-line matches our request. + */ + struct strbuf exp = STRBUF_INIT; + + if (packet_get_line(&buffer, &last->buf, &last->len) <= 0) + die("%s has invalid packet header", refs_url); + if (buffer.len && buffer.buf[buffer.len - 1] == '\n') + strbuf_setlen(&buffer, buffer.len - 1); + + strbuf_addf(&exp, "# service=%s", service); + if (strbuf_cmp(&exp, &buffer)) + die("invalid server response; got '%s'", buffer.buf); + strbuf_release(&exp); + + /* The header can include additional metadata lines, up + * until a packet flush marker. Ignore these now, but + * in the future we might start to scan them. + */ + strbuf_reset(&buffer); + while (packet_get_line(&buffer, &last->buf, &last->len) > 0) + strbuf_reset(&buffer); + + last->proto_git = 1; + } + + free(refs_url); + strbuf_release(&buffer); + last_discovery = last; + return last; +} + +static int write_discovery(int fd, void *data) +{ + struct discovery *heads = data; + int err = 0; + if (write_in_full(fd, heads->buf, heads->len) != heads->len) + err = 1; + close(fd); + return err; +} + +static struct ref *parse_git_refs(struct discovery *heads) +{ + struct ref *list = NULL; + struct async async; + + memset(&async, 0, sizeof(async)); + async.proc = write_discovery; + async.data = heads; + + if (start_async(&async)) + die("cannot start thread to parse advertised refs"); + get_remote_heads(async.out, &list, 0, NULL, 0, NULL); + close(async.out); + if (finish_async(&async)) + die("ref parsing thread failed"); + return list; +} + +static struct ref *parse_info_refs(struct discovery *heads) +{ + char *data, *start, *mid; + char *ref_name; + int i = 0; + + struct ref *refs = NULL; + struct ref *ref = NULL; + struct ref *last_ref = NULL; + + data = heads->buf; start = NULL; mid = data; - while (i < buffer.len) { + while (i < heads->len) { if (!start) { start = &data[i]; } @@ -131,8 +233,7 @@ static struct ref *get_refs(void) i++; } - strbuf_release(&buffer); - + init_walker(); ref = alloc_ref("HEAD"); if (!walker->fetch_ref(walker, ref) && !resolve_remote_symref(ref, refs)) { @@ -142,11 +243,23 @@ static struct ref *get_refs(void) free(ref); } - strbuf_release(&buffer); - free(refs_url); return refs; } +static struct ref *get_refs(int for_push) +{ + struct discovery *heads; + + if (for_push) + heads = discover_refs("git-receive-pack"); + else + heads = discover_refs("git-upload-pack"); + + if (heads->proto_git) + return parse_git_refs(heads); + return parse_info_refs(heads); +} + static void output_refs(struct ref *refs) { struct ref *posn; @@ -317,7 +430,8 @@ int main(int argc, const char **argv) parse_fetch(&buf); } else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) { - output_refs(get_refs()); + int for_push = !!strstr(buf.buf + 4, "for-push"); + output_refs(get_refs(for_push)); } else if (!prefixcmp(buf.buf, "push ")) { parse_push(&buf); From de1a2fdd38b138c4e4fed6412783dcb74d63d2da Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:41 -0700 Subject: [PATCH 236/884] Smart push over HTTP: client side The git-remote-curl backend detects if the remote server supports the git-receive-pack service, and if so, runs git-send-pack in a pipe to dump the command and pack data as a single POST request. The advertisements from the server that were obtained during the discovery are passed into git-send-pack before the POST request starts. This permits git-send-pack to operate largely unmodified. For smaller packs (those under 1 MiB) a HTTP/1.0 POST with a Content-Length is used, permitting interaction with any server. The 1 MiB limit is arbitrary, but is sufficent to fit most deltas created by human authors against text sources with the occasional small binary file (e.g. few KiB icon image). The configuration option http.postBuffer can be used to increase (or shink) this buffer if the default is not sufficient. For larger packs which cannot be spooled entirely into the helper's memory space (due to http.postBuffer being too small), the POST request requires HTTP/1.1 and sets "Transfer-Encoding: chunked". This permits the client to upload an unknown amount of data in one HTTP transaction without needing to pregenerate the entire pack file locally. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano --- Documentation/config.txt | 8 ++ builtin-send-pack.c | 116 ++++++++++++++++++- http.c | 13 ++- http.h | 2 + remote-curl.c | 235 ++++++++++++++++++++++++++++++++++++++- send-pack.h | 3 +- sideband.c | 11 +- transport.c | 1 + 8 files changed, 374 insertions(+), 15 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index cd1781498e..7130d0723a 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1089,6 +1089,14 @@ http.maxRequests:: How many HTTP requests to launch in parallel. Can be overridden by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5. +http.postBuffer:: + Maximum size in bytes of the buffer used by smart HTTP + transports when POSTing data to the remote system. + For requests larger than this buffer size, HTTP/1.1 and + Transfer-Encoding: chunked is used to avoid creating a + massive pack file locally. Default is 1 MiB, which is + sufficient for most requests. + http.lowSpeedLimit, http.lowSpeedTime:: If the HTTP transfer speed is less than 'http.lowSpeedLimit' for longer than 'http.lowSpeedTime' seconds, the transfer is aborted. diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 37e528e283..a0fbad0f61 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -2,9 +2,11 @@ #include "commit.h" #include "refs.h" #include "pkt-line.h" +#include "sideband.h" #include "run-command.h" #include "remote.h" #include "send-pack.h" +#include "quote.h" static const char send_pack_usage[] = "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=] [--verbose] [--thin] [:] [...]\n" @@ -59,7 +61,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext memset(&po, 0, sizeof(po)); po.argv = argv; po.in = -1; - po.out = fd; + po.out = args->stateless_rpc ? -1 : fd; po.git_cmd = 1; if (start_command(&po)) die_errno("git pack-objects failed"); @@ -83,6 +85,20 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext } close(po.in); + + if (args->stateless_rpc) { + char *buf = xmalloc(LARGE_PACKET_MAX); + while (1) { + ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX); + if (n <= 0) + break; + send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX); + } + free(buf); + close(po.out); + po.out = -1; + } + if (finish_command(&po)) return error("pack-objects died with strange error"); return 0; @@ -303,6 +319,59 @@ static int refs_pushed(struct ref *ref) return 0; } +static void print_helper_status(struct ref *ref) +{ + struct strbuf buf = STRBUF_INIT; + + for (; ref; ref = ref->next) { + const char *msg = NULL; + const char *res; + + switch(ref->status) { + case REF_STATUS_NONE: + res = "error"; + msg = "no match"; + break; + + case REF_STATUS_OK: + res = "ok"; + break; + + case REF_STATUS_UPTODATE: + res = "ok"; + msg = "up to date"; + break; + + case REF_STATUS_REJECT_NONFASTFORWARD: + res = "error"; + msg = "non-fast forward"; + break; + + case REF_STATUS_REJECT_NODELETE: + case REF_STATUS_REMOTE_REJECT: + res = "error"; + break; + + case REF_STATUS_EXPECTING_REPORT: + default: + continue; + } + + strbuf_reset(&buf); + strbuf_addf(&buf, "%s %s", res, ref->name); + if (ref->remote_status) + msg = ref->remote_status; + if (msg) { + strbuf_addch(&buf, ' '); + quote_two_c_style(&buf, "", msg, 0); + } + strbuf_addch(&buf, '\n'); + + safe_write(1, buf.buf, buf.len); + } + strbuf_release(&buf); +} + int send_pack(struct send_pack_args *args, int fd[], struct child_process *conn, struct ref *remote_refs, @@ -310,6 +379,7 @@ int send_pack(struct send_pack_args *args, { int in = fd[0]; int out = fd[1]; + struct strbuf req_buf = STRBUF_INIT; struct ref *ref; int new_refs; int ask_for_status_report = 0; @@ -391,14 +461,14 @@ int send_pack(struct send_pack_args *args, char *new_hex = sha1_to_hex(ref->new_sha1); if (ask_for_status_report) { - packet_write(out, "%s %s %s%c%s", + packet_buf_write(&req_buf, "%s %s %s%c%s", old_hex, new_hex, ref->name, 0, "report-status"); ask_for_status_report = 0; expect_status_report = 1; } else - packet_write(out, "%s %s %s", + packet_buf_write(&req_buf, "%s %s %s", old_hex, new_hex, ref->name); } ref->status = expect_status_report ? @@ -406,7 +476,17 @@ int send_pack(struct send_pack_args *args, REF_STATUS_OK; } - packet_flush(out); + if (args->stateless_rpc) { + if (!args->dry_run) { + packet_buf_flush(&req_buf); + send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX); + } + } else { + safe_write(out, req_buf.buf, req_buf.len); + packet_flush(out); + } + strbuf_release(&req_buf); + if (new_refs && !args->dry_run) { if (pack_objects(out, remote_refs, extra_have, args) < 0) { for (ref = remote_refs; ref; ref = ref->next) @@ -414,11 +494,15 @@ int send_pack(struct send_pack_args *args, return -1; } } + if (args->stateless_rpc && !args->dry_run) + packet_flush(out); if (expect_status_report) ret = receive_status(in, remote_refs); else ret = 0; + if (args->stateless_rpc) + packet_flush(out); if (ret < 0) return ret; @@ -478,6 +562,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) struct extra_have_objects extra_have; struct ref *remote_refs, *local_refs; int ret; + int helper_status = 0; int send_all = 0; const char *receivepack = "git-receive-pack"; int flags; @@ -523,6 +608,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.use_thin_pack = 1; continue; } + if (!strcmp(arg, "--stateless-rpc")) { + args.stateless_rpc = 1; + continue; + } + if (!strcmp(arg, "--helper-status")) { + helper_status = 1; + continue; + } usage(send_pack_usage); } if (!dest) { @@ -551,7 +644,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) } } - conn = git_connect(fd, dest, receivepack, args.verbose ? CONNECT_VERBOSE : 0); + if (args.stateless_rpc) { + conn = NULL; + fd[0] = 0; + fd[1] = 1; + } else { + conn = git_connect(fd, dest, receivepack, + args.verbose ? CONNECT_VERBOSE : 0); + } memset(&extra_have, 0, sizeof(extra_have)); @@ -575,12 +675,16 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) ret = send_pack(&args, fd, conn, remote_refs, &extra_have); + if (helper_status) + print_helper_status(remote_refs); + close(fd[1]); close(fd[0]); ret |= finish_connect(conn); - print_push_status(dest, remote_refs); + if (!helper_status) + print_push_status(dest, remote_refs); if (!args.dry_run && remote) { struct ref *ref; diff --git a/http.c b/http.c index 23b2a1932c..ed6414a2aa 100644 --- a/http.c +++ b/http.c @@ -1,9 +1,11 @@ #include "http.h" #include "pack.h" +#include "sideband.h" int data_received; int active_requests; int http_is_verbose; +size_t http_post_buffer = 16 * LARGE_PACKET_MAX; #ifdef USE_CURL_MULTI static int max_requests = -1; @@ -97,8 +99,6 @@ size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf) return eltsize * nmemb; } -static void finish_active_slot(struct active_request_slot *slot); - #ifdef USE_CURL_MULTI static void process_curl_messages(void) { @@ -174,6 +174,13 @@ static int http_options(const char *var, const char *value, void *cb) if (!strcmp("http.proxy", var)) return git_config_string(&curl_http_proxy, var, value); + if (!strcmp("http.postbuffer", var)) { + http_post_buffer = git_config_int(var, value); + if (http_post_buffer < LARGE_PACKET_MAX) + http_post_buffer = LARGE_PACKET_MAX; + return 0; + } + /* Fall back on the default ones */ return git_default_config(var, value, cb); } @@ -638,7 +645,7 @@ void release_active_slot(struct active_request_slot *slot) #endif } -static void finish_active_slot(struct active_request_slot *slot) +void finish_active_slot(struct active_request_slot *slot) { closedown_active_slot(slot); curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code); diff --git a/http.h b/http.h index 4c4e99c2f6..f828e1d806 100644 --- a/http.h +++ b/http.h @@ -79,6 +79,7 @@ extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp); extern struct active_request_slot *get_active_slot(void); extern int start_active_slot(struct active_request_slot *slot); extern void run_active_slot(struct active_request_slot *slot); +extern void finish_active_slot(struct active_request_slot *slot); extern void finish_all_active_slots(void); extern void release_active_slot(struct active_request_slot *slot); @@ -94,6 +95,7 @@ extern void http_cleanup(void); extern int data_received; extern int active_requests; extern int http_is_verbose; +extern size_t http_post_buffer; extern char curl_errorstr[CURL_ERROR_SIZE]; diff --git a/remote-curl.c b/remote-curl.c index 3917d45deb..f1206cb572 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -6,6 +6,7 @@ #include "exec_cmd.h" #include "run-command.h" #include "pkt-line.h" +#include "sideband.h" static struct remote *remote; static const char *url; @@ -16,7 +17,8 @@ struct options { unsigned long depth; unsigned progress : 1, followtags : 1, - dry_run : 1; + dry_run : 1, + thin : 1; }; static struct options options; @@ -274,6 +276,188 @@ static void output_refs(struct ref *refs) free_refs(refs); } +struct rpc_state { + const char *service_name; + const char **argv; + char *service_url; + char *hdr_content_type; + char *hdr_accept; + char *buf; + size_t alloc; + size_t len; + size_t pos; + int in; + int out; + struct strbuf result; +}; + +static size_t rpc_out(void *ptr, size_t eltsize, + size_t nmemb, void *buffer_) +{ + size_t max = eltsize * nmemb; + struct rpc_state *rpc = buffer_; + size_t avail = rpc->len - rpc->pos; + + if (!avail) { + avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc); + if (!avail) + return 0; + rpc->pos = 0; + rpc->len = avail; + } + + if (max < avail); + avail = max; + memcpy(ptr, rpc->buf + rpc->pos, avail); + rpc->pos += avail; + return avail; +} + +static size_t rpc_in(const void *ptr, size_t eltsize, + size_t nmemb, void *buffer_) +{ + size_t size = eltsize * nmemb; + struct rpc_state *rpc = buffer_; + write_or_die(rpc->in, ptr, size); + return size; +} + +static int post_rpc(struct rpc_state *rpc) +{ + struct active_request_slot *slot; + struct slot_results results; + struct curl_slist *headers = NULL; + int err = 0, large_request = 0; + + /* Try to load the entire request, if we can fit it into the + * allocated buffer space we can use HTTP/1.0 and avoid the + * chunked encoding mess. + */ + while (1) { + size_t left = rpc->alloc - rpc->len; + char *buf = rpc->buf + rpc->len; + int n; + + if (left < LARGE_PACKET_MAX) { + large_request = 1; + break; + } + + n = packet_read_line(rpc->out, buf, left); + if (!n) + break; + rpc->len += n; + } + + slot = get_active_slot(); + slot->results = &results; + + curl_easy_setopt(slot->curl, CURLOPT_POST, 1); + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); + curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url); + + headers = curl_slist_append(headers, rpc->hdr_content_type); + headers = curl_slist_append(headers, rpc->hdr_accept); + + if (large_request) { + /* The request body is large and the size cannot be predicted. + * We must use chunked encoding to send it. + */ + headers = curl_slist_append(headers, "Expect: 100-continue"); + headers = curl_slist_append(headers, "Transfer-Encoding: chunked"); + curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out); + curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc); + if (options.verbosity > 1) { + fprintf(stderr, "POST %s (chunked)\n", rpc->service_name); + fflush(stderr); + } + + } else { + /* We know the complete request size in advance, use the + * more normal Content-Length approach. + */ + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, rpc->buf); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, rpc->len); + if (options.verbosity > 1) { + fprintf(stderr, "POST %s (%lu bytes)\n", + rpc->service_name, (unsigned long)rpc->len); + fflush(stderr); + } + } + + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in); + curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc); + + slot->curl_result = curl_easy_perform(slot->curl); + finish_active_slot(slot); + + if (results.curl_result != CURLE_OK) { + err |= error("RPC failed; result=%d, HTTP code = %ld", + results.curl_result, results.http_code); + } + + curl_slist_free_all(headers); + return err; +} + +static int rpc_service(struct rpc_state *rpc, struct discovery *heads) +{ + const char *svc = rpc->service_name; + struct strbuf buf = STRBUF_INIT; + struct child_process client; + int err = 0; + + init_walker(); + memset(&client, 0, sizeof(client)); + client.in = -1; + client.out = -1; + client.git_cmd = 1; + client.argv = rpc->argv; + if (start_command(&client)) + exit(1); + if (heads) + write_or_die(client.in, heads->buf, heads->len); + + rpc->alloc = http_post_buffer; + rpc->buf = xmalloc(rpc->alloc); + rpc->in = client.in; + rpc->out = client.out; + strbuf_init(&rpc->result, 0); + + strbuf_addf(&buf, "%s/%s", url, svc); + rpc->service_url = strbuf_detach(&buf, NULL); + + strbuf_addf(&buf, "Content-Type: application/x-%s-request", svc); + rpc->hdr_content_type = strbuf_detach(&buf, NULL); + + strbuf_addf(&buf, "Accept: application/x-%s-response", svc); + rpc->hdr_accept = strbuf_detach(&buf, NULL); + + while (!err) { + int n = packet_read_line(rpc->out, rpc->buf, rpc->alloc); + if (!n) + break; + rpc->pos = 0; + rpc->len = n; + err |= post_rpc(rpc); + } + strbuf_read(&rpc->result, client.out, 0); + + close(client.in); + close(client.out); + client.in = -1; + client.out = -1; + + err |= finish_command(&client); + free(rpc->service_url); + free(rpc->hdr_content_type); + free(rpc->hdr_accept); + free(rpc->buf); + strbuf_release(&buf); + return err; +} + static int fetch_dumb(int nr_heads, struct ref **to_fetch) { char **targets = xmalloc(nr_heads * sizeof(char*)); @@ -371,6 +555,52 @@ static int push_dav(int nr_spec, char **specs) return 0; } +static int push_git(struct discovery *heads, int nr_spec, char **specs) +{ + struct rpc_state rpc; + const char **argv; + int argc = 0, i, err; + + argv = xmalloc((10 + nr_spec) * sizeof(char*)); + argv[argc++] = "send-pack"; + argv[argc++] = "--stateless-rpc"; + argv[argc++] = "--helper-status"; + if (options.thin) + argv[argc++] = "--thin"; + if (options.dry_run) + argv[argc++] = "--dry-run"; + if (options.verbosity > 1) + argv[argc++] = "--verbose"; + argv[argc++] = url; + for (i = 0; i < nr_spec; i++) + argv[argc++] = specs[i]; + argv[argc++] = NULL; + + memset(&rpc, 0, sizeof(rpc)); + rpc.service_name = "git-receive-pack", + rpc.argv = argv; + + err = rpc_service(&rpc, heads); + if (rpc.result.len) + safe_write(1, rpc.result.buf, rpc.result.len); + strbuf_release(&rpc.result); + free(argv); + return err; +} + +static int push(int nr_spec, char **specs) +{ + struct discovery *heads = discover_refs("git-receive-pack"); + int ret; + + if (heads->proto_git) + ret = push_git(heads, nr_spec, specs); + else + ret = push_dav(nr_spec, specs); + free_discovery(heads); + return ret; +} + static void parse_push(struct strbuf *buf) { char **specs = NULL; @@ -391,7 +621,7 @@ static void parse_push(struct strbuf *buf) break; } while (1); - if (push_dav(nr_spec, specs)) + if (push(nr_spec, specs)) exit(128); /* error already reported */ for (i = 0; i < nr_spec; i++) free(specs[i]); @@ -414,6 +644,7 @@ int main(int argc, const char **argv) options.verbosity = 1; options.progress = !!isatty(2); + options.thin = 1; remote = remote_get(argv[1]); diff --git a/send-pack.h b/send-pack.h index 8b3cf028ed..28141ac913 100644 --- a/send-pack.h +++ b/send-pack.h @@ -8,7 +8,8 @@ struct send_pack_args { force_update:1, use_thin_pack:1, use_ofs_delta:1, - dry_run:1; + dry_run:1, + stateless_rpc:1; }; int send_pack(struct send_pack_args *args, diff --git a/sideband.c b/sideband.c index 899b1ff366..d5ffa1c891 100644 --- a/sideband.c +++ b/sideband.c @@ -135,9 +135,14 @@ ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet n = sz; if (packet_max - 5 < n) n = packet_max - 5; - sprintf(hdr, "%04x", n + 5); - hdr[4] = band; - safe_write(fd, hdr, 5); + if (0 <= band) { + sprintf(hdr, "%04x", n + 5); + hdr[4] = band; + safe_write(fd, hdr, 5); + } else { + sprintf(hdr, "%04x", n + 4); + safe_write(fd, hdr, 4); + } safe_write(fd, p, n); p += n; sz -= n; diff --git a/transport.c b/transport.c index 6d9652db94..2ff16503be 100644 --- a/transport.c +++ b/transport.c @@ -731,6 +731,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re NULL); } + memset(&args, 0, sizeof(args)); args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR); args.force_update = !!(flags & TRANSPORT_PUSH_FORCE); args.use_thin_pack = data->thin; From 249b2004d8c9c58ed1ea1665dfd376af0312ed7e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:42 -0700 Subject: [PATCH 237/884] Smart fetch over HTTP: client side The git-remote-curl backend detects if the remote server supports the git-upload-pack service, and if so, runs git-fetch-pack locally in a pipe to generate the want/have commands. The advertisements from the server that were obtained during the discovery are passed into git-fetch-pack before the POST request starts, permitting server capability discovery and enablement. Common objects that are discovered are appended onto the request as have lines and are sent again on the next request. This allows the remote side to reinitialize its in-memory list of common objects during the next request. Because all requests are relatively short, below git-remote-curl's 1 MiB buffer limit, requests will use the standard Content-Length header and be valid HTTP/1.0 POST requests. This makes the fetch client more tolerant of proxy servers which don't support HTTP/1.1 or the chunked transfer encoding. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano --- builtin-fetch-pack.c | 110 ++++++++++++++++++++++++++++++++++++------- fetch-pack.h | 3 +- remote-curl.c | 69 +++++++++++++++++++++++++-- 3 files changed, 160 insertions(+), 22 deletions(-) diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 615f54974e..8ed4a6feaa 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -165,6 +165,24 @@ enum ack_type { ACK_ready }; +static void consume_shallow_list(int fd) +{ + if (args.stateless_rpc && args.depth > 0) { + /* If we sent a depth we will get back "duplicate" + * shallow and unshallow commands every time there + * is a block of have lines exchanged. + */ + char line[1000]; + while (packet_read_line(fd, line, sizeof(line))) { + if (!prefixcmp(line, "shallow ")) + continue; + if (!prefixcmp(line, "unshallow ")) + continue; + die("git fetch-pack: expected shallow list"); + } + } +} + static enum ack_type get_ack(int fd, unsigned char *result_sha1) { static char line[1000]; @@ -190,6 +208,15 @@ static enum ack_type get_ack(int fd, unsigned char *result_sha1) die("git fetch_pack: expected ACK/NAK, got '%s'", line); } +static void send_request(int fd, struct strbuf *buf) +{ + if (args.stateless_rpc) { + send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX); + packet_flush(fd); + } else + safe_write(fd, buf->buf, buf->len); +} + static int find_common(int fd[2], unsigned char *result_sha1, struct ref *refs) { @@ -199,7 +226,10 @@ static int find_common(int fd[2], unsigned char *result_sha1, unsigned in_vain = 0; int got_continue = 0; struct strbuf req_buf = STRBUF_INIT; + size_t state_len = 0; + if (args.stateless_rpc && multi_ack == 1) + die("--stateless-rpc requires multi_ack_detailed"); if (marked) for_each_ref(clear_marks, NULL); marked = 1; @@ -256,13 +286,13 @@ static int find_common(int fd[2], unsigned char *result_sha1, if (args.depth > 0) packet_buf_write(&req_buf, "deepen %d", args.depth); packet_buf_flush(&req_buf); - - safe_write(fd[1], req_buf.buf, req_buf.len); + state_len = req_buf.len; if (args.depth > 0) { char line[1024]; unsigned char sha1[20]; + send_request(fd[1], &req_buf); while (packet_read_line(fd[0], line, sizeof(line))) { if (!prefixcmp(line, "shallow ")) { if (get_sha1_hex(line + 8, sha1)) @@ -284,28 +314,40 @@ static int find_common(int fd[2], unsigned char *result_sha1, } die("expected shallow/unshallow, got %s", line); } + } else if (!args.stateless_rpc) + send_request(fd[1], &req_buf); + + if (!args.stateless_rpc) { + /* If we aren't using the stateless-rpc interface + * we don't need to retain the headers. + */ + strbuf_setlen(&req_buf, 0); + state_len = 0; } flushes = 0; retval = -1; while ((sha1 = get_rev())) { - packet_write(fd[1], "have %s\n", sha1_to_hex(sha1)); + packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1)); if (args.verbose) fprintf(stderr, "have %s\n", sha1_to_hex(sha1)); in_vain++; if (!(31 & ++count)) { int ack; - packet_flush(fd[1]); + packet_buf_flush(&req_buf); + send_request(fd[1], &req_buf); + strbuf_setlen(&req_buf, state_len); flushes++; /* * We keep one window "ahead" of the other side, and * will wait for an ACK only on the next one */ - if (count == 32) + if (!args.stateless_rpc && count == 32) continue; + consume_shallow_list(fd[0]); do { ack = get_ack(fd[0], result_sha1); if (args.verbose && ack) @@ -322,6 +364,17 @@ static int find_common(int fd[2], unsigned char *result_sha1, case ACK_continue: { struct commit *commit = lookup_commit(result_sha1); + if (args.stateless_rpc + && ack == ACK_common + && !(commit->object.flags & COMMON)) { + /* We need to replay the have for this object + * on the next RPC request so the peer knows + * it is in common with us. + */ + const char *hex = sha1_to_hex(result_sha1); + packet_buf_write(&req_buf, "have %s\n", hex); + state_len = req_buf.len; + } mark_common(commit, 0, 1); retval = 0; in_vain = 0; @@ -339,7 +392,8 @@ static int find_common(int fd[2], unsigned char *result_sha1, } } done: - packet_write(fd[1], "done\n"); + packet_buf_write(&req_buf, "done\n"); + send_request(fd[1], &req_buf); if (args.verbose) fprintf(stderr, "done\n"); if (retval != 0) { @@ -348,6 +402,7 @@ done: } strbuf_release(&req_buf); + consume_shallow_list(fd[0]); while (flushes || multi_ack) { int ack = get_ack(fd[0], result_sha1); if (ack) { @@ -672,6 +727,8 @@ static struct ref *do_fetch_pack(int fd[2], */ warning("no common commits"); + if (args.stateless_rpc) + packet_flush(fd[1]); if (get_pack(fd, pack_lockfile)) die("git fetch-pack: fetch failed."); @@ -742,6 +799,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) struct ref *ref = NULL; char *dest = NULL, **heads; int fd[2]; + char *pack_lockfile = NULL; + char **pack_lockfile_ptr = NULL; struct child_process *conn; nr_heads = 0; @@ -791,6 +850,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.no_progress = 1; continue; } + if (!strcmp("--stateless-rpc", arg)) { + args.stateless_rpc = 1; + continue; + } + if (!strcmp("--lock-pack", arg)) { + args.lock_pack = 1; + pack_lockfile_ptr = &pack_lockfile; + continue; + } usage(fetch_pack_usage); } dest = (char *)arg; @@ -801,19 +869,27 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) if (!dest) usage(fetch_pack_usage); - conn = git_connect(fd, (char *)dest, args.uploadpack, - args.verbose ? CONNECT_VERBOSE : 0); - if (conn) { - get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL); - - ref = fetch_pack(&args, fd, conn, ref, dest, nr_heads, heads, NULL); - close(fd[0]); - close(fd[1]); - if (finish_connect(conn)) - ref = NULL; + if (args.stateless_rpc) { + conn = NULL; + fd[0] = 0; + fd[1] = 1; } else { - ref = NULL; + conn = git_connect(fd, (char *)dest, args.uploadpack, + args.verbose ? CONNECT_VERBOSE : 0); } + + get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL); + + ref = fetch_pack(&args, fd, conn, ref, dest, + nr_heads, heads, pack_lockfile_ptr); + if (pack_lockfile) { + printf("lock %s\n", pack_lockfile); + fflush(stdout); + } + close(fd[0]); + close(fd[1]); + if (finish_connect(conn)) + ref = NULL; ret = !ref; if (!ret && nr_heads) { diff --git a/fetch-pack.h b/fetch-pack.h index 8bd9c32561..fbe85ac05f 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -13,7 +13,8 @@ struct fetch_pack_args fetch_all:1, verbose:1, no_progress:1, - include_tag:1; + include_tag:1, + stateless_rpc:1; }; struct ref *fetch_pack(struct fetch_pack_args *args, diff --git a/remote-curl.c b/remote-curl.c index f1206cb572..0eb6fc48fb 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -45,7 +45,7 @@ static int set_option(const char *name, const char *value) options.progress = 0; else return -1; - return 1 /* TODO implement later */; + return 0; } else if (!strcmp(name, "depth")) { char *end; @@ -53,7 +53,7 @@ static int set_option(const char *name, const char *value) if (value == end || *end) return -1; options.depth = v; - return 1 /* TODO implement later */; + return 0; } else if (!strcmp(name, "followtags")) { if (!strcmp(value, "true")) @@ -62,7 +62,7 @@ static int set_option(const char *name, const char *value) options.followtags = 0; else return -1; - return 1 /* TODO implement later */; + return 0; } else if (!strcmp(name, "dry-run")) { if (!strcmp(value, "true")) @@ -463,6 +463,8 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch) char **targets = xmalloc(nr_heads * sizeof(char*)); int ret, i; + if (options.depth) + die("dumb http transport does not support --depth"); for (i = 0; i < nr_heads; i++) targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1)); @@ -481,6 +483,65 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch) return ret ? error("Fetch failed.") : 0; } +static int fetch_git(struct discovery *heads, + int nr_heads, struct ref **to_fetch) +{ + struct rpc_state rpc; + char *depth_arg = NULL; + const char **argv; + int argc = 0, i, err; + + argv = xmalloc((15 + nr_heads) * sizeof(char*)); + argv[argc++] = "fetch-pack"; + argv[argc++] = "--stateless-rpc"; + argv[argc++] = "--lock-pack"; + if (options.followtags) + argv[argc++] = "--include-tag"; + if (options.thin) + argv[argc++] = "--thin"; + if (options.verbosity >= 3) { + argv[argc++] = "-v"; + argv[argc++] = "-v"; + } + if (!options.progress) + argv[argc++] = "--no-progress"; + if (options.depth) { + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "--depth=%lu", options.depth); + depth_arg = strbuf_detach(&buf, NULL); + argv[argc++] = depth_arg; + } + argv[argc++] = url; + for (i = 0; i < nr_heads; i++) { + struct ref *ref = to_fetch[i]; + if (!ref->name || !*ref->name) + die("cannot fetch by sha1 over smart http"); + argv[argc++] = ref->name; + } + argv[argc++] = NULL; + + memset(&rpc, 0, sizeof(rpc)); + rpc.service_name = "git-upload-pack", + rpc.argv = argv; + + err = rpc_service(&rpc, heads); + if (rpc.result.len) + safe_write(1, rpc.result.buf, rpc.result.len); + strbuf_release(&rpc.result); + free(argv); + free(depth_arg); + return err; +} + +static int fetch(int nr_heads, struct ref **to_fetch) +{ + struct discovery *d = discover_refs("git-upload-pack"); + if (d->proto_git) + return fetch_git(d, nr_heads, to_fetch); + else + return fetch_dumb(nr_heads, to_fetch); +} + static void parse_fetch(struct strbuf *buf) { struct ref **to_fetch = NULL; @@ -523,7 +584,7 @@ static void parse_fetch(struct strbuf *buf) break; } while (1); - if (fetch_dumb(nr_heads, to_fetch)) + if (fetch(nr_heads, to_fetch)) exit(128); /* error already reported */ free_refs(list_head); free(to_fetch); From b8538603a34b13ccb1a3a096f461b8a35d7ea878 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:43 -0700 Subject: [PATCH 238/884] Smart HTTP fetch: gzip requests The upload-pack requests are mostly plain text and they compress rather well. Deflating them with Content-Encoding: gzip can easily drop the size of the request by 50%, reducing the amount of data to transfer as we negotiate the common commits. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano --- remote-curl.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/remote-curl.c b/remote-curl.c index 0eb6fc48fb..0d7cf16e9c 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -289,6 +289,7 @@ struct rpc_state { int in; int out; struct strbuf result; + unsigned gzip_request : 1; }; static size_t rpc_out(void *ptr, size_t eltsize, @@ -327,6 +328,8 @@ static int post_rpc(struct rpc_state *rpc) struct active_request_slot *slot; struct slot_results results; struct curl_slist *headers = NULL; + int use_gzip = rpc->gzip_request; + char *gzip_body = NULL; int err = 0, large_request = 0; /* Try to load the entire request, if we can fit it into the @@ -340,6 +343,7 @@ static int post_rpc(struct rpc_state *rpc) if (left < LARGE_PACKET_MAX) { large_request = 1; + use_gzip = 0; break; } @@ -355,6 +359,7 @@ static int post_rpc(struct rpc_state *rpc) curl_easy_setopt(slot->curl, CURLOPT_POST, 1); curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url); + curl_easy_setopt(slot->curl, CURLOPT_ENCODING, ""); headers = curl_slist_append(headers, rpc->hdr_content_type); headers = curl_slist_append(headers, rpc->hdr_accept); @@ -372,6 +377,49 @@ static int post_rpc(struct rpc_state *rpc) fflush(stderr); } + } else if (use_gzip && 1024 < rpc->len) { + /* The client backend isn't giving us compressed data so + * we can try to deflate it ourselves, this may save on. + * the transfer time. + */ + size_t size; + z_stream stream; + int ret; + + memset(&stream, 0, sizeof(stream)); + ret = deflateInit2(&stream, Z_BEST_COMPRESSION, + Z_DEFLATED, (15 + 16), + 8, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) + die("cannot deflate request; zlib init error %d", ret); + size = deflateBound(&stream, rpc->len); + gzip_body = xmalloc(size); + + stream.next_in = (unsigned char *)rpc->buf; + stream.avail_in = rpc->len; + stream.next_out = (unsigned char *)gzip_body; + stream.avail_out = size; + + ret = deflate(&stream, Z_FINISH); + if (ret != Z_STREAM_END) + die("cannot deflate request; zlib deflate error %d", ret); + + ret = deflateEnd(&stream); + if (ret != Z_OK) + die("cannot deflate request; zlib end error %d", ret); + + size = stream.total_out; + + headers = curl_slist_append(headers, "Content-Encoding: gzip"); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, size); + + if (options.verbosity > 1) { + fprintf(stderr, "POST %s (gzip %lu to %lu bytes)\n", + rpc->service_name, + (unsigned long)rpc->len, (unsigned long)size); + fflush(stderr); + } } else { /* We know the complete request size in advance, use the * more normal Content-Length approach. @@ -398,6 +446,7 @@ static int post_rpc(struct rpc_state *rpc) } curl_slist_free_all(headers); + free(gzip_body); return err; } @@ -523,6 +572,7 @@ static int fetch_git(struct discovery *heads, memset(&rpc, 0, sizeof(rpc)); rpc.service_name = "git-upload-pack", rpc.argv = argv; + rpc.gzip_request = 1; err = rpc_service(&rpc, heads); if (rpc.result.len) From eeb3aeddb23746912b6c903a7d90dffdd686708f Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Fri, 30 Oct 2009 17:47:44 -0700 Subject: [PATCH 239/884] t5540-http-push: remove redundant fetches Signed-off-by: Tay Ray Chuan Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/t5540-http-push.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index 2ece6612e1..28a746e5b9 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -116,9 +116,7 @@ test_expect_success 'create and delete remote branch' ' test_tick && git commit -m dev && git push origin dev && - git fetch && git push origin :dev && - git fetch && test_must_fail git show-ref --verify refs/remotes/origin/dev ' From 859d1fb427ca79d3c70a3806c892e4dd148701bf Mon Sep 17 00:00:00 2001 From: Clemens Buchacher Date: Fri, 30 Oct 2009 17:47:45 -0700 Subject: [PATCH 240/884] set httpd port before sourcing lib-httpd If LIB_HTTPD_PORT is not set already, lib-httpd will set it to the default 8111. Signed-off-by: Clemens Buchacher Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/t5540-http-push.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index 28a746e5b9..d1234f9fef 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -9,17 +9,16 @@ This test runs various sanity checks on http-push.' . ./test-lib.sh -ROOT_PATH="$PWD" -LIB_HTTPD_DAV=t -LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'} - if git http-push > /dev/null 2>&1 || [ $? -eq 128 ] then say "skipping test, USE_CURL_MULTI is not defined" test_done fi +LIB_HTTPD_DAV=t +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'} . "$TEST_DIRECTORY"/lib-httpd.sh +ROOT_PATH="$PWD" start_httpd test_expect_success 'setup remote repository' ' From 024bb1256627219671a0924b195582b6e049ca87 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:46 -0700 Subject: [PATCH 241/884] http tests: use /dumb/ URL prefix To clarify what part of the HTTP transprot is being tested we change the URLs used by existing tests to include /dumb/ at the start, indicating they use the non-Git aware code paths. Signed-off-by: Shawn O. Pearce CC: Tay Ray Chuan Signed-off-by: Junio C Hamano --- t/lib-httpd/apache.conf | 7 ++++++- t/t5540-http-push.sh | 10 +++++----- t/t5550-http-fetch.sh | 4 ++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 21aa42f1c6..47a438f4c6 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -8,6 +8,11 @@ ErrorLog error.log LoadModule log_config_module modules/mod_log_config.so + + LoadModule alias_module modules/mod_alias.so + + +Alias /dumb/ www/ LoadModule ssl_module modules/mod_ssl.so @@ -26,7 +31,7 @@ SSLEngine On LoadModule dav_fs_module modules/mod_dav_fs.so DAVLockDB DAVLock - + Dav on diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index d1234f9fef..4a9450e79f 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -42,7 +42,7 @@ test_expect_success 'setup remote repository' ' test_expect_success 'clone remote repository' ' cd "$ROOT_PATH" && - git clone $HTTPD_URL/test_repo.git test_repo_clone + git clone $HTTPD_URL/dumb/test_repo.git test_repo_clone ' test_expect_success 'push to remote repository with packed refs' ' @@ -75,7 +75,7 @@ test_expect_success 'http-push fetches unpacked objects' ' cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \ "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_unpacked.git && - git clone $HTTPD_URL/test_repo_unpacked.git \ + git clone $HTTPD_URL/dumb/test_repo_unpacked.git \ "$ROOT_PATH"/fetch_unpacked && # By reset, we force git to retrieve the object @@ -84,14 +84,14 @@ test_expect_success 'http-push fetches unpacked objects' ' git remote rm origin && git reflog expire --expire=0 --all && git prune && - git push -f -v $HTTPD_URL/test_repo_unpacked.git master) + git push -f -v $HTTPD_URL/dumb/test_repo_unpacked.git master) ' test_expect_success 'http-push fetches packed objects' ' cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \ "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git && - git clone $HTTPD_URL/test_repo_packed.git \ + git clone $HTTPD_URL/dumb/test_repo_packed.git \ "$ROOT_PATH"/test_repo_clone_packed && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git && @@ -104,7 +104,7 @@ test_expect_success 'http-push fetches packed objects' ' git remote rm origin && git reflog expire --expire=0 --all && git prune && - git push -f -v $HTTPD_URL/test_repo_packed.git master) + git push -f -v $HTTPD_URL/dumb/test_repo_packed.git master) ' test_expect_success 'create and delete remote branch' ' diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh index 0e69324652..776057c543 100755 --- a/t/t5550-http-fetch.sh +++ b/t/t5550-http-fetch.sh @@ -30,7 +30,7 @@ test_expect_success 'create http-accessible bare repository' ' ' test_expect_success 'clone http repository' ' - git clone $HTTPD_URL/repo.git clone && + git clone $HTTPD_URL/dumb/repo.git clone && test_cmp file clone/file ' @@ -58,7 +58,7 @@ test_expect_success 'fetch packed objects' ' cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && git --bare repack && git --bare prune-packed && - git clone $HTTPD_URL/repo_pack.git + git clone $HTTPD_URL/dumb/repo_pack.git ' stop_httpd From 7da4e2280ccaf5ecb357f7cb2b81d62f78f00f9e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:47 -0700 Subject: [PATCH 242/884] test smart http fetch and push The top level directory "/smart/" of the test Apache server is mapped through our git-http-backend CGI, but uses the same underlying repository space as the server's document root. This is the most simple installation possible. Server logs are checked to verify the client has accessed only the smart URLs during the test. During fetch testing the headers are also logged from libcurl to ensure we are making a reasonably sane HTTP request, and getting back reasonably sane response headers from the CGI. When validating the request headers used during smart fetch we munge away the actual Content-Length and replace it with the placeholder "xxx". This avoids unnecessary varability in the test caused by an unrelated change in the requested capabilities in the first want line of the request. However, we still want to look for and verify that Content-Length was used, because smaller payloads should be using Content-Length and not "Transfer-Encoding: chunked". When validating the server response headers we must discard both Content-Length and Transfer-Encoding, as Apache2 can use either format to return our response. During development of this test I observed Apache returning both forms, depending on when the processes got CPU time. If our CGI returned the pack data quickly, Apache just buffered the whole thing and returned a Content-Length. If our CGI took just a bit too long to complete, Apache flushed its buffer and instead used "Transfer-Encoding: chunked". Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/lib-httpd/apache.conf | 17 +++++++ t/t5540-http-push.sh | 2 +- t/t5541-http-push.sh | 92 ++++++++++++++++++++++++++++++++++++ t/t5550-http-fetch.sh | 8 +++- t/t5551-http-fetch.sh | 102 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 219 insertions(+), 2 deletions(-) create mode 100755 t/t5541-http-push.sh create mode 100755 t/t5551-http-fetch.sh diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 47a438f4c6..0fe3fd0d01 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -11,9 +11,26 @@ ErrorLog error.log LoadModule alias_module modules/mod_alias.so + + LoadModule cgi_module modules/mod_cgi.so + + + LoadModule env_module modules/mod_env.so + Alias /dumb/ www/ + + SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} + +ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/ + + Options None + + + Options ExecCGI + + LoadModule ssl_module modules/mod_ssl.so diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index 4a9450e79f..bb18f8bfc4 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -3,7 +3,7 @@ # Copyright (c) 2008 Clemens Buchacher # -test_description='test http-push +test_description='test WebDAV http-push This test runs various sanity checks on http-push.' diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh new file mode 100755 index 0000000000..2a58d0cc9c --- /dev/null +++ b/t/t5541-http-push.sh @@ -0,0 +1,92 @@ +#!/bin/sh +# +# Copyright (c) 2008 Clemens Buchacher +# + +test_description='test smart pushing over http via http-backend' +. ./test-lib.sh + +if test -n "$NO_CURL"; then + say 'skipping test, git built without http support' + test_done +fi + +ROOT_PATH="$PWD" +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5541'} +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'setup remote repository' ' + cd "$ROOT_PATH" && + mkdir test_repo && + cd test_repo && + git init && + : >path1 && + git add path1 && + test_tick && + git commit -m initial && + cd - && + git clone --bare test_repo test_repo.git && + cd test_repo.git && + git config http.receivepack true && + ORIG_HEAD=$(git rev-parse --verify HEAD) && + cd - && + mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH" +' + +test_expect_success 'clone remote repository' ' + cd "$ROOT_PATH" && + git clone $HTTPD_URL/smart/test_repo.git test_repo_clone +' + +test_expect_success 'push to remote repository' ' + cd "$ROOT_PATH"/test_repo_clone && + : >path2 && + git add path2 && + test_tick && + git commit -m path2 && + HEAD=$(git rev-parse --verify HEAD) && + git push && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && + test $HEAD = $(git rev-parse --verify HEAD)) +' + +test_expect_success 'push already up-to-date' ' + git push +' + +test_expect_success 'create and delete remote branch' ' + cd "$ROOT_PATH"/test_repo_clone && + git checkout -b dev && + : >path3 && + git add path3 && + test_tick && + git commit -m dev && + git push origin dev && + git push origin :dev && + test_must_fail git show-ref --verify refs/remotes/origin/dev +' + +cat >exp <act <"$HTTPD_ROOT_PATH"/access.log && + test_cmp exp act +' + +stop_httpd +test_done diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh index 776057c543..8cfce969bc 100755 --- a/t/t5550-http-fetch.sh +++ b/t/t5550-http-fetch.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='test fetching over http' +test_description='test dumb fetching over http via static file' . ./test-lib.sh if test -n "$NO_CURL"; then @@ -61,5 +61,11 @@ test_expect_success 'fetch packed objects' ' git clone $HTTPD_URL/dumb/repo_pack.git ' +test_expect_success 'did not use upload-pack service' ' + grep '/git-upload-pack' <"$HTTPD_ROOT_PATH"/access.log >act + : >exp + test_cmp exp act +' + stop_httpd test_done diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh new file mode 100755 index 0000000000..eb0c039323 --- /dev/null +++ b/t/t5551-http-fetch.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +test_description='test smart fetching over http via http-backend' +. ./test-lib.sh + +if test -n "$NO_CURL"; then + say 'skipping test, git built without http support' + test_done +fi + +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5551'} +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'setup repository' ' + echo content >file && + git add file && + git commit -m one +' + +test_expect_success 'create http-accessible bare repository' ' + mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git --bare init + ) && + git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git push public master:master +' + +cat >exp < GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 +> Accept: */* +> Pragma: no-cache + +< HTTP/1.1 200 OK +< Pragma: no-cache +< Cache-Control: no-cache, max-age=0, must-revalidate +< Content-Type: application/x-git-upload-pack-advertisement +< +> POST /smart/repo.git/git-upload-pack HTTP/1.1 +> Accept-Encoding: deflate, gzip +> Content-Type: application/x-git-upload-pack-request +> Accept: application/x-git-upload-pack-response +> Content-Length: xxx + +< HTTP/1.1 200 OK +< Pragma: no-cache +< Cache-Control: no-cache, max-age=0, must-revalidate +< Content-Type: application/x-git-upload-pack-result +< +EOF +test_expect_success 'clone http repository' ' + GIT_CURL_VERBOSE=1 git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err && + test_cmp file clone/file && + tr '\''\015'\'' Q <]/{ + s/^/> / + } + + /^> User-Agent: /d + /^> Host: /d + s/^> Content-Length: .*/> Content-Length: xxx/ + + /^< Server: /d + /^< Expires: /d + /^< Date: /d + /^< Content-Length: /d + /^< Transfer-Encoding: /d + " >act && + test_cmp exp act +' + +test_expect_success 'fetch changes via http' ' + echo content >>file && + git commit -a -m two && + git push public + (cd clone && git pull) && + test_cmp file clone/file +' + +cat >exp <act <"$HTTPD_ROOT_PATH"/access.log && + test_cmp exp act +' + +stop_httpd +test_done From 5abb013b3ddfb42e5baa3c7de052af596a0ee82f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 4 Nov 2009 17:16:37 -0800 Subject: [PATCH 243/884] http-backend: Use http.getanyfile to disable dumb HTTP serving Some repository owners may wish to enable smart HTTP, but disallow dumb content serving. Disallowing dumb serving might be because the owners want to rely upon reachability to control which objects clients may access from the repository, or they just want to encourage clients to use the more bandwidth efficient transport. If http.getanyfile is set to false the backend CGI will return with '403 Forbidden' when an object file is accessed by a dumb client. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-http-backend.txt | 8 +++++++ http-backend.c | 34 ++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index f17251ab9d..67aec067c8 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -29,6 +29,14 @@ SERVICES These services can be enabled/disabled using the per-repository configuration file: +http.getanyfile:: + This serves older Git clients which are unable to use the + upload pack service. When enabled, clients are able to read + any file within the repository, including objects that are + no longer reachable from a branch but are still present. + It is enabled by default, but a repository can disable it + by setting this configuration item to `false`. + http.uploadpack:: This serves 'git-fetch-pack' and 'git-ls-remote' clients. It is enabled by default, but a repository can disable it diff --git a/http-backend.c b/http-backend.c index 7900cda69a..902126675a 100644 --- a/http-backend.c +++ b/http-backend.c @@ -10,6 +10,7 @@ static const char content_type[] = "Content-Type"; static const char content_length[] = "Content-Length"; static const char last_modified[] = "Last-Modified"; +static int getanyfile = 1; static struct string_list *query_params; @@ -194,6 +195,12 @@ static NORETURN void forbidden(const char *err, ...) exit(0); } +static void select_getanyfile(void) +{ + if (!getanyfile) + forbidden("Unsupported service: getanyfile"); +} + static void send_strbuf(const char *type, struct strbuf *buf) { hdr_int(content_length, buf->len); @@ -238,38 +245,51 @@ static void send_file(const char *the_type, const char *name) static void get_text_file(char *name) { + select_getanyfile(); hdr_nocache(); send_file("text/plain", name); } static void get_loose_object(char *name) { + select_getanyfile(); hdr_cache_forever(); send_file("application/x-git-loose-object", name); } static void get_pack_file(char *name) { + select_getanyfile(); hdr_cache_forever(); send_file("application/x-git-packed-objects", name); } static void get_idx_file(char *name) { + select_getanyfile(); hdr_cache_forever(); send_file("application/x-git-packed-objects-toc", name); } static int http_config(const char *var, const char *value, void *cb) { - struct rpc_service *svc = cb; - - if (!prefixcmp(var, "http.") && - !strcmp(var + 5, svc->config_name)) { - svc->enabled = git_config_bool(var, value); + if (!strcmp(var, "http.getanyfile")) { + getanyfile = git_config_bool(var, value); return 0; } + if (!prefixcmp(var, "http.")) { + int i; + + for (i = 0; i < ARRAY_SIZE(rpc_service); i++) { + struct rpc_service *svc = &rpc_service[i]; + if (!strcmp(var + 5, svc->config_name)) { + svc->enabled = git_config_bool(var, value); + return 0; + } + } + } + /* we are not interested in parsing any other configuration here */ return 0; } @@ -293,7 +313,6 @@ static struct rpc_service *select_service(const char *name) if (!svc) forbidden("Unsupported service: '%s'", name); - git_config(http_config, svc); if (svc->enabled < 0) { const char *user = getenv("REMOTE_USER"); svc->enabled = (user && *user) ? 1 : 0; @@ -442,6 +461,7 @@ static void get_info_refs(char *arg) run_service(argv); } else { + select_getanyfile(); for_each_ref(show_text_ref, &buf); send_strbuf("text/plain", &buf); } @@ -455,6 +475,7 @@ static void get_info_packs(char *arg) struct packed_git *p; size_t cnt = 0; + select_getanyfile(); prepare_packed_git(); for (p = packed_git; p; p = p->next) { if (p->pack_local) @@ -621,6 +642,7 @@ int main(int argc, char **argv) if (!enter_repo(dir, 0)) not_found("Not a git repository: '%s'", dir); + git_config(http_config, NULL); cmd->imp(cmd_arg); return 0; } From 7f640b778f8cf87159890157a815f1d728573477 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 4 Nov 2009 17:16:38 -0800 Subject: [PATCH 244/884] http-backend: Test configuration options Test the major configuration settings which control access to the repository: http.getanyfile http.uploadpack http.receivepack Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/t5560-http-backend.sh | 229 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100755 t/t5560-http-backend.sh diff --git a/t/t5560-http-backend.sh b/t/t5560-http-backend.sh new file mode 100755 index 0000000000..908ba079d2 --- /dev/null +++ b/t/t5560-http-backend.sh @@ -0,0 +1,229 @@ +#!/bin/sh + +test_description='test git-http-backend' +. ./test-lib.sh + +if test -n "$NO_CURL"; then + say 'skipping test, git built without http support' + test_done +fi + +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5560'} +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +find_file() { + cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + find $1 -type f | + sed -e 1q +} + +config() { + git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" config $1 $2 +} + +GET() { + curl --include "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null && + tr '\015' Q act && + echo "HTTP/1.1 $2" >exp && + test_cmp exp act +} + +POST() { + curl --include --data "$2" \ + --header "Content-Type: application/x-$1-request" \ + "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null && + tr '\015' Q act && + echo "HTTP/1.1 $3" >exp && + test_cmp exp act +} + +log_div() { + echo >>"$HTTPD_ROOT_PATH"/access.log + echo "### $1" >>"$HTTPD_ROOT_PATH"/access.log + echo "###" >>"$HTTPD_ROOT_PATH"/access.log +} + +test_expect_success 'setup repository' ' + echo content >file && + git add file && + git commit -m one && + + mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git --bare init && + : >objects/info/alternates && + : >objects/info/http-alternates + ) && + git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git push public master:master && + + (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git repack -a -d + ) && + + echo other >file && + git add file && + git commit -m two && + git push public master:master && + + LOOSE_URL=$(find_file objects/??) && + PACK_URL=$(find_file objects/pack/*.pack) && + IDX_URL=$(find_file objects/pack/*.idx) +' + +get_static_files() { + GET HEAD "$1" && + GET info/refs "$1" && + GET objects/info/packs "$1" && + GET objects/info/alternates "$1" && + GET objects/info/http-alternates "$1" && + GET $LOOSE_URL "$1" && + GET $PACK_URL "$1" && + GET $IDX_URL "$1" +} + +test_expect_success 'direct refs/heads/master not found' ' + log_div "refs/heads/master" + GET refs/heads/master "404 Not Found" +' +test_expect_success 'static file is ok' ' + log_div "getanyfile default" + get_static_files "200 OK" +' +test_expect_success 'static file if http.getanyfile true is ok' ' + log_div "getanyfile true" + config http.getanyfile true && + get_static_files "200 OK" +' +test_expect_success 'static file if http.getanyfile false fails' ' + log_div "getanyfile false" + config http.getanyfile false && + get_static_files "403 Forbidden" +' + +test_expect_success 'http.uploadpack default enabled' ' + log_div "uploadpack default" + GET info/refs?service=git-upload-pack "200 OK" && + POST git-upload-pack 0000 "200 OK" +' +test_expect_success 'http.uploadpack true' ' + log_div "uploadpack true" + config http.uploadpack true && + GET info/refs?service=git-upload-pack "200 OK" && + POST git-upload-pack 0000 "200 OK" +' +test_expect_success 'http.uploadpack false' ' + log_div "uploadpack false" + config http.uploadpack false && + GET info/refs?service=git-upload-pack "403 Forbidden" && + POST git-upload-pack 0000 "403 Forbidden" +' + +test_expect_success 'http.receivepack default disabled' ' + log_div "receivepack default" + GET info/refs?service=git-receive-pack "403 Forbidden" && + POST git-receive-pack 0000 "403 Forbidden" +' +test_expect_success 'http.receivepack true' ' + log_div "receivepack true" + config http.receivepack true && + GET info/refs?service=git-receive-pack "200 OK" && + POST git-receive-pack 0000 "200 OK" +' +test_expect_success 'http.receivepack false' ' + log_div "receivepack false" + config http.receivepack false && + GET info/refs?service=git-receive-pack "403 Forbidden" && + POST git-receive-pack 0000 "403 Forbidden" +' + +cat >exp <act <"$HTTPD_ROOT_PATH"/access.log && + test_cmp exp act +' + +stop_httpd +test_done From ef0555712c9821cc163508777f20b934bf146971 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 4 Nov 2009 16:32:46 -0500 Subject: [PATCH 245/884] pack-objects: move thread autodetection closer to relevant code Let's keep thread stuff close together if possible. And in this case, this even reduces the #ifdef noise, and allows for skipping the autodetection altogether if delta search is not needed (like with a pure clone). Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 02f9246cdb..4c91e944c2 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1629,6 +1629,8 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, struct thread_params *p; int i, ret, active_threads = 0; + if (!delta_search_threads) /* --threads=0 means autodetect */ + delta_search_threads = online_cpus(); if (delta_search_threads <= 1) { find_deltas(list, &list_size, window, depth, processed); return; @@ -2324,11 +2326,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (keep_unreachable && unpack_unreachable) die("--keep-unreachable and --unpack-unreachable are incompatible."); -#ifdef THREADED_DELTA_SEARCH - if (!delta_search_threads) /* --threads=0 means autodetect */ - delta_search_threads = online_cpus(); -#endif - prepare_packed_git(); if (progress) From b9759f0762c17a5b7bc36af98613c67249931330 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 6 Nov 2009 16:08:41 +0100 Subject: [PATCH 246/884] gitweb: Fix blob linenr links in pathinfo mode In pathinfo mode, we use that refers to the base location of gitweb in order for various external media links to work well. However, this means that for the page to refer to itself, it must regenerate full link, and this is exactly what the blob view page did not do for line numbers. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 24b219310a..184b683aa5 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -5065,7 +5065,8 @@ sub git_blob { chomp $line; $nr++; $line = untabify($line); - printf "
%4i %s
\n", + printf "\n", $nr, $nr, $nr, esc_html($line, -nbsp=>1); } } From 1a994dc3d258a1603752e0c472b072acc343a963 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Wed, 4 Nov 2009 22:33:52 -0800 Subject: [PATCH 247/884] t1200: cleanup and modernize test style Many parts of the tests in t1200 are run outside the test harness, circumventing the usefulness of -v and spewing messages to stdout when -v isn't used. Fix these problems by modernizing the test a bit. An extra test_done has existed since commit 6a74642 (git-commit --amend: two fixes., 2006-04-20) leading to the last 6 tests never being run. Remove it and teach the resolve merge test about fast-forward merges. Also fix the last test's incorrect find command and prune before checking for unpacked objects so we remove the unreachable conflict-marked blob. Finally, we remove the TODO notes, because fetch, push, and clone have their own tests since t1200 was introduced and we're not going to add them here 4 years later. Signed-off-by: Stephen Boyd Signed-off-by: Junio C Hamano --- t/t1200-tutorial.sh | 138 +++++++++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 60 deletions(-) diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index 67e637b781..c57c9d5300 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -7,14 +7,18 @@ test_description='A simple turial in the form of a test case' . ./test-lib.sh -echo "Hello World" > hello -echo "Silly example" > example +test_expect_success 'blob' ' + echo "Hello World" > hello && + echo "Silly example" > example && -git update-index --add hello example + git update-index --add hello example && -test_expect_success 'blob' "test blob = \"$(git cat-file -t 557db03)\"" + test blob = "$(git cat-file -t 557db03)" +' -test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git cat-file blob 557db03)\"" +test_expect_success 'blob 557db03' ' + test "Hello World" = "$(git cat-file blob 557db03)" +' echo "It's a new day for git" >>hello cat > diff.expect << EOF @@ -26,25 +30,33 @@ index 557db03..263414f 100644 Hello World +It's a new day for git EOF -git diff-files -p > diff.output -test_expect_success 'git diff-files -p' 'cmp diff.expect diff.output' -git diff > diff.output -test_expect_success 'git diff' 'cmp diff.expect diff.output' -tree=$(git write-tree 2>/dev/null) +test_expect_success 'git diff-files -p' ' + git diff-files -p > diff.output && + cmp diff.expect diff.output +' -test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tree" +test_expect_success 'git diff' ' + git diff > diff.output && + cmp diff.expect diff.output +' -output="$(echo "Initial commit" | git commit-tree $(git write-tree) 2>&1 > .git/refs/heads/master)" +test_expect_success 'tree' ' + tree=$(git write-tree 2>/dev/null) + test 8988da15d077d4829fc51d8544c097def6644dbb = $tree +' -git diff-index -p HEAD > diff.output -test_expect_success 'git diff-index -p HEAD' 'cmp diff.expect diff.output' +test_expect_success 'git diff-index -p HEAD' ' + echo "Initial commit" | \ + git commit-tree $(git write-tree) 2>&1 > .git/refs/heads/master && + git diff-index -p HEAD > diff.output && + cmp diff.expect diff.output +' -git diff HEAD > diff.output -test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output' - -#rm hello -#test_expect_success 'git read-tree --reset HEAD' "git read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git update-index --refresh)\"" +test_expect_success 'git diff HEAD' ' + git diff HEAD > diff.output && + cmp diff.expect diff.output +' cat > whatchanged.expect << EOF commit VARIABLE @@ -69,39 +81,45 @@ index 0000000..557db03 +Hello World EOF -git whatchanged -p --root | \ - sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \ +test_expect_success 'git whatchanged -p --root' ' + git whatchanged -p --root | \ + sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \ -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \ -> whatchanged.output -test_expect_success 'git whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output' + > whatchanged.output && + cmp whatchanged.expect whatchanged.output +' -git tag my-first-tag -test_expect_success 'git tag my-first-tag' 'cmp .git/refs/heads/master .git/refs/tags/my-first-tag' +test_expect_success 'git tag my-first-tag' ' + git tag my-first-tag && + cmp .git/refs/heads/master .git/refs/tags/my-first-tag +' -# TODO: test git clone - -git checkout -b mybranch -test_expect_success 'git checkout -b mybranch' 'cmp .git/refs/heads/master .git/refs/heads/mybranch' +test_expect_success 'git checkout -b mybranch' ' + git checkout -b mybranch && + cmp .git/refs/heads/master .git/refs/heads/mybranch +' cat > branch.expect < branch.output -test_expect_success 'git branch' 'cmp branch.expect branch.output' - -git checkout mybranch -echo "Work, work, work" >>hello -git commit -m 'Some work.' -i hello - -git checkout master - -echo "Play, play, play" >>hello -echo "Lots of fun" >>example -git commit -m 'Some fun.' -i hello example +test_expect_success 'git branch' ' + git branch > branch.output && + cmp branch.expect branch.output +' test_expect_success 'git resolve now fails' ' + git checkout mybranch && + echo "Work, work, work" >>hello && + git commit -m "Some work." -i hello && + + git checkout master && + + echo "Play, play, play" >>hello && + echo "Lots of fun" >>example && + git commit -m "Some fun." -i hello example && + test_must_fail git merge -m "Merge work in mybranch" mybranch ' @@ -112,10 +130,6 @@ Play, play, play Work, work, work EOF -git commit -m 'Merged "mybranch" changes.' -i hello - -test_done - cat > show-branch.expect << EOF * [master] Merged "mybranch" changes. ! [mybranch] Some work. @@ -124,21 +138,26 @@ cat > show-branch.expect << EOF *+ [mybranch] Some work. EOF -git show-branch --topo-order master mybranch > show-branch.output -test_expect_success 'git show-branch' 'cmp show-branch.expect show-branch.output' - -git checkout mybranch +test_expect_success 'git show-branch' ' + git commit -m "Merged \"mybranch\" changes." -i hello && + git show-branch --topo-order master mybranch > show-branch.output && + cmp show-branch.expect show-branch.output +' cat > resolve.expect << EOF -Updating from VARIABLE to VARIABLE +Updating VARIABLE..VARIABLE +Fast forward (no commit created; -m option ignored) example | 1 + hello | 1 + 2 files changed, 2 insertions(+), 0 deletions(-) EOF -git merge -s "Merge upstream changes." master | \ - sed -e "1s/[0-9a-f]\{40\}/VARIABLE/g" >resolve.output -test_expect_success 'git resolve' 'cmp resolve.expect resolve.output' +test_expect_success 'git resolve' ' + git checkout mybranch && + git merge -m "Merge upstream changes." master | \ + sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" >resolve.output && + cmp resolve.expect resolve.output +' cat > show-branch2.expect << EOF ! [master] Merged "mybranch" changes. @@ -147,17 +166,16 @@ cat > show-branch2.expect << EOF -- [master] Merged "mybranch" changes. EOF -git show-branch --topo-order master mybranch > show-branch2.output -test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.output' - -# TODO: test git fetch - -# TODO: test git push +test_expect_success 'git show-branch (part 2)' ' + git show-branch --topo-order master mybranch > show-branch2.output && + cmp show-branch2.expect show-branch2.output +' test_expect_success 'git repack' 'git repack' test_expect_success 'git prune-packed' 'git prune-packed' test_expect_success '-> only packed objects' ' - ! find -type f .git/objects/[0-9a-f][0-9a-f] + git prune && # Remove conflict marked blobs + ! find .git/objects/[0-9a-f][0-9a-f] -type f ' test_done From 7c5858a643724a9a4a67f1290c03d57f483ed261 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Wed, 4 Nov 2009 22:33:53 -0800 Subject: [PATCH 248/884] t1200: Make documentation and test agree There were some differences between t1200 and the gitcore-tutorial. Add missing tests for manually merging two branches, and use the same commands in both files. Signed-off-by: Stephen Boyd Signed-off-by: Junio C Hamano --- Documentation/gitcore-tutorial.txt | 20 +++--- t/t1200-tutorial.sh | 97 +++++++++++++++++++++++++++--- 2 files changed, 98 insertions(+), 19 deletions(-) diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index b3640c4e64..7bdf0901dd 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -185,7 +185,7 @@ object is. git will tell you that you have a "blob" object (i.e., just a regular file), and you can see the contents with ---------------- -$ git cat-file "blob" 557db03 +$ git cat-file blob 557db03 ---------------- which will print out "Hello World". The object `557db03` is nothing @@ -1188,7 +1188,7 @@ $ git show-branch -- + [mybranch] Some work. * [master] Some fun. -*+ [mybranch^] New day. +*+ [mybranch^] Initial commit ------------ Now we are ready to experiment with the merge by hand. @@ -1204,11 +1204,11 @@ $ mb=$(git merge-base HEAD mybranch) The command writes the commit object name of the common ancestor to the standard output, so we captured its output to a variable, because we will be using it in the next step. By the way, the common -ancestor commit is the "New day." commit in this case. You can +ancestor commit is the "Initial commit" commit in this case. You can tell it by: ------------ -$ git name-rev $mb +$ git name-rev --name-only --tags $mb my-first-tag ------------ @@ -1237,8 +1237,8 @@ inspect the index file with this command: ------------ $ git ls-files --stage 100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example -100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello -100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello ------------ @@ -1253,8 +1253,8 @@ To look at only non-zero stages, use `\--unmerged` flag: ------------ $ git ls-files --unmerged -100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello -100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello ------------ @@ -1283,8 +1283,8 @@ the working tree.. This can be seen if you run `ls-files ------------ $ git ls-files --stage 100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example -100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello -100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello ------------ diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index c57c9d5300..299e724175 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -47,8 +47,9 @@ test_expect_success 'tree' ' ' test_expect_success 'git diff-index -p HEAD' ' - echo "Initial commit" | \ - git commit-tree $(git write-tree) 2>&1 > .git/refs/heads/master && + tree=$(git write-tree) + commit=$(echo "Initial commit" | git commit-tree $tree) && + git update-ref HEAD $commit && git diff-index -p HEAD > diff.output && cmp diff.expect diff.output ' @@ -131,16 +132,18 @@ Work, work, work EOF cat > show-branch.expect << EOF -* [master] Merged "mybranch" changes. +* [master] Merge work in mybranch ! [mybranch] Some work. -- -- [master] Merged "mybranch" changes. +- [master] Merge work in mybranch *+ [mybranch] Some work. +* [master^] Some fun. EOF test_expect_success 'git show-branch' ' - git commit -m "Merged \"mybranch\" changes." -i hello && - git show-branch --topo-order master mybranch > show-branch.output && + git commit -m "Merge work in mybranch" -i hello && + git show-branch --topo-order --more=1 master mybranch \ + > show-branch.output && cmp show-branch.expect show-branch.output ' @@ -160,10 +163,10 @@ test_expect_success 'git resolve' ' ' cat > show-branch2.expect << EOF -! [master] Merged "mybranch" changes. - * [mybranch] Merged "mybranch" changes. +! [master] Merge work in mybranch + * [mybranch] Merge work in mybranch -- --- [master] Merged "mybranch" changes. +-- [master] Merge work in mybranch EOF test_expect_success 'git show-branch (part 2)' ' @@ -171,6 +174,82 @@ test_expect_success 'git show-branch (part 2)' ' cmp show-branch2.expect show-branch2.output ' +cat > show-branch3.expect << EOF +! [master] Merge work in mybranch + * [mybranch] Merge work in mybranch +-- +-- [master] Merge work in mybranch ++* [master^2] Some work. ++* [master^] Some fun. +EOF + +test_expect_success 'git show-branch (part 3)' ' + git show-branch --topo-order --more=2 master mybranch \ + > show-branch3.output && + cmp show-branch3.expect show-branch3.output +' + +test_expect_success 'rewind to "Some fun." and "Some work."' ' + git checkout mybranch && + git reset --hard master^2 && + git checkout master && + git reset --hard master^ +' + +cat > show-branch4.expect << EOF +* [master] Some fun. + ! [mybranch] Some work. +-- + + [mybranch] Some work. +* [master] Some fun. +*+ [mybranch^] Initial commit +EOF + +test_expect_success 'git show-branch (part 4)' ' + git show-branch --topo-order > show-branch4.output && + cmp show-branch4.expect show-branch4.output +' + +test_expect_success 'manual merge' ' + mb=$(git merge-base HEAD mybranch) && + git name-rev --name-only --tags $mb > name-rev.output && + test "my-first-tag" = $(cat name-rev.output) && + + git read-tree -m -u $mb HEAD mybranch +' + +cat > ls-files.expect << EOF +100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello +100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello +EOF + +test_expect_success 'git ls-files --stage' ' + git ls-files --stage > ls-files.output && + cmp ls-files.expect ls-files.output +' + +cat > ls-files-unmerged.expect << EOF +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello +100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello +EOF + +test_expect_success 'git ls-files --unmerged' ' + git ls-files --unmerged > ls-files-unmerged.output && + cmp ls-files-unmerged.expect ls-files-unmerged.output +' + +test_expect_success 'git-merge-index' ' + test_must_fail git merge-index git-merge-one-file hello +' + +test_expect_success 'git ls-files --stage (part 2)' ' + git ls-files --stage > ls-files.output2 && + cmp ls-files.expect ls-files.output2 +' + test_expect_success 'git repack' 'git repack' test_expect_success 'git prune-packed' 'git prune-packed' test_expect_success '-> only packed objects' ' From b9f3bde150a1be1e2adfb2834caaf415dd7b72ef Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 6 Nov 2009 22:47:37 -0800 Subject: [PATCH 249/884] t1200: further modernize test script style Instead of using bare "cmp", use "test_cmp". Output when the test is run with a -v option becomes easier to diagnose when something goes wrong because on saner platforms test_cmp uses "diff -u". There is no need to put an extra backslash to a line that ends with a '|' (i.e. the upstream of a pipe). Signed-off-by: Junio C Hamano --- t/t1200-tutorial.sh | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index 299e724175..7bd8e06898 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -33,12 +33,12 @@ EOF test_expect_success 'git diff-files -p' ' git diff-files -p > diff.output && - cmp diff.expect diff.output + test_cmp diff.expect diff.output ' test_expect_success 'git diff' ' git diff > diff.output && - cmp diff.expect diff.output + test_cmp diff.expect diff.output ' test_expect_success 'tree' ' @@ -51,12 +51,12 @@ test_expect_success 'git diff-index -p HEAD' ' commit=$(echo "Initial commit" | git commit-tree $tree) && git update-ref HEAD $commit && git diff-index -p HEAD > diff.output && - cmp diff.expect diff.output + test_cmp diff.expect diff.output ' test_expect_success 'git diff HEAD' ' git diff HEAD > diff.output && - cmp diff.expect diff.output + test_cmp diff.expect diff.output ' cat > whatchanged.expect << EOF @@ -83,21 +83,21 @@ index 0000000..557db03 EOF test_expect_success 'git whatchanged -p --root' ' - git whatchanged -p --root | \ + git whatchanged -p --root | sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \ -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \ > whatchanged.output && - cmp whatchanged.expect whatchanged.output + test_cmp whatchanged.expect whatchanged.output ' test_expect_success 'git tag my-first-tag' ' git tag my-first-tag && - cmp .git/refs/heads/master .git/refs/tags/my-first-tag + test_cmp .git/refs/heads/master .git/refs/tags/my-first-tag ' test_expect_success 'git checkout -b mybranch' ' git checkout -b mybranch && - cmp .git/refs/heads/master .git/refs/heads/mybranch + test_cmp .git/refs/heads/master .git/refs/heads/mybranch ' cat > branch.expect < branch.output && - cmp branch.expect branch.output + test_cmp branch.expect branch.output ' test_expect_success 'git resolve now fails' ' @@ -144,7 +144,7 @@ test_expect_success 'git show-branch' ' git commit -m "Merge work in mybranch" -i hello && git show-branch --topo-order --more=1 master mybranch \ > show-branch.output && - cmp show-branch.expect show-branch.output + test_cmp show-branch.expect show-branch.output ' cat > resolve.expect << EOF @@ -157,9 +157,9 @@ EOF test_expect_success 'git resolve' ' git checkout mybranch && - git merge -m "Merge upstream changes." master | \ + git merge -m "Merge upstream changes." master | sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" >resolve.output && - cmp resolve.expect resolve.output + test_cmp resolve.expect resolve.output ' cat > show-branch2.expect << EOF @@ -171,7 +171,7 @@ EOF test_expect_success 'git show-branch (part 2)' ' git show-branch --topo-order master mybranch > show-branch2.output && - cmp show-branch2.expect show-branch2.output + test_cmp show-branch2.expect show-branch2.output ' cat > show-branch3.expect << EOF @@ -186,7 +186,7 @@ EOF test_expect_success 'git show-branch (part 3)' ' git show-branch --topo-order --more=2 master mybranch \ > show-branch3.output && - cmp show-branch3.expect show-branch3.output + test_cmp show-branch3.expect show-branch3.output ' test_expect_success 'rewind to "Some fun." and "Some work."' ' @@ -207,7 +207,7 @@ EOF test_expect_success 'git show-branch (part 4)' ' git show-branch --topo-order > show-branch4.output && - cmp show-branch4.expect show-branch4.output + test_cmp show-branch4.expect show-branch4.output ' test_expect_success 'manual merge' ' @@ -227,7 +227,7 @@ EOF test_expect_success 'git ls-files --stage' ' git ls-files --stage > ls-files.output && - cmp ls-files.expect ls-files.output + test_cmp ls-files.expect ls-files.output ' cat > ls-files-unmerged.expect << EOF @@ -238,7 +238,7 @@ EOF test_expect_success 'git ls-files --unmerged' ' git ls-files --unmerged > ls-files-unmerged.output && - cmp ls-files-unmerged.expect ls-files-unmerged.output + test_cmp ls-files-unmerged.expect ls-files-unmerged.output ' test_expect_success 'git-merge-index' ' @@ -247,7 +247,7 @@ test_expect_success 'git-merge-index' ' test_expect_success 'git ls-files --stage (part 2)' ' git ls-files --stage > ls-files.output2 && - cmp ls-files.expect ls-files.output2 + test_cmp ls-files.expect ls-files.output2 ' test_expect_success 'git repack' 'git repack' From 5c5dd6e5a42d8d65362a15fb8f54f11ae6e03e22 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 6 Nov 2009 22:58:14 -0800 Subject: [PATCH 250/884] t1200: prepare for merging with Fast-forward bikeshedding A tree-wide bikeshedding to replace "fast forward" into "fast-forward" is in 'master'. Since we want to keep this "test modernization" series mergeable also to the maintenance track, we would need to tweak the test to accept both old spellings and new spellings. Sigh... This kind of headache is the primary reason we try not to allow such a tree-wide bike-shedding, but the damage has already been done. Signed-off-by: Junio C Hamano --- t/t1200-tutorial.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index 7bd8e06898..6bf84755f3 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -149,7 +149,7 @@ test_expect_success 'git show-branch' ' cat > resolve.expect << EOF Updating VARIABLE..VARIABLE -Fast forward (no commit created; -m option ignored) +FASTFORWARD (no commit created; -m option ignored) example | 1 + hello | 1 + 2 files changed, 2 insertions(+), 0 deletions(-) @@ -158,7 +158,8 @@ EOF test_expect_success 'git resolve' ' git checkout mybranch && git merge -m "Merge upstream changes." master | - sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" >resolve.output && + sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" \ + -e "s/^Fast[- ]forward /FASTFORWARD /" >resolve.output && test_cmp resolve.expect resolve.output ' From e5138436ddd4b5f75c1e910f6b844e4fcf91343d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 6 Nov 2009 23:06:06 -0800 Subject: [PATCH 251/884] builtin-commit.c: fix logic to omit empty line before existing footers "commit -s" used to add an empty line before adding S-o-b line only when the last line of the existing log message is not another S-o-b line, but c1e01b0 (commit: More generous accepting of RFC-2822 footer lines., 2009-10-28) introduced logic to omit this empty line when the message ends with a run of "footer" lines, to cover S-o-b's friends, e.g. Acked-by. However, the logic was overzealous and missed one corner case. A message that consists of a single line that begins with Token + colon, it can be mistaken as a S-o-b's friend. We do want an empty line in such a case. Signed-off-by: Junio C Hamano --- builtin-commit.c | 2 +- t/t7502-commit.sh | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/builtin-commit.c b/builtin-commit.c index c395cbf14f..cfa6b06e92 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -530,7 +530,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--) ; /* do nothing */ if (prefixcmp(sb.buf + i, sob.buf)) { - if (!ends_rfc2822_footer(&sb)) + if (!i || !ends_rfc2822_footer(&sb)) strbuf_addch(&sb, '\n'); strbuf_addbuf(&sb, &sob); } diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index 56cd866019..fe94552296 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -258,4 +258,13 @@ test_expect_success 'Hand committing of a redundant merge removes dups' ' ' +test_expect_success 'A single-liner subject with a token plus colon is not a footer' ' + + git reset --hard && + git commit -s -m "hello: kitty" --allow-empty && + git cat-file commit HEAD | sed -e "1,/^$/d" >actual && + test $(wc -l Date: Thu, 5 Nov 2009 11:57:57 +0100 Subject: [PATCH 252/884] pre-commit.sample: Diff against the empty tree when HEAD is invalid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was already the case for the old "diff --check" call, but the new one that checks whether there are any non-ascii file names was missing it, making that check fail for root commits. Signed-off-by: Björn Steinbrink Signed-off-by: Junio C Hamano --- templates/hooks--pre-commit.sample | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/templates/hooks--pre-commit.sample b/templates/hooks--pre-commit.sample index 043970a751..439eefda51 100755 --- a/templates/hooks--pre-commit.sample +++ b/templates/hooks--pre-commit.sample @@ -7,6 +7,14 @@ # # To enable this hook, rename this file to "pre-commit". +if git-rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) @@ -17,7 +25,7 @@ if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. - test "$(git diff --cached --name-only --diff-filter=A -z | + test "$(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0')" then echo "Error: Attempt to add a non-ascii file name." @@ -35,12 +43,4 @@ then exit 1 fi -if git-rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 -fi - exec git diff-index --check --cached $against -- From 32ca42491246eb00d226826039fff18d58b57081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sun, 8 Nov 2009 02:04:21 +0100 Subject: [PATCH 253/884] log --format: don't ignore %w() at the start of format string This fixes e.g. --format='%w(72)%s'. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- pretty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pretty.c b/pretty.c index 91be0ce0a0..5e9d1f84b9 100644 --- a/pretty.c +++ b/pretty.c @@ -618,7 +618,7 @@ static void rewrap_message_tail(struct strbuf *sb, if (c->width == new_width && c->indent1 == new_indent1 && c->indent2 == new_indent2) return; - if (c->wrap_start && c->wrap_start < sb->len) + if (c->wrap_start < sb->len) strbuf_wrap(sb, c->wrap_start, c->width, c->indent1, c->indent2); c->wrap_start = sb->len; c->width = new_width; From 1d46f2ea143534e46e6bfee3f34cd90b734bfe80 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 4 Nov 2009 02:19:40 -0500 Subject: [PATCH 254/884] format-patch: make "-p" suppress diffstat Once upon a time, format-patch would use its default stat plus patch format only when no diff format was given on the command line. This meant that "format-patch -p" would suppress the stat and show just the patch. Commit 68daa64 changed this to keep the stat format when we had an "implicit" patch format, like "-U5". As a side effect, this meant that an explicit patch format was now ignored (because cmd_format_patch didn't know the reason that the format was set way down in diff_opt_parse). This patch unbreaks what 68daa64 did (while still preserving what 68daa64 was trying to do), reinstating "-p" to suppress the default behavior. We do this by parsing "-p" ourselves in format-patch, and noting whether it was used explicitly. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-log.c | 9 +++++++-- t/t4014-format-patch.sh | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/builtin-log.c b/builtin-log.c index 25e21ed415..7b91c91423 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -891,6 +891,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct patch_ids ids; char *add_signoff = NULL; struct strbuf buf = STRBUF_INIT; + int use_patch_format = 0; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, "use [PATCH n/m] even with a single patch", @@ -920,6 +921,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback }, OPT_BOOLEAN(0, "no-binary", &no_binary_diff, "don't output binary diffs"), + OPT_BOOLEAN('p', NULL, &use_patch_format, + "show patch format instead of default (patch + stat)"), OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream, "don't include a patch matching a commit upstream"), OPT_GROUP("Messaging"), @@ -1027,8 +1030,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (argc > 1) die ("unrecognized argument: %s", argv[1]); - if (!rev.diffopt.output_format - || rev.diffopt.output_format == DIFF_FORMAT_PATCH) + if (use_patch_format) + rev.diffopt.output_format |= DIFF_FORMAT_PATCH; + else if (!rev.diffopt.output_format || + rev.diffopt.output_format == DIFF_FORMAT_PATCH) rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH; if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff) diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 531f5b795c..cab6ce2e97 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -455,6 +455,27 @@ test_expect_success 'format-patch respects -U' ' ' +cat > expect << EOF + +diff --git a/file b/file +index 40f36c6..2dc5c23 100644 +--- a/file ++++ b/file +@@ -14,3 +14,19 @@ C + D + E + F ++5 +EOF + +test_expect_success 'format-patch -p suppresses stat' ' + + git format-patch -p -2 && + sed -e "1,/^$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output && + test_cmp expect output + +' + test_expect_success 'format-patch from a subdirectory (1)' ' filename=$( rm -rf sub && From f9bbaa384efd4c5c56b9a13d08680f6fc09324ca Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 8 Nov 2009 16:07:16 -0600 Subject: [PATCH 255/884] Add intermediate build products to .gitignore Temporaries such as configure.ac+ and Documentation/*.xml+ sometimes remain after an interrupted build. Tell git not to track them. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 51a37b1af7..f0d2e96b88 100644 --- a/.gitignore +++ b/.gitignore @@ -168,6 +168,7 @@ git.spec *.exe *.[aos] *.py[co] +*+ config.mak autom4te.cache config.cache From 035b76b03f63e077e3465a99bee5dffa29822344 Mon Sep 17 00:00:00 2001 From: Ramsay Jones Date: Tue, 27 Oct 2009 19:11:55 +0000 Subject: [PATCH 256/884] Makefile: merge two Cygwin configuration sections into one Signed-off-by: Ramsay Jones Signed-off-by: Junio C Hamano --- Makefile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index fea237bc80..8e1cfc556f 100644 --- a/Makefile +++ b/Makefile @@ -782,6 +782,8 @@ ifeq ($(uname_O),Cygwin) NO_MMAP = YesPlease NO_IPV6 = YesPlease X = .exe + COMPAT_OBJS += compat/cygwin.o + UNRELIABLE_FSTAT = UnfortunatelyYes endif ifeq ($(uname_S),FreeBSD) NEEDS_LIBICONV = YesPlease @@ -891,10 +893,6 @@ ifeq ($(uname_S),HP-UX) NO_SYS_SELECT_H = YesPlease SNPRINTF_RETURNS_BOGUS = YesPlease endif -ifneq (,$(findstring CYGWIN,$(uname_S))) - COMPAT_OBJS += compat/cygwin.o - UNRELIABLE_FSTAT = UnfortunatelyYes -endif ifdef MSVC GIT_VERSION := $(GIT_VERSION).MSVC pathsep = ; From d691d84eedc0f02b4caebbee89149fff18e1db91 Mon Sep 17 00:00:00 2001 From: Ramsay Jones Date: Sat, 7 Nov 2009 20:08:01 +0000 Subject: [PATCH 257/884] Makefile: keep MSVC and Cygwin configuration separate Signed-off-by: Ramsay Jones Signed-off-by: Junio C Hamano --- Makefile | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 8e1cfc556f..306ca8611c 100644 --- a/Makefile +++ b/Makefile @@ -212,6 +212,12 @@ uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not') uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not') +ifdef MSVC + # avoid the MingW and Cygwin configuration sections + uname_S := Windows + uname_O := Windows +endif + # CFLAGS and LDFLAGS are for the users to override from the command line. CFLAGS = -g -O2 -Wall @@ -893,7 +899,7 @@ ifeq ($(uname_S),HP-UX) NO_SYS_SELECT_H = YesPlease SNPRINTF_RETURNS_BOGUS = YesPlease endif -ifdef MSVC +ifeq ($(uname_S),Windows) GIT_VERSION := $(GIT_VERSION).MSVC pathsep = ; NO_PREAD = YesPlease @@ -945,7 +951,7 @@ else BASIC_CFLAGS += -Zi -MTd endif X = .exe -else +endif ifneq (,$(findstring MINGW,$(uname_S))) pathsep = ; NO_PREAD = YesPlease @@ -994,7 +1000,6 @@ else NO_PTHREADS = YesPlease endif endif -endif -include config.mak.autogen -include config.mak From b1b952043f8f909649fdf053c371109c84f9cf56 Mon Sep 17 00:00:00 2001 From: Ramsay Jones Date: Sat, 7 Nov 2009 20:10:31 +0000 Subject: [PATCH 258/884] MSVC: Add support for building with NO_MMAP When the NO_MMAP build variable is set, the msvc linker complains: error LNK2001: unresolved external symbol _getpagesize The msvc libraries do not define the getpagesize() function, so we move the mingw_getpagesize() implementation from the conditionally built win32mmap.c file to mingw.c. Signed-off-by: Ramsay Jones Signed-off-by: Junio C Hamano --- compat/mingw.c | 12 ++++++++++++ compat/mingw.h | 2 +- compat/win32mmap.c | 12 ------------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 6b5b5b2c70..15fe33eaa0 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1000,6 +1000,18 @@ repeat: return -1; } +/* + * Note that this doesn't return the actual pagesize, but + * the allocation granularity. If future Windows specific git code + * needs the real getpagesize function, we need to find another solution. + */ +int mingw_getpagesize(void) +{ + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwAllocationGranularity; +} + struct passwd *getpwuid(int uid) { static char user_name[100]; diff --git a/compat/mingw.h b/compat/mingw.h index 5b5258bceb..26c402733e 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -166,7 +166,7 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz); int mingw_rename(const char*, const char*); #define rename mingw_rename -#ifdef USE_WIN32_MMAP +#if defined(USE_WIN32_MMAP) || defined(_MSC_VER) int mingw_getpagesize(void); #define getpagesize mingw_getpagesize #endif diff --git a/compat/win32mmap.c b/compat/win32mmap.c index 779d796cd5..1c5a14922f 100644 --- a/compat/win32mmap.c +++ b/compat/win32mmap.c @@ -1,17 +1,5 @@ #include "../git-compat-util.h" -/* - * Note that this doesn't return the actual pagesize, but - * the allocation granularity. If future Windows specific git code - * needs the real getpagesize function, we need to find another solution. - */ -int mingw_getpagesize(void) -{ - SYSTEM_INFO si; - GetSystemInfo(&si); - return si.dwAllocationGranularity; -} - void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) { HANDLE hmap; From 69a9cd31b18008cbacfb35406a1bfa17fc1f352a Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Sun, 8 Nov 2009 16:09:47 +0100 Subject: [PATCH 259/884] Documentation: add "Fighting regressions with git bisect" article This patch adds an asciidoc version of the "Fighting regressions with git bisect" article that the author wrote for the Linux-Kongress 2009 (http://www.linux-kongress.org/2009). This paper might be interesting to people who want to learn as much as possible about "git bisect" from a single document. The slides of the related presentation are available at: http://www.linux-kongress.org/2009/slides/fighting_regressions_with_git_bisect_christian_couder.pdf But the Linux Kongress people will not publish this paper online because they print the papers on their UpTimes magazine (http://www.lob.de/isbn/978-3-86541-358-1). But they don't take away the rights of the author (which is very nice), so I have the right to publish it. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- Documentation/Makefile | 1 + Documentation/git-bisect-lk2009.txt | 1358 +++++++++++++++++++++++++++ Documentation/git-bisect.txt | 5 + 3 files changed, 1364 insertions(+) create mode 100644 Documentation/git-bisect-lk2009.txt diff --git a/Documentation/Makefile b/Documentation/Makefile index cd5b4396db..3f599524ea 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -17,6 +17,7 @@ DOC_HTML=$(MAN_HTML) ARTICLES = howto-index ARTICLES += everyday ARTICLES += git-tools +ARTICLES += git-bisect-lk2009 # with their own formatting rules. SP_ARTICLES = howto/revert-branch-rebase howto/using-merge-subtree user-manual API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt))) diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt new file mode 100644 index 0000000000..6b7b2e5497 --- /dev/null +++ b/Documentation/git-bisect-lk2009.txt @@ -0,0 +1,1358 @@ +Fighting regressions with git bisect +==================================== +:Author: Christian Couder +:Email: chriscool@tuxfamily.org +:Date: 2009/11/08 + +Abstract +-------- + +"git bisect" enables software users and developers to easily find the +commit that introduced a regression. We show why it is important to +have good tools to fight regressions. We describe how "git bisect" +works from the outside and the algorithms it uses inside. Then we +explain how to take advantage of "git bisect" to improve current +practices. And we discuss how "git bisect" could improve in the +future. + + +Introduction to "git bisect" +---------------------------- + +Git is a Distributed Version Control system (DVCS) created by Linus +Torvalds and maintained by Junio Hamano. + +In Git like in many other Version Control Systems (VCS), the different +states of the data that is managed by the system are called +commits. And, as VCS are mostly used to manage software source code, +sometimes "interesting" changes of behavior in the software are +introduced in some commits. + +In fact people are specially interested in commits that introduce a +"bad" behavior, called a bug or a regression. They are interested in +these commits because a commit (hopefully) contains a very small set +of source code changes. And it's much easier to understand and +properly fix a problem when you only need to check a very small set of +changes, than when you don't know where look in the first place. + +So to help people find commits that introduce a "bad" behavior, the +"git bisect" set of commands was invented. And it follows of course +that in "git bisect" parlance, commits where the "interesting +behavior" is present are called "bad" commits, while other commits are +called "good" commits. And a commit that introduce the behavior we are +interested in is called a "first bad commit". Note that there could be +more than one "first bad commit" in the commit space we are searching. + +So "git bisect" is designed to help find a "first bad commit". And to +be as efficient as possible, it tries to perform a binary search. + + +Fighting regressions overview +----------------------------- + +Regressions: a big problem +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Regressions are a big problem in the software industry. But it's +difficult to put some real numbers behind that claim. + +There are some numbers about bugs in general, like a NIST study in +2002 <<1>> that said: + +_____________ +Software bugs, or errors, are so prevalent and so detrimental that +they cost the U.S. economy an estimated $59.5 billion annually, or +about 0.6 percent of the gross domestic product, according to a newly +released study commissioned by the Department of Commerce's National +Institute of Standards and Technology (NIST). At the national level, +over half of the costs are borne by software users and the remainder +by software developers/vendors. The study also found that, although +all errors cannot be removed, more than a third of these costs, or an +estimated $22.2 billion, could be eliminated by an improved testing +infrastructure that enables earlier and more effective identification +and removal of software defects. These are the savings associated with +finding an increased percentage (but not 100 percent) of errors closer +to the development stages in which they are introduced. Currently, +over half of all errors are not found until "downstream" in the +development process or during post-sale software use. +_____________ + +And then: + +_____________ +Software developers already spend approximately 80 percent of +development costs on identifying and correcting defects, and yet few +products of any type other than software are shipped with such high +levels of errors. +_____________ + +Eventually the conclusion started with: + +_____________ +The path to higher software quality is significantly improved software +testing. +_____________ + +There are other estimates saying that 80% of the cost related to +software is about maintenance <<2>>. + +Though, according to Wikipedia <<3>>: + +_____________ +A common perception of maintenance is that it is merely fixing +bugs. However, studies and surveys over the years have indicated that +the majority, over 80%, of the maintenance effort is used for +non-corrective actions (Pigosky 1997). This perception is perpetuated +by users submitting problem reports that in reality are functionality +enhancements to the system. +_____________ + +But we can guess that improving on existing software is very costly +because you have to watch out for regressions. At least this would +make the above studies consistent among themselves. + +Of course some kind of software is developed, then used during some +time without being improved on much, and then finally thrown away. In +this case, of course, regressions may not be a big problem. But on the +other hand, there is a lot of big software that is continually +developed and maintained during years or even tens of years by a lot +of people. And as there are often many people who depend (sometimes +critically) on such software, regressions are a really big problem. + +One such software is the linux kernel. And if we look at the linux +kernel, we can see that a lot of time and effort is spent to fight +regressions. The release cycle start with a 2 weeks long merge +window. Then the first release candidate (rc) version is tagged. And +after that about 7 or 8 more rc versions will appear with around one +week between each of them, before the final release. + +The time between the first rc release and the final release is +supposed to be used to test rc versions and fight bugs and especially +regressions. And this time is more than 80% of the release cycle +time. But this is not the end of the fight yet, as of course it +continues after the release. + +And then this is what Ingo Molnar (a well known linux kernel +developer) says about his use of git bisect: + +_____________ +I most actively use it during the merge window (when a lot of trees +get merged upstream and when the influx of bugs is the highest) - and +yes, there have been cases that i used it multiple times a day. My +average is roughly once a day. +_____________ + +So regressions are fought all the time by developers, and indeed it is +well known that bugs should be fixed as soon as possible, so as soon +as they are found. That's why it is interesting to have good tools for +this purpose. + +Other tools to fight regressions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +So what are the tools used to fight regressions? They are nearly the +same as those used to fight regular bugs. The only specific tools are +test suites and tools similar as "git bisect". + +Test suites are very nice. But when they are used alone, they are +supposed to be used so that all the tests are checked after each +commit. This means that they are not very efficient, because many +tests are run for no interesting result, and they suffer from +combinational explosion. + +In fact the problem is that big software often has many different +configuration options and that each test case should pass for each +configuration after each commit. So if you have for each release: N +configurations, M commits and T test cases, you should perform: + +------------- +N * M * T tests +------------- + +where N, M and T are all growing with the size your software. + +So very soon it will not be possible to completely test everything. + +And if some bugs slip through your test suite, then you can add a test +to your test suite. But if you want to use your new improved test +suite to find where the bug slipped in, then you will either have to +emulate a bisection process or you will perhaps bluntly test each +commit backward starting from the "bad" commit you have which may be +very wasteful. + +"git bisect" overview +--------------------- + +Starting a bisection +~~~~~~~~~~~~~~~~~~~~ + +The first "git bisect" subcommand to use is "git bisect start" to +start the search. Then bounds must be set to limit the commit +space. This is done usually by giving one "bad" and at least one +"good" commit. They can be passed in the initial call to "git bisect +start" like this: + +------------- +$ git bisect start [BAD [GOOD...]] +------------- + +or they can be set using: + +------------- +$ git bisect bad [COMMIT] +------------- + +and: + +------------- +$ git bisect good [COMMIT...] +------------- + +where BAD, GOOD and COMMIT are all names that can be resolved to a +commit. + +Then "git bisect" will checkout a commit of its choosing and ask the +user to test it, like this: + +------------- +$ git bisect start v2.6.27 v2.6.25 +Bisecting: 10928 revisions left to test after this (roughly 14 steps) +[2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: clean up using max_low_pfn on 32-bit +------------- + +Note that the example that we will use is really a toy example, we +will be looking for the first commit that has a version like +"2.6.26-something", that is the commit that has a "SUBLEVEL = 26" line +in the top level Makefile. This is a toy example because there are +better ways to find this commit with git than using "git bisect" (for +example "git blame" or "git log -S"). + +Driving a bisection manually +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +At this point there are basically 2 ways to drive the search. It can +be driven manually by the user or it can be driven automatically by a +script or a command. + +If the user is driving it, then at each step of the search, the user +will have to test the current commit and say if it is "good" or "bad" +using the "git bisect good" or "git bisect bad" commands respectively +that have been described above. For example: + +------------- +$ git bisect bad +Bisecting: 5480 revisions left to test after this (roughly 13 steps) +[66c0b394f08fd89236515c1c84485ea712a157be] KVM: kill file->f_count abuse in kvm +------------- + +And after a few more steps like that, "git bisect" will eventually +find a first bad commit: + +------------- +$ git bisect bad +2ddcca36c8bcfa251724fe342c8327451988be0d is the first bad commit +commit 2ddcca36c8bcfa251724fe342c8327451988be0d +Author: Linus Torvalds +Date: Sat May 3 11:59:44 2008 -0700 + + Linux 2.6.26-rc1 + +:100644 100644 5cf8258195331a4dbdddff08b8d68642638eea57 4492984efc09ab72ff6219a7bc21fb6a957c4cd5 M Makefile +------------- + +At this point we can see what the commit does, check it out (if it's +not already checked out) or tinker with it, for example: + +------------- +$ git show HEAD +commit 2ddcca36c8bcfa251724fe342c8327451988be0d +Author: Linus Torvalds +Date: Sat May 3 11:59:44 2008 -0700 + + Linux 2.6.26-rc1 + +diff --git a/Makefile b/Makefile +index 5cf8258..4492984 100644 +--- a/Makefile ++++ b/Makefile +@@ -1,7 +1,7 @@ + VERSION = 2 + PATCHLEVEL = 6 +-SUBLEVEL = 25 +-EXTRAVERSION = ++SUBLEVEL = 26 ++EXTRAVERSION = -rc1 + NAME = Funky Weasel is Jiggy wit it + + # *DOCUMENTATION* +------------- + +And when we are finished we can use "git bisect reset" to go back to +the branch we were in before we started bisecting: + +------------- +$ git bisect reset +Checking out files: 100% (21549/21549), done. +Previous HEAD position was 2ddcca3... Linux 2.6.26-rc1 +Switched to branch 'master' +------------- + +Driving a bisection automatically +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The other way to drive the bisection process is to tell "git bisect" +to launch a script or command at each bisection step to know if the +current commit is "good" or "bad". To do that, we use the "git bisect +run" command. For example: + +------------- +$ git bisect start v2.6.27 v2.6.25 +Bisecting: 10928 revisions left to test after this (roughly 14 steps) +[2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: clean up using max_low_pfn on 32-bit +$ +$ git bisect run grep '^SUBLEVEL = 25' Makefile +running grep ^SUBLEVEL = 25 Makefile +Bisecting: 5480 revisions left to test after this (roughly 13 steps) +[66c0b394f08fd89236515c1c84485ea712a157be] KVM: kill file->f_count abuse in kvm +running grep ^SUBLEVEL = 25 Makefile +SUBLEVEL = 25 +Bisecting: 2740 revisions left to test after this (roughly 12 steps) +[671294719628f1671faefd4882764886f8ad08cb] V4L/DVB(7879): Adding cx18 Support for mxl5005s +... +... +running grep ^SUBLEVEL = 25 Makefile +Bisecting: 0 revisions left to test after this (roughly 0 steps) +[2ddcca36c8bcfa251724fe342c8327451988be0d] Linux 2.6.26-rc1 +running grep ^SUBLEVEL = 25 Makefile +2ddcca36c8bcfa251724fe342c8327451988be0d is the first bad commit +commit 2ddcca36c8bcfa251724fe342c8327451988be0d +Author: Linus Torvalds +Date: Sat May 3 11:59:44 2008 -0700 + + Linux 2.6.26-rc1 + +:100644 100644 5cf8258195331a4dbdddff08b8d68642638eea57 4492984efc09ab72ff6219a7bc21fb6a957c4cd5 M Makefile +bisect run success +------------- + +In this example, we passed "grep '^SUBLEVEL = 25' Makefile" as +parameter to "git bisect run". This means that at each step, the grep +command we passed will be launched. And if it exits with code 0 (that +means success) then git bisect will mark the current state as +"good". If it exits with code 1 (or any code between 1 and 127 +included, except the special code 125), then the current state will be +marked as "bad". + +Exit code between 128 and 255 are special to "git bisect run". They +make it stop immediately the bisection process. This is useful for +example if the command passed takes too long to complete, because you +can kill it with a signal and it will stop the bisection process. + +It can also be useful in scripts passed to "git bisect run" to "exit +255" if some very abnormal situation is detected. + +Avoiding untestable commits +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes it happens that the current state cannot be tested, for +example if it does not compile because there was a bug preventing it +at that time. This is what the special exit code 125 is for. It tells +"git bisect run" that the current commit should be marked as +untestable and that another one should be chosen and checked out. + +If the bisection process is driven manually, you can use "git bisect +skip" to do the same thing. (In fact the special exit code 125 makes +"git bisect run" use "git bisect skip" in the background.) + +Or if you want more control, you can inspect the current state using +for example "git bisect visualize". It will launch gitk (or "git log" +if the DISPLAY environment variable is not set) to help you find a +better bisection point. + +Either way, if you have a string of untestable commits, it might +happen that the regression you are looking for has been introduced by +one of these untestable commits. In this case it's not possible to +tell for sure which commit introduced the regression. + +So if you used "git bisect skip" (or the run script exited with +special code 125) you could get a result like this: + +------------- +There are only 'skip'ped commits left to test. +The first bad commit could be any of: +15722f2fa328eaba97022898a305ffc8172db6b1 +78e86cf3e850bd755bb71831f42e200626fbd1e0 +e15b73ad3db9b48d7d1ade32f8cd23a751fe0ace +070eab2303024706f2924822bfec8b9847e4ac1b +We cannot bisect more! +------------- + +Saving a log and replaying it +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to show other people your bisection process, you can get a +log using for example: + +------------- +$ git bisect log > bisect_log.txt +------------- + +And it is possible to replay it using: + +------------- +$ git bisect replay bisect_log.txt +------------- + + +"git bisect" details +-------------------- + +Bisection algorithm +~~~~~~~~~~~~~~~~~~~ + +As the Git commits form a directed acyclic graph (DAG), finding the +best bisection commit to test at each step is not so simple. Anyway +Linus found and implemented a "truly stupid" algorithm, later improved +by Junio Hamano, that works quite well. + +So the algorithm used by "git bisect" to find the best bisection +commit when there are no skipped commits is the following: + +1) keep only the commits that: + +a) are ancestor of the "bad" commit (including the "bad" commit itself), +b) are not ancestor of a "good" commit (excluding the "good" commits). + +This means that we get rid of the uninteresting commits in the DAG. + +For example if we start with a graph like this: + +------------- +G-Y-G-W-W-W-X-X-X-X + \ / + W-W-B + / +Y---G-W---W + \ / \ +Y-Y X-X-X-X + +-> time goes this way -> +------------- + +where B is the "bad" commit, "G" are "good" commits and W, X, and Y +are other commits, we will get the following graph after this first +step: + +------------- +W-W-W + \ + W-W-B + / +W---W +------------- + +So only the W and B commits will be kept. Because commits X and Y will +have been removed by rules a) and b) respectively, and because commits +G are removed by rule b) too. + +Note for git users, that it is equivalent as keeping only the commit +given by: + +------------- +git rev-list BAD --not GOOD1 GOOD2... +------------- + +Also note that we don't require the commits that are kept to be +descendants of a "good" commit. So in the following example, commits W +and Z will be kept: + +------------- +G-W-W-W-B + / +Z-Z +------------- + +2) starting from the "good" ends of the graph, associate to each +commit the number of ancestors it has plus one + +For example with the following graph where H is the "bad" commit and A +and D are some parents of some "good" commits: + +------------- +A-B-C + \ + F-G-H + / +D---E +------------- + +this will give: + +------------- +1 2 3 +A-B-C + \6 7 8 + F-G-H +1 2/ +D---E +------------- + +3) associate to each commit: min(X, N - X) + +where X is the value associated to the commit in step 2) and N is the +total number of commits in the graph. + +In the above example we have N = 8, so this will give: + +------------- +1 2 3 +A-B-C + \2 1 0 + F-G-H +1 2/ +D---E +------------- + +4) the best bisection point is the commit with the highest associated +number + +So in the above example the best bisection point is commit C. + +5) note that some shortcuts are implemented to speed up the algorithm + +As we know N from the beginning, we know that min(X, N - X) can't be +greater than N/2. So during steps 2) and 3), if we would associate N/2 +to a commit, then we know this is the best bisection point. So in this +case we can just stop processing any other commit and return the +current commit. + +Bisection algorithm debugging +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For any commit graph, you can see the number associated with each +commit using "git rev-list --bisect-all". + +For example, for the above graph, a command like: + +------------- +$ git rev-list --bisect-all BAD --not GOOD1 GOOD2 +------------- + +would output something like: + +------------- +e15b73ad3db9b48d7d1ade32f8cd23a751fe0ace (dist=3) +15722f2fa328eaba97022898a305ffc8172db6b1 (dist=2) +78e86cf3e850bd755bb71831f42e200626fbd1e0 (dist=2) +a1939d9a142de972094af4dde9a544e577ddef0e (dist=2) +070eab2303024706f2924822bfec8b9847e4ac1b (dist=1) +a3864d4f32a3bf5ed177ddef598490a08760b70d (dist=1) +a41baa717dd74f1180abf55e9341bc7a0bb9d556 (dist=1) +9e622a6dad403b71c40979743bb9d5be17b16bd6 (dist=0) +------------- + +Bisection algorithm discussed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +First let's define "best bisection point". We will say that a commit X +is a best bisection point or a best bisection commit if knowing its +state ("good" or "bad") gives as much information as possible whether +the state of the commit happens to be "good" or "bad". + +This means that the best bisection commits are the commits where the +following function is maximum: + +------------- +f(X) = min(information_if_good(X), information_if_bad(X)) +------------- + +where information_if_good(X) is the information we get if X is good +and information_if_bad(X) is the information we get if X is bad. + +Now we will suppose that there is only one "first bad commit". This +means that all its descendants are "bad" and all the other commits are +"good". And we will suppose that all commits have an equal probability +of being good or bad, or of being the first bad commit, so knowing the +state of c commits gives always the same amount of information +wherever these c commits are on the graph and whatever c is. (So we +suppose that these commits being for example on a branch or near a +good or a bad commit does not give more or less information). + +Let's also suppose that we have a cleaned up graph like one after step +1) in the bisection algorithm above. This means that we can measure +the information we get in terms of number of commit we can remove from +the graph.. + +And let's take a commit X in the graph. + +If X is found to be "good", then we know that its ancestors are all +"good", so we want to say that: + +------------- +information_if_good(X) = number_of_ancestors(X) (TRUE) +------------- + +And this is true because at step 1) b) we remove the ancestors of the +"good" commits. + +If X is found to be "bad", then we know that its descendants are all +"bad", so we want to say that: + +------------- +information_if_bad(X) = number_of_descendants(X) (WRONG) +------------- + +But this is wrong because at step 1) a) we keep only the ancestors of +the bad commit. So we get more information when a commit is marked as +"bad", because we also know that the ancestors of the previous "bad" +commit that are not ancestors of the new "bad" commit are not the +first bad commit. We don't know if they are good or bad, but we know +that they are not the first bad commit because they are not ancestor +of the new "bad" commit. + +So when a commit is marked as "bad" we know we can remove all the +commits in the graph except those that are ancestors of the new "bad" +commit. This means that: + +------------- +information_if_bad(X) = N - number_of_ancestors(X) (TRUE) +------------- + +where N is the number of commits in the (cleaned up) graph. + +So in the end this means that to find the best bisection commits we +should maximize the function: + +------------- +f(X) = min(number_of_ancestors(X), N - number_of_ancestors(X)) +------------- + +And this is nice because at step 2) we compute number_of_ancestors(X) +and so at step 3) we compute f(X). + +Let's take the following graph as an example: + +------------- + G-H-I-J + / \ +A-B-C-D-E-F O + \ / + K-L-M-N +------------- + +If we compute the following non optimal function on it: + +------------- +g(X) = min(number_of_ancestors(X), number_of_descendants(X)) +------------- + +we get: + +------------- + 4 3 2 1 + G-H-I-J +1 2 3 4 5 6/ \0 +A-B-C-D-E-F O + \ / + K-L-M-N + 4 3 2 1 +------------- + +but with the algorithm used by git bisect we get: + +------------- + 7 7 6 5 + G-H-I-J +1 2 3 4 5 6/ \0 +A-B-C-D-E-F O + \ / + K-L-M-N + 7 7 6 5 +------------- + +So we chose G, H, K or L as the best bisection point, which is better +than F. Because if for example L is bad, then we will know not only +that L, M and N are bad but also that G, H, I and J are not the first +bad commit (since we suppose that there is only one first bad commit +and it must be an ancestor of L). + +So the current algorithm seems to be the best possible given what we +initially supposed. + +Skip algorithm +~~~~~~~~~~~~~~ + +When some commits have been skipped (using "git bisect skip"), then +the bisection algorithm is the same for step 1) to 3). But then we use +roughly the following steps: + +6) sort the commit by decreasing associated value + +7) if the first commit has not been skipped, we can return it and stop +here + +8) otherwise filter out all the skipped commits in the sorted list + +9) use a pseudo random number generator (PRNG) to generate a random +number between 0 and 1 + +10) multiply this random number with its square root to bias it toward +0 + +11) multiply the result by the number of commits in the filtered list +to get an index into this list + +12) return the commit at the computed index + +Skip algorithm discussed +~~~~~~~~~~~~~~~~~~~~~~~~ + +After step 7) (in the skip algorithm), we could check if the second +commit has been skipped and return it if it is not the case. And in +fact that was the algorithm we used from when "git bisect skip" was +developed in git version 1.5.4 (released on February 1st 2008) until +git version 1.6.4 (released July 29th 2009). + +But Ingo Molnar and H. Peter Anvin (another well known linux kernel +developer) both complained that sometimes the best bisection points +all happened to be in an area where all the commits are +untestable. And in this case the user was asked to test many +untestable commits, which could be very inefficient. + +Indeed untestable commits are often untestable because a breakage was +introduced at one time, and that breakage was fixed only after many +other commits were introduced. + +This breakage is of course most of the time unrelated to the breakage +we are trying to locate in the commit graph. But it prevents us to +know if the interesting "bad behavior" is present or not. + +So it is a fact that commits near an untestable commit have a high +probability of being untestable themselves. And the best bisection +commits are often found together too (due to the bisection algorithm). + +This is why it is a bad idea to just chose the next best unskipped +bisection commit when the first one has been skipped. + +We found that most commits on the graph may give quite a lot of +information when they are tested. And the commits that will not on +average give a lot of information are the one near the good and bad +commits. + +So using a PRNG with a bias to favor commits away from the good and +bad commits looked like a good choice. + +One obvious improvement to this algorithm would be to look for a +commit that has an associated value near the one of the best bisection +commit, and that is on another branch, before using the PRNG. Because +if such a commit exists, then it is not very likely to be untestable +too, so it will probably give more information than a nearly randomly +chosen one. + +Checking merge bases +~~~~~~~~~~~~~~~~~~~~ + +There is another tweak in the bisection algorithm that has not been +described in the "bisection algorithm" above. + +We supposed in the previous examples that the "good" commits were +ancestors of the "bad" commit. But this is not a requirement of "git +bisect". + +Of course the "bad" commit cannot be an ancestor of a "good" commit, +because the ancestors of the good commits are supposed to be +"good". And all the "good" commits must be related to the bad commit. +They cannot be on a branch that has no link with the branch of the +"bad" commit. But it is possible for a good commit to be related to a +bad commit and yet not be neither one of its ancestor nor one of its +descendants. + +For example, there can be a "main" branch, and a "dev" branch that was +forked of the main branch at a commit named "D" like this: + +------------- +A-B-C-D-E-F-G <--main + \ + H-I-J <--dev +------------- + +The commit "D" is called a "merge base" for branch "main" and "dev" +because it's the best common ancestor for these branches for a merge. + +Now let's suppose that commit J is bad and commit G is good and that +we apply the bisection algorithm like it has been previously +described. + +As described in step 1) b) of the bisection algorithm, we remove all +the ancestors of the good commits because they are supposed to be good +too. + +So we would be left with only: + +------------- +H-I-J +------------- + +But what happens if the first bad commit is "B" and if it has been +fixed in the "main" branch by commit "F"? + +The result of such a bisection would be that we would find that H is +the first bad commit, when in fact it's B. So that would be wrong! + +And yes it's can happen in practice that people working on one branch +are not aware that people working on another branch fixed a bug! It +could also happen that F fixed more than one bug or that it is a +revert of some big development effort that was not ready to be +released. + +In fact development teams often maintain both a development branch and +a maintenance branch, and it would be quite easy for them if "git +bisect" just worked when they want to bisect a regression on the +development branch that is not on the maintenance branch. They should +be able to start bisecting using: + +------------- +$ git bisect start dev main +------------- + +To enable that additional nice feature, when a bisection is started +and when some good commits are not ancestors of the bad commit, we +first compute the merge bases between the bad and the good commits and +we chose these merge bases as the first commits that will be checked +out and tested. + +If it happens that one merge base is bad, then the bisection process +is stopped with a message like: + +------------- +The merge base BBBBBB is bad. +This means the bug has been fixed between BBBBBB and [GGGGGG,...]. +------------- + +where BBBBBB is the sha1 hash of the bad merge base and [GGGGGG,...] +is a comma separated list of the sha1 of the good commits. + +If some of the merge bases are skipped, then the bisection process +continues, but the following message is printed for each skipped merge +base: + +------------- +Warning: the merge base between BBBBBB and [GGGGGG,...] must be skipped. +So we cannot be sure the first bad commit is between MMMMMM and BBBBBB. +We continue anyway. +------------- + +where BBBBBB is the sha1 hash of the bad commit, MMMMMM is the sha1 +hash of the merge base that is skipped and [GGGGGG,...] is a comma +separated list of the sha1 of the good commits. + +So if there is no bad merge base, the bisection process continues as +usual after this step. + +Best bisecting practices +------------------------ + +Using test suites and git bisect together +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you both have a test suite and use git bisect, then it becomes less +important to check that all tests pass after each commit. Though of +course it is probably a good idea to have some checks to avoid +breaking too many things because it could make bisecting other bugs +more difficult. + +You can focus your efforts to check at a few points (for example rc +and beta releases) that all the T test cases pass for all the N +configurations. And when some tests don't pass you can use "git +bisect" (or better "git bisect run"). So you should perform roughly: + +------------- +c * N * T + b * M * log2(M) tests +------------- + +where c is the number of rounds of test (so a small constant) and b is +the ratio of bug per commit (hopefully a small constant too). + +So of course it's much better as it's O(N \* T) vs O(N \* T \* M) if +you would test everything after each commit. + +This means that test suites are good to prevent some bugs from being +committed and they are also quite good to tell you that you have some +bugs. But they are not so good to tell you where some bugs have been +introduced. To tell you that efficiently, git bisect is needed. + +The other nice thing with test suites, is that when you have one, you +already know how to test for bad behavior. So you can use this +knowledge to create a new test case for "git bisect" when it appears +that there is a regression. So it will be easier to bisect the bug and +fix it. And then you can add the test case you just created to your +test suite. + +So if you know how to create test cases and how to bisect, you will be +subject to a virtuous circle: + +more tests => easier to create tests => easier to bisect => more tests + +So test suites and "git bisect" are complementary tools that are very +powerful and efficient when used together. + +Bisecting build failures +~~~~~~~~~~~~~~~~~~~~~~~~ + +You can very easily automatically bisect broken builds using something +like: + +------------- +$ git bisect start BAD GOOD +$ git bisect run make +------------- + +Passing sh -c "some commands" to "git bisect run" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For example: + +------------- +$ git bisect run sh -c "make || exit 125; ./my_app | grep 'good output'" +------------- + +On the other hand if you do this often, then it can be worth having +scripts to avoid too much typing. + +Finding performance regressions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Here is an example script that comes slightly modified from a real +world script used by Junio Hamano <<4>>. + +This script can be passed to "git bisect run" to find the commit that +introduced a performance regression: + +------------- +#!/bin/sh + +# Build errors are not what I am interested in. +make my_app || exit 255 + +# We are checking if it stops in a reasonable amount of time, so +# let it run in the background... + +./my_app >log 2>&1 & + +# ... and grab its process ID. +pid=$! + +# ... and then wait for sufficiently long. +sleep $NORMAL_TIME + +# ... and then see if the process is still there. +if kill -0 $pid +then + # It is still running -- that is bad. + kill $pid; sleep 1; kill $pid; + exit 1 +else + # It has already finished (the $pid process was no more), + # and we are happy. + exit 0 +fi +------------- + +Following general best practices +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is obviously a good idea not to have commits with changes that +knowingly break things, even if some other commits later fix the +breakage. + +It is also a good idea when using any VCS to have only one small +logical change in each commit. + +The smaller the changes in your commit, the most effective "git +bisect" will be. And you will probably need "git bisect" less in the +first place, as small changes are easier to review even if they are +only reviewed by the commiter. + +Another good idea is to have good commit messages. They can be very +helpful to understand why some changes were made. + +These general best practices are very helpful if you bisect often. + +Avoiding bug prone merges +~~~~~~~~~~~~~~~~~~~~~~~~~ + +First merges by themselves can introduce some regressions even when +the merge needs no source code conflict resolution. This is because a +semantic change can happen in one branch while the other branch is not +aware of it. + +For example one branch can change the semantic of a function while the +other branch add more calls to the same function. + +This is made much worse if many files have to be fixed to resolve +conflicts. That's why such merges are called "evil merges". They can +make regressions very difficult to track down. It can even be +misleading to know the first bad commit if it happens to be such a +merge, because people might think that the bug comes from bad conflict +resolution when it comes from a semantic change in one branch. + +Anyway "git rebase" can be used to linearize history. This can be used +either to avoid merging in the first place. Or it can be used to +bisect on a linear history instead of the non linear one, as this +should give more information in case of a semantic change in one +branch. + +Merges can be also made simpler by using smaller branches or by using +many topic branches instead of only long version related branches. + +And testing can be done more often in special integration branches +like linux-next for the linux kernel. + +Adapting your work-flow +~~~~~~~~~~~~~~~~~~~~~~~ + +A special work-flow to process regressions can give great results. + +Here is an example of a work-flow used by Andreas Ericsson: + +* write, in the test suite, a test script that exposes the regression +* use "git bisect run" to find the commit that introduced it +* fix the bug that is often made obvious by the previous step +* commit both the fix and the test script (and if needed more tests) + +And here is what Andreas said about this work-flow <<5>>: + +_____________ +To give some hard figures, we used to have an average report-to-fix +cycle of 142.6 hours (according to our somewhat weird bug-tracker +which just measures wall-clock time). Since we moved to git, we've +lowered that to 16.2 hours. Primarily because we can stay on top of +the bug fixing now, and because everyone's jockeying to get to fix +bugs (we're quite proud of how lazy we are to let git find the bugs +for us). Each new release results in ~40% fewer bugs (almost certainly +due to how we now feel about writing tests). +_____________ + +Clearly this work-flow uses the virtuous circle between test suites +and "git bisect". In fact it makes it the standard procedure to deal +with regression. + +In other messages Andreas says that they also use the "best practices" +described above: small logical commits, topic branches, no evil +merge,... These practices all improve the bisectability of the commit +graph, by making it easier and more useful to bisect. + +So a good work-flow should be designed around the above points. That +is making bisecting easier, more useful and standard. + +Involving QA people and if possible end users +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +One nice about "git bisect" is that it is not only a developer +tool. It can effectively be used by QA people or even end users (if +they have access to the source code or if they can get access to all +the builds). + +There was a discussion at one point on the linux kernel mailing list +of whether it was ok to always ask end user to bisect, and very good +points were made to support the point of view that it is ok. + +For example David Miller wrote <<6>>: + +_____________ +What people don't get is that this is a situation where the "end node +principle" applies. When you have limited resources (here: developers) +you don't push the bulk of the burden upon them. Instead you push +things out to the resource you have a lot of, the end nodes (here: +users), so that the situation actually scales. +_____________ + +This means that it is often "cheaper" if QA people or end users can do +it. + +What is interesting too is that end users that are reporting bugs (or +QA people that reproduced a bug) have access to the environment where +the bug happens. So they can often more easily reproduce a +regression. And if they can bisect, then more information will be +extracted from the environment where the bug happens, which means that +it will be easier to understand and then fix the bug. + +For open source projects it can be a good way to get more useful +contributions from end users, and to introduce them to QA and +development activities. + +Using complex scripts +~~~~~~~~~~~~~~~~~~~~~ + +In some cases like for kernel development it can be worth developing +complex scripts to be able to fully automate bisecting. + +Here is what Ingo Molnar says about that <<7>>: + +_____________ +i have a fully automated bootup-hang bisection script. It is based on +"git-bisect run". I run the script, it builds and boots kernels fully +automatically, and when the bootup fails (the script notices that via +the serial log, which it continuously watches - or via a timeout, if +the system does not come up within 10 minutes it's a "bad" kernel), +the script raises my attention via a beep and i power cycle the test +box. (yeah, i should make use of a managed power outlet to 100% +automate it) +_____________ + +Combining test suites, git bisect and other systems together +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We have seen that test suites an git bisect are very powerful when +used together. It can be even more powerful if you can combine them +with other systems. + +For example some test suites could be run automatically at night with +some unusual (or even random) configurations. And if a regression is +found by a test suite, then "git bisect" can be automatically +launched, and its result can be emailed to the author of the first bad +commit found by "git bisect", and perhaps other people too. And a new +entry in the bug tracking system could be automatically created too. + + +The future of bisecting +----------------------- + +"git replace" +~~~~~~~~~~~~~ + +We saw earlier that "git bisect skip" is now using a PRNG to try to +avoid areas in the commit graph where commits are untestable. The +problem is that sometimes the first bad commit will be in an +untestable area. + +To simplify the discussion we will suppose that the untestable area is +a simple string of commits and that it was created by a breakage +introduced by one commit (let's call it BBC for bisect breaking +commit) and later fixed by another one (let's call it BFC for bisect +fixing commit). + +For example: + +------------- +...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z-... +------------- + +where we know that Y is good and BFC is bad, and where BBC and X1 to +X6 are untestable. + +In this case if you are bisecting manually, what you can do is create +a special branch that starts just before the BBC. The first commit in +this branch should be the BBC with the BFC squashed into it. And the +other commits in the branch should be the commits between BBC and BFC +rebased on the first commit of the branch and then the commit after +BFC also rebased on. + +For example: + +------------- + (BBC+BFC)-X1'-X2'-X3'-X4'-X5'-X6'-Z' + / +...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z-... +------------- + +where commits quoted with ' have been rebased. + +You can easily create such a branch with Git using interactive rebase. + +For example using: + +------------- +$ git rebase -i Y Z +------------- + +and then moving BFC after BBC and squashing it. + +After that you can start bisecting as usual in the new branch and you +should eventually find the first bad commit. + +For example: + +------------- +$ git bisect start Z' Y +------------- + +If you are using "git bisect run", you can use the same manual fix up +as above, and then start another "git bisect run" in the special +branch. Or as the "git bisect" man page says, the script passed to +"git bisect run" can apply a patch before it compiles and test the +software <<8>>. The patch should turn a current untestable commits +into a testable one. So the testing will result in "good" or "bad" and +"git bisect" will be able to find the first bad commit. And the script +should not forget to remove the patch once the testing is done before +exiting from the script. + +(Note that instead of a patch you can use "git cherry-pick BFC" to +apply the fix, and in this case you should use "git reset --hard +HEAD^" to revert the cherry-pick after testing and before returning +from the script.) + +But the above ways to work around untestable areas are a little bit +clunky. Using special branches is nice because these branches can be +shared by developers like usual branches, but the risk is that people +will get many such branches. And it disrupts the normal "git bisect" +work-flow. So, if you want to use "git bisect run" completely +automatically, you have to add special code in your script to restart +bisection in the special branches. + +Anyway one can notice in the above special branch example that the Z' +and Z commits should point to the same source code state (the same +"tree" in git parlance). That's because Z' result from applying the +same changes as Z just in a slightly different order. + +So if we could just "replace" Z by Z' when we bisect, then we would +not need to add anything to a script. It would just work for anyone in +the project sharing the special branches and the replacements. + +With the example above that would give: + +------------- + (BBC+BFC)-X1'-X2'-X3'-X4'-X5'-X6'-Z'-... + / +...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z +------------- + +That's why the "git replace" command was created. Technically it +stores replacements "refs" in the "refs/replace/" hierarchy. These +"refs" are like branches (that are stored in "refs/heads/") or tags +(that are stored in "refs/tags"), and that means that they can +automatically be shared like branches or tags among developers. + +"git replace" is a very powerful mechanism. It can be used to fix +commits in already released history, for example to change the commit +message or the author. And it can also be used instead of git "grafts" +to link a repository with another old repository. + +In fact it's this last feature that "sold" it to the git community, so +it is now in the "master" branch of git's git repository and it should +be released in git 1.6.5 in October or November 2009. + +One problem with "git replace" is that currently it stores all the +replacements refs in "refs/replace/", but it would be perhaps better +if the replacement refs that are useful only for bisecting would be in +"refs/replace/bisect/". This way the replacement refs could be used +only for bisecting, while other refs directly in "refs/replace/" would +be used nearly all the time. + +Bisecting sporadic bugs +~~~~~~~~~~~~~~~~~~~~~~~ + +Another possible improvement to "git bisect" would be to optionally +add some redundancy to the tests performed so that it would be more +reliable when tracking sporadic bugs. + +This has been requested by some kernel developers because some bugs +called sporadic bugs do not appear in all the kernel builds because +they are very dependent on the compiler output. + +The idea is that every 3 test for example, "git bisect" could ask the +user to test a commit that has already been found to be "good" or +"bad" (because one of its descendants or one of its ancestors has been +found to be "good" or "bad" respectively). If it happens that a commit +has been previously incorrectly classified then the bisection can be +aborted early, hopefully before too many mistakes have been made. Then +the user will have to look at what happened and then restart the +bisection using a fixed bisect log. + +There is already a project called BBChop created by Ealdwulf Wuffinga +on Github that does something like that using Bayesian Search Theory +<<9>>: + +_____________ +BBChop is like 'git bisect' (or equivalent), but works when your bug +is intermittent. That is, it works in the presence of false negatives +(when a version happens to work this time even though it contains the +bug). It assumes that there are no false positives (in principle, the +same approach would work, but adding it may be non-trivial). +_____________ + +But BBChop is independent of any VCS and it would be easier for Git +users to have something integrated in Git. + +Conclusion +---------- + +We have seen that regressions are an important problem, and that "git +bisect" has nice features that complement very well practices and +other tools, especially test suites, that are generally used to fight +regressions. But it might be needed to change some work-flows and +(bad) habits to get the most out of it. + +Some improvements to the algorithms inside "git bisect" are possible +and some new features could help in some cases, but overall "git +bisect" works already very well, is used a lot, and is already very +useful. To back up that last claim, let's give the final word to Ingo +Molnar when he was asked by the author how much time does he think +"git bisect" saves him when he uses it: + +_____________ +a _lot_. + +About ten years ago did i do my first 'bisection' of a Linux patch +queue. That was prior the Git (and even prior the BitKeeper) days. I +literally days spent sorting out patches, creating what in essence +were standalone commits that i guessed to be related to that bug. + +It was a tool of absolute last resort. I'd rather spend days looking +at printk output than do a manual 'patch bisection'. + +With Git bisect it's a breeze: in the best case i can get a ~15 step +kernel bisection done in 20-30 minutes, in an automated way. Even with +manual help or when bisecting multiple, overlapping bugs, it's rarely +more than an hour. + +In fact it's invaluable because there are bugs i would never even +_try_ to debug if it wasn't for git bisect. In the past there were bug +patterns that were immediately hopeless for me to debug - at best i +could send the crash/bug signature to lkml and hope that someone else +can think of something. + +And even if a bisection fails today it tells us something valuable +about the bug: that it's non-deterministic - timing or kernel image +layout dependent. + +So git bisect is unconditional goodness - and feel free to quote that +;-) +_____________ + +Acknowledgements +---------------- + +Many thanks to Junio Hamano for his help in reviewing this paper, for +reviewing the patches I sent to the git mailing list, for discussing +some ideas and helping me improve them, for improving "git bisect" a +lot and for his awesome work in maintaining and developing Git. + +Many thanks to Ingo Molnar for giving me very useful information that +appears in this paper, for commenting on this paper, for his +suggestions to improve "git bisect" and for evangelizing "git bisect" +on the linux kernel mailing lists. + +Many thanks to Linus Torvalds for inventing, developing and +evangelizing "git bisect", Git and Linux. + +Many thanks to the many other great people who helped one way or +another when I worked on git, especially to Andreas Ericsson, Johannes +Schindelin, H. Peter Anvin, Daniel Barkalow, Bill Lear, John Hawley, +Shawn O. Pierce, Jeff King, Sam Vilain, Jon Seymour. + +Many thanks to the Linux-Kongress program committee for choosing the +author to given a talk and for publishing this paper. + +References +---------- + +- [[[1]]] http://www.nist.gov/public_affairs/releases/n02-10.htm['Software Errors Cost U.S. Economy $59.5 Billion Annually'. Nist News Release.] +- [[[2]]] http://java.sun.com/docs/codeconv/html/CodeConventions.doc.html#16712['Code Conventions for the Java Programming Language'. Sun Microsystems.] +- [[[3]]] http://en.wikipedia.org/wiki/Software_maintenance['Software maintenance'. Wikipedia.] +- [[[4]]] http://article.gmane.org/gmane.comp.version-control.git/45195/[Junio C Hamano. 'Automated bisect success story'. Gmane.] +- [[[5]]] http://lwn.net/Articles/317154/[Christian Couder. 'Fully automated bisecting with "git bisect run"'. LWN.net.] +- [[[6]]] http://lwn.net/Articles/277872/[Jonathan Corbet. 'Bisection divides users and developers'. LWN.net.] +- [[[7]]] http://article.gmane.org/gmane.linux.scsi/36652/[Ingo Molnar. 'Re: BUG 2.6.23-rc3 can't see sd partitions on Alpha'. Gmane.] +- [[[8]]] http://www.kernel.org/pub/software/scm/git/docs/git-bisect.html[Junio C Hamano and the git-list. 'git-bisect(1) Manual Page'. Linux Kernel Archives.] +- [[[9]]] http://github.com/Ealdwulf/bbchop[Ealdwulf. 'bbchop'. GitHub.] diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index d2ffae0c10..c39d957c3a 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -330,6 +330,11 @@ Documentation ------------- Documentation by Junio C Hamano and the git-list . +SEE ALSO +-------- +link:git-bisect-lk2009.html[Fighting regressions with git bisect], +linkgit:git-blame[1]. + GIT --- Part of the linkgit:git[1] suite From 3ce9450a810243cbd9d0250d9ae3ea6834f50b9c Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 7 Nov 2009 16:13:28 +0100 Subject: [PATCH 260/884] gitweb: Document current snapshot rules via new tests Add t9502-gitweb-standalone-parse-output test script, which runs gitweb as a CGI script from the commandline and checks that it produces the correct output. Currently this test script contains only tests of snapshot naming (proposed name of snapshot file) and snapshot prefix (prefix of files in the archive / snapshot). It defines and uses 'tar' snapshot format, without compression, for easy checking of snapshot prefix. Testing is done using check_snapshot function. Gitweb uses the following format for snapshot filenames: - where is project name with '.git' or '/.git' suffix stripped, unless '.git' is the whole project name. For snapshot prefix it uses simply: / Disadvantages of current snapshot rules: * There exists convention that . archive unpacks to / directory (/ is prefix of archive). Gitweb does not respect it * Snapshot links generated by gitweb use full SHA-1 id as a value of 'h' / $hash parameter. With current rules it leads to long file names like e.g. repo-1005c80cc11c531d327b12195027cbbb4ff9e3cb.tgz * For handcrafted URLs, where 'h' / $hash parameter is a symbolic 'volatile' revision name such as "HEAD" or "next" snapshot name doesn't tell us what exact version it was created from * Proposed filename in Content-Disposition header should not contain any directory path information, which means that it should not contain '/' (see RFC2183)... which means that snapshot naming is broken for $hash being e.g. hirearchical branch name such as 'xx/test' This would be improved in next commit. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- t/t9502-gitweb-standalone-parse-output.sh | 87 +++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100755 t/t9502-gitweb-standalone-parse-output.sh diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh new file mode 100755 index 0000000000..741187b9e4 --- /dev/null +++ b/t/t9502-gitweb-standalone-parse-output.sh @@ -0,0 +1,87 @@ +#!/bin/sh +# +# Copyright (c) 2009 Mark Rada +# + +test_description='gitweb as standalone script (parsing script output). + +This test runs gitweb (git web interface) as a CGI script from the +commandline, and checks that it produces the correct output, either +in the HTTP header or the actual script output.' + + +. ./gitweb-lib.sh + +# ---------------------------------------------------------------------- +# snapshot file name and prefix + +cat >>gitweb_config.perl <<\EOF + +$known_snapshot_formats{'tar'} = { + 'display' => 'tar', + 'type' => 'application/x-tar', + 'suffix' => '.tar', + 'format' => 'tar', +}; + +$feature{'snapshot'}{'default'} = ['tar']; +EOF + +# Call check_snapshot with the arguments " []" +# +# This will check that gitweb HTTP header contains proposed filename +# as with '.tar' suffix added, and that generated tarfile +# (gitweb message body) has as prefix for al files in tarfile +# +# default to +check_snapshot () { + basename=$1 + prefix=${2:-"$1"} + echo "basename=$basename" + grep "filename=.*$basename.tar" gitweb.headers >/dev/null 2>&1 && + "$TAR" tf gitweb.body >file_list && + ! grep -v "^$prefix/" file_list +} + +test_expect_success setup ' + test_commit first foo && + git branch xx/test && + FULL_ID=$(git rev-parse --verify HEAD) && + SHORT_ID=$(git rev-parse --verify --short=7 HEAD) +' +test_debug ' + echo "FULL_ID = $FULL_ID" + echo "SHORT_ID = $SHORT_ID" +' + +test_expect_success 'snapshot: full sha1' ' + gitweb_run "p=.git;a=snapshot;h=$FULL_ID;sf=tar" && + check_snapshot ".git-$FULL_ID" ".git" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: shortened sha1' ' + gitweb_run "p=.git;a=snapshot;h=$SHORT_ID;sf=tar" && + check_snapshot ".git-$SHORT_ID" ".git" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: HEAD' ' + gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tar" && + check_snapshot ".git-HEAD" ".git" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: short branch name (master)' ' + gitweb_run "p=.git;a=snapshot;h=master;sf=tar" && + check_snapshot ".git-master" ".git" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_failure 'snapshot: hierarchical branch name (xx/test)' ' + gitweb_run "p=.git;a=snapshot;h=xx/test;sf=tar" && + ! grep "filename=.*/" gitweb.headers +' +test_debug 'cat gitweb.headers' + +test_done From b629275fd02aa07c2630d1a8c8a14011ff164043 Mon Sep 17 00:00:00 2001 From: Mark Rada Date: Sat, 7 Nov 2009 16:13:29 +0100 Subject: [PATCH 261/884] gitweb: Smarter snapshot names Teach gitweb how to produce nicer snapshot names by only using the short hash id. If clients make requests using a tree-ish that is not a partial or full SHA-1 hash, then the short hash will also be appended to whatever they asked for. If clients request snapshot of a tag (which means that $hash ('h') parameter has 'refs/tags/' prefix), use only tag name. Update tests cases in t9502-gitweb-standalone-parse-output. Gitweb uses the following format for snapshot filenames: -. where is project name with '.git' or '/.git' suffix stripped, unless '.git' is the whole project name. For snapshot prefix it uses: -/ as compared to / before (without version info). Current rules for : * if 'h' / $hash parameter is SHA-1 or shortened SHA-1, use SHA-1 shortened to to 7 characters * otherwise if 'h' / $hash parameter is tag name (it begins with 'refs/tags/' prefix, use tag name (with 'refs/tags/' stripped * otherwise if 'h' / $hash parameter starts with 'refs/heads/' prefix, strip this prefix, convert '/' into '.', and append shortened SHA-1 after '-', i.e. use - Signed-off-by: Mark Rada Signed-off-by: Shawn O. Pearce Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 76 ++++++++++++++++++----- t/t9502-gitweb-standalone-parse-output.sh | 38 ++++++++++-- 2 files changed, 93 insertions(+), 21 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 8d4a2ae600..d8dfd950a4 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1983,16 +1983,27 @@ sub quote_command { # get HEAD ref of given project as hash sub git_get_head_hash { - my $project = shift; + return git_get_full_hash(shift, 'HEAD'); +} + +sub git_get_full_hash { + return git_get_hash(@_); +} + +sub git_get_short_hash { + return git_get_hash(@_, '--short=7'); +} + +sub git_get_hash { + my ($project, $hash, @options) = @_; my $o_git_dir = $git_dir; my $retval = undef; $git_dir = "$projectroot/$project"; - if (open my $fd, "-|", git_cmd(), "rev-parse", "--verify", "HEAD") { - my $head = <$fd>; + if (open my $fd, '-|', git_cmd(), 'rev-parse', + '--verify', '-q', @options, $hash) { + $retval = <$fd>; + chomp $retval if defined $retval; close $fd; - if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) { - $retval = $1; - } } if (defined $o_git_dir) { $git_dir = $o_git_dir; @@ -5179,6 +5190,43 @@ sub git_tree { git_footer_html(); } +sub snapshot_name { + my ($project, $hash) = @_; + + # path/to/project.git -> project + # path/to/project/.git -> project + my $name = to_utf8($project); + $name =~ s,([^/])/*\.git$,$1,; + $name = basename($name); + # sanitize name + $name =~ s/[[:cntrl:]]/?/g; + + my $ver = $hash; + if ($hash =~ /^[0-9a-fA-F]+$/) { + # shorten SHA-1 hash + my $full_hash = git_get_full_hash($project, $hash); + if ($full_hash =~ /^$hash/ && length($hash) > 7) { + $ver = git_get_short_hash($project, $hash); + } + } elsif ($hash =~ m!^refs/tags/(.*)$!) { + # tags don't need shortened SHA-1 hash + $ver = $1; + } else { + # branches and other need shortened SHA-1 hash + if ($hash =~ m!^refs/(?:heads|remotes)/(.*)$!) { + $ver = $1; + } + $ver .= '-' . git_get_short_hash($project, $hash); + } + # in case of hierarchical branch names + $ver =~ s!/!.!g; + + # name = project-version_string + $name = "$name-$ver"; + + return wantarray ? ($name, $name) : $name; +} + sub git_snapshot { my $format = $input_params{'snapshot_format'}; if (!@snapshot_fmts) { @@ -5203,24 +5251,20 @@ sub git_snapshot { die_error(400, 'Object is not a tree-ish'); } - my $name = $project; - $name =~ s,([^/])/*\.git$,$1,; - $name = basename($name); - my $filename = to_utf8($name); - $name =~ s/\047/\047\\\047\047/g; - my $cmd; - $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}"; - $cmd = quote_command( + my ($name, $prefix) = snapshot_name($project, $hash); + my $filename = "$name$known_snapshot_formats{$format}{'suffix'}"; + my $cmd = quote_command( git_cmd(), 'archive', "--format=$known_snapshot_formats{$format}{'format'}", - "--prefix=$name/", $hash); + "--prefix=$prefix/", $hash); if (exists $known_snapshot_formats{$format}{'compressor'}) { $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}}); } + $filename =~ s/(["\\])/\\$1/g; print $cgi->header( -type => $known_snapshot_formats{$format}{'type'}, - -content_disposition => 'inline; filename="' . "$filename" . '"', + -content_disposition => 'inline; filename="' . $filename . '"', -status => '200 OK'); open my $fd, "-|", $cmd diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh index 741187b9e4..dd83890001 100755 --- a/t/t9502-gitweb-standalone-parse-output.sh +++ b/t/t9502-gitweb-standalone-parse-output.sh @@ -56,29 +56,57 @@ test_debug ' test_expect_success 'snapshot: full sha1' ' gitweb_run "p=.git;a=snapshot;h=$FULL_ID;sf=tar" && - check_snapshot ".git-$FULL_ID" ".git" + check_snapshot ".git-$SHORT_ID" ' test_debug 'cat gitweb.headers && cat file_list' test_expect_success 'snapshot: shortened sha1' ' gitweb_run "p=.git;a=snapshot;h=$SHORT_ID;sf=tar" && - check_snapshot ".git-$SHORT_ID" ".git" + check_snapshot ".git-$SHORT_ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: almost full sha1' ' + ID=$(git rev-parse --short=30 HEAD) && + gitweb_run "p=.git;a=snapshot;h=$ID;sf=tar" && + check_snapshot ".git-$SHORT_ID" ' test_debug 'cat gitweb.headers && cat file_list' test_expect_success 'snapshot: HEAD' ' gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tar" && - check_snapshot ".git-HEAD" ".git" + check_snapshot ".git-HEAD-$SHORT_ID" ' test_debug 'cat gitweb.headers && cat file_list' test_expect_success 'snapshot: short branch name (master)' ' gitweb_run "p=.git;a=snapshot;h=master;sf=tar" && - check_snapshot ".git-master" ".git" + ID=$(git rev-parse --verify --short=7 master) && + check_snapshot ".git-master-$ID" ' test_debug 'cat gitweb.headers && cat file_list' -test_expect_failure 'snapshot: hierarchical branch name (xx/test)' ' +test_expect_success 'snapshot: short tag name (first)' ' + gitweb_run "p=.git;a=snapshot;h=first;sf=tar" && + ID=$(git rev-parse --verify --short=7 first) && + check_snapshot ".git-first-$ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: full branch name (refs/heads/master)' ' + gitweb_run "p=.git;a=snapshot;h=refs/heads/master;sf=tar" && + ID=$(git rev-parse --verify --short=7 master) && + check_snapshot ".git-master-$ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: full tag name (refs/tags/first)' ' + gitweb_run "p=.git;a=snapshot;h=refs/tags/first;sf=tar" && + check_snapshot ".git-first" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: hierarchical branch name (xx/test)' ' gitweb_run "p=.git;a=snapshot;h=xx/test;sf=tar" && ! grep "filename=.*/" gitweb.headers ' From 92815b3363c6cf317337437a986bdf2e8f1aa3a0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Nov 2009 07:24:33 -0800 Subject: [PATCH 262/884] Git-aware CGI to provide dumb HTTP transport http-backend: Fix symbol clash on AIX 5.3 Mike says: > > +static void send_file(const char *the_type, const char *name) > > +{ > > I think a symbol clash here is responsible for a build breakage in > next on AIX 5.3: > > CC http-backend.o > http-backend.c:213: error: conflicting types for `send_file' > /usr/include/sys/socket.h:676: error: previous declaration of `send_file' > gmake: *** [http-backend.o] Error 1 So we rename the function send_local_file(). Reported-by: Mike Ralphson Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- http-backend.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/http-backend.c b/http-backend.c index 902126675a..646e9108bd 100644 --- a/http-backend.c +++ b/http-backend.c @@ -209,7 +209,7 @@ static void send_strbuf(const char *type, struct strbuf *buf) safe_write(1, buf->buf, buf->len); } -static void send_file(const char *the_type, const char *name) +static void send_local_file(const char *the_type, const char *name) { const char *p = git_path("%s", name); size_t buf_alloc = 8192; @@ -247,28 +247,28 @@ static void get_text_file(char *name) { select_getanyfile(); hdr_nocache(); - send_file("text/plain", name); + send_local_file("text/plain", name); } static void get_loose_object(char *name) { select_getanyfile(); hdr_cache_forever(); - send_file("application/x-git-loose-object", name); + send_local_file("application/x-git-loose-object", name); } static void get_pack_file(char *name) { select_getanyfile(); hdr_cache_forever(); - send_file("application/x-git-packed-objects", name); + send_local_file("application/x-git-packed-objects", name); } static void get_idx_file(char *name) { select_getanyfile(); hdr_cache_forever(); - send_file("application/x-git-packed-objects-toc", name); + send_local_file("application/x-git-packed-objects-toc", name); } static int http_config(const char *var, const char *value, void *cb) From 34b6cb8bb032bd16f3d1c93a8417beb75e51ed29 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Nov 2009 11:26:43 -0800 Subject: [PATCH 263/884] http-backend: Protect GIT_PROJECT_ROOT from /../ requests Eons ago HPA taught git-daemon how to protect itself from /../ attacks, which Junio brought back into service in d79374c7b58d ("daemon.c and path.enter_repo(): revamp path validation"). I did not carry this into git-http-backend as originally we relied only upon PATH_TRANSLATED, and assumed the HTTP server had done its access control checks to validate the resolved path was within a directory permitting access from the remote client. This would usually be sufficient to protect a server from requests for its /etc/passwd file by http://host/smart/../etc/passwd sorts of URLs. However in 917adc036086 Mark Lodato added GIT_PROJECT_ROOT as an additional method of configuring the CGI. When this environment variable is used the web server does not generate the final access path and therefore may blindly pass through "/../etc/passwd" in PATH_INFO under the assumption that "/../" might have special meaning to the invoked CGI. Instead of permitting these sorts of malformed path requests, we now reject them back at the client, with an error message for the server log. This matches git-daemon behavior. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- cache.h | 1 + daemon.c | 49 +---------------------------------------- http-backend.c | 6 +++++ path.c | 47 +++++++++++++++++++++++++++++++++++++++ t/t5560-http-backend.sh | 31 ++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 48 deletions(-) diff --git a/cache.h b/cache.h index 4e283be24e..ecbd88adba 100644 --- a/cache.h +++ b/cache.h @@ -656,6 +656,7 @@ const char *make_relative_path(const char *abs, const char *base); int normalize_path_copy(char *dst, const char *src); int longest_ancestor_length(const char *path, const char *prefix_list); char *strip_path_suffix(const char *path, const char *suffix); +int daemon_avoid_alias(const char *path); /* Read and unpack a sha1 file into memory, write memory to a sha1 file */ extern int sha1_object_info(const unsigned char *, unsigned long *); diff --git a/daemon.c b/daemon.c index 1b5ada6648..ce4800621c 100644 --- a/daemon.c +++ b/daemon.c @@ -101,53 +101,6 @@ static void NORETURN daemon_die(const char *err, va_list params) exit(1); } -static int avoid_alias(char *p) -{ - int sl, ndot; - - /* - * This resurrects the belts and suspenders paranoia check by HPA - * done in <435560F7.4080006@zytor.com> thread, now enter_repo() - * does not do getcwd() based path canonicalizations. - * - * sl becomes true immediately after seeing '/' and continues to - * be true as long as dots continue after that without intervening - * non-dot character. - */ - if (!p || (*p != '/' && *p != '~')) - return -1; - sl = 1; ndot = 0; - p++; - - while (1) { - char ch = *p++; - if (sl) { - if (ch == '.') - ndot++; - else if (ch == '/') { - if (ndot < 3) - /* reject //, /./ and /../ */ - return -1; - ndot = 0; - } - else if (ch == 0) { - if (0 < ndot && ndot < 3) - /* reject /.$ and /..$ */ - return -1; - return 0; - } - else - sl = ndot = 0; - } - else if (ch == 0) - return 0; - else if (ch == '/') { - sl = 1; - ndot = 0; - } - } -} - static char *path_ok(char *directory) { static char rpath[PATH_MAX]; @@ -157,7 +110,7 @@ static char *path_ok(char *directory) dir = directory; - if (avoid_alias(dir)) { + if (daemon_avoid_alias(dir)) { logerror("'%s': aliased", dir); return NULL; } diff --git a/http-backend.c b/http-backend.c index 646e9108bd..f8ea9d7faa 100644 --- a/http-backend.c +++ b/http-backend.c @@ -559,7 +559,13 @@ static char* getdir(void) if (root && *root) { if (!pathinfo || !*pathinfo) die("GIT_PROJECT_ROOT is set but PATH_INFO is not"); + if (daemon_avoid_alias(pathinfo)) + die("'%s': aliased", pathinfo); strbuf_addstr(&buf, root); + if (buf.buf[buf.len - 1] != '/') + strbuf_addch(&buf, '/'); + if (pathinfo[0] == '/') + pathinfo++; strbuf_addstr(&buf, pathinfo); return strbuf_detach(&buf, NULL); } else if (path && *path) { diff --git a/path.c b/path.c index 047fdb0a1f..c7679be5c8 100644 --- a/path.c +++ b/path.c @@ -564,3 +564,50 @@ char *strip_path_suffix(const char *path, const char *suffix) return NULL; return xstrndup(path, chomp_trailing_dir_sep(path, path_len)); } + +int daemon_avoid_alias(const char *p) +{ + int sl, ndot; + + /* + * This resurrects the belts and suspenders paranoia check by HPA + * done in <435560F7.4080006@zytor.com> thread, now enter_repo() + * does not do getcwd() based path canonicalizations. + * + * sl becomes true immediately after seeing '/' and continues to + * be true as long as dots continue after that without intervening + * non-dot character. + */ + if (!p || (*p != '/' && *p != '~')) + return -1; + sl = 1; ndot = 0; + p++; + + while (1) { + char ch = *p++; + if (sl) { + if (ch == '.') + ndot++; + else if (ch == '/') { + if (ndot < 3) + /* reject //, /./ and /../ */ + return -1; + ndot = 0; + } + else if (ch == 0) { + if (0 < ndot && ndot < 3) + /* reject /.$ and /..$ */ + return -1; + return 0; + } + else + sl = ndot = 0; + } + else if (ch == 0) + return 0; + else if (ch == '/') { + sl = 1; + ndot = 0; + } + } +} diff --git a/t/t5560-http-backend.sh b/t/t5560-http-backend.sh index 908ba079d2..ed034bc980 100755 --- a/t/t5560-http-backend.sh +++ b/t/t5560-http-backend.sh @@ -146,6 +146,37 @@ test_expect_success 'http.receivepack false' ' POST git-receive-pack 0000 "403 Forbidden" ' +run_backend() { + REQUEST_METHOD=GET \ + GIT_PROJECT_ROOT="$HTTPD_DOCUMENT_ROOT_PATH" \ + PATH_INFO="$2" \ + git http-backend >act.out 2>act.err +} + +path_info() { + if test $1 = 0; then + run_backend "$2" + else + test_must_fail run_backend "$2" && + echo "fatal: '$2': aliased" >exp.err && + test_cmp exp.err act.err + fi +} + +test_expect_success 'http-backend blocks bad PATH_INFO' ' + config http.getanyfile true && + + run_backend 0 /repo.git/HEAD && + + run_backend 1 /repo.git/../HEAD && + run_backend 1 /../etc/passwd && + run_backend 1 ../etc/passwd && + run_backend 1 /etc//passwd && + run_backend 1 /etc/./passwd && + run_backend 1 /etc/.../passwd && + run_backend 1 //domain/data.txt +' + cat >exp < Date: Mon, 9 Nov 2009 10:10:36 -0800 Subject: [PATCH 264/884] t5551-http-fetch: Work around some libcurl versions Some versions of libcurl report their output when GIT_CURL_VERBOSE is set differently than other versions do. At least one variant (version unknown but likely pre-7.18.1) reports the POST payload to stderr, and omits the blank line after each HTTP request/response. We clip these lines out of the stderr output now before doing the compare, so we aren't surprised by this trivial difference. Reported-by: Tarmigan Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/t5551-http-fetch.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh index eb0c039323..0bf165bacd 100755 --- a/t/t5551-http-fetch.sh +++ b/t/t5551-http-fetch.sh @@ -31,23 +31,19 @@ cat >exp < GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 > Accept: */* > Pragma: no-cache - < HTTP/1.1 200 OK < Pragma: no-cache < Cache-Control: no-cache, max-age=0, must-revalidate < Content-Type: application/x-git-upload-pack-advertisement -< > POST /smart/repo.git/git-upload-pack HTTP/1.1 > Accept-Encoding: deflate, gzip > Content-Type: application/x-git-upload-pack-request > Accept: application/x-git-upload-pack-response > Content-Length: xxx - < HTTP/1.1 200 OK < Pragma: no-cache < Cache-Control: no-cache, max-age=0, must-revalidate < Content-Type: application/x-git-upload-pack-result -< EOF test_expect_success 'clone http repository' ' GIT_CURL_VERBOSE=1 git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err && @@ -56,6 +52,8 @@ test_expect_success 'clone http repository' ' sed -e " s/Q\$// /^[*] /d + /^$/d + /^< $/d /^[^><]/{ s/^/> / @@ -64,6 +62,8 @@ test_expect_success 'clone http repository' ' /^> User-Agent: /d /^> Host: /d s/^> Content-Length: .*/> Content-Length: xxx/ + /^> 00..want /d + /^> 00.*done/d /^< Server: /d /^< Expires: /d From 203666352f36702a8773ab47f67ef467528245ae Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Nov 2009 10:10:37 -0800 Subject: [PATCH 265/884] t5551-http-fetch: Work around broken Accept header in libcurl Unfortunately at least one version of libcurl has a bug causing it to include "Accept: */*" in the same POST request where we have already asked for "Accept: application/x-git-upload-pack-response". This is a bug in libcurl, not Git, or our test vector. The application has explicitly asked the server for a single content type, but libcurl has mistakenly also told the server the client application will accept */*, which is any content type. Based on the libcurl change log, this "Accept: */*" header bug may have been fixed in version 7.18.1 released March 30, 2008: http://curl.haxx.se/changes.html#7_18_1 Rather than require users to upgrade libcurl we change the test vector to trim this line out of the 2nd request. Reported-by: Tarmigan Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/t5551-http-fetch.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh index 0bf165bacd..c0505ecd7b 100755 --- a/t/t5551-http-fetch.sh +++ b/t/t5551-http-fetch.sh @@ -61,6 +61,9 @@ test_expect_success 'clone http repository' ' /^> User-Agent: /d /^> Host: /d + /^> POST /,$ { + /^> Accept: [*]\\/[*]/d + } s/^> Content-Length: .*/> Content-Length: xxx/ /^> 00..want /d /^> 00.*done/d From fe9a215214acd2cf9132aec70e0758786a6e3e8b Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:41 -0600 Subject: [PATCH 266/884] Retire fetch--tool helper to contrib/examples When git-fetch was builtin-ized, the previous script was moved to contrib/examples. Now, it is the sole remaining user for 'git fetch--tool'. The fetch--tool code is still worth keeping around so people can try out the old git-fetch.sh, for example when investigating regressions from the builtinifaction. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- Makefile | 1 - builtin.h | 1 - builtin-fetch--tool.c => contrib/examples/builtin-fetch--tool.c | 0 git.c | 1 - 4 files changed, 3 deletions(-) rename builtin-fetch--tool.c => contrib/examples/builtin-fetch--tool.c (100%) diff --git a/Makefile b/Makefile index 5d5976f94f..c0ba479d21 100644 --- a/Makefile +++ b/Makefile @@ -601,7 +601,6 @@ BUILTIN_OBJS += builtin-diff-index.o BUILTIN_OBJS += builtin-diff-tree.o BUILTIN_OBJS += builtin-diff.o BUILTIN_OBJS += builtin-fast-export.o -BUILTIN_OBJS += builtin-fetch--tool.o BUILTIN_OBJS += builtin-fetch-pack.o BUILTIN_OBJS += builtin-fetch.o BUILTIN_OBJS += builtin-fmt-merge-msg.o diff --git a/builtin.h b/builtin.h index a2174dc855..c3f83c093f 100644 --- a/builtin.h +++ b/builtin.h @@ -48,7 +48,6 @@ extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); extern int cmd_fast_export(int argc, const char **argv, const char *prefix); extern int cmd_fetch(int argc, const char **argv, const char *prefix); extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix); -extern int cmd_fetch__tool(int argc, const char **argv, const char *prefix); extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix); extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix); extern int cmd_format_patch(int argc, const char **argv, const char *prefix); diff --git a/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c similarity index 100% rename from builtin-fetch--tool.c rename to contrib/examples/builtin-fetch--tool.c diff --git a/git.c b/git.c index bd2c5fe77b..f295561a93 100644 --- a/git.c +++ b/git.c @@ -304,7 +304,6 @@ static void handle_internal_command(int argc, const char **argv) { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, - { "fetch--tool", cmd_fetch__tool, RUN_SETUP }, { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP }, { "for-each-ref", cmd_for_each_ref, RUN_SETUP }, { "format-patch", cmd_format_patch, RUN_SETUP }, From 9c855c31786b9e879ef4cd3b8b5aa97bc4bcf8ec Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:42 -0600 Subject: [PATCH 267/884] Show usage string for 'git grep -h' Clarification: the following description only talks about "git grep -h" without any other options and arguments. Such a change cannot be breaking backward compatibility. "grep -h" cannot be asking for suppressing filenames, as there is no match pattern specified. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-grep.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/builtin-grep.c b/builtin-grep.c index 1df25b07b5..01be9bf7ff 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -788,6 +788,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_END() }; + /* + * 'git grep -h', unlike 'git grep -h ', is a request + * to show usage information and exit. + */ + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(grep_usage, options); + memset(&opt, 0, sizeof(opt)); opt.prefix = prefix; opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; From fef34270f209eb5d2cde01b8175b24d96d1cff21 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:43 -0600 Subject: [PATCH 268/884] Show usage string for 'git cherry -h' Treat an "-h" option as a request for help, rather than an "Unknown commit -h" error. "cherry -h" could be asking to compare histories that leads to our HEAD and a commit that can be named as "-h". Strictly speaking, that may be a valid refname, but the user would have to say something like "tags/-h" to name such a pathological ref already, so it is not such a big deal. The "-h" option keeps its meaning even if preceded by other options or followed by other arguments. This keeps the command-line syntax closer to what parse_options would give and supports shell aliases like 'alias cherry="git cherry -v"' a little better. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-log.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builtin-log.c b/builtin-log.c index 207a36178b..524850735a 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -1237,6 +1237,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) argv++; } + if (argc > 1 && !strcmp(argv[1], "-h")) + usage(cherry_usage); + switch (argc) { case 4: limit = argv[3]; From 6e9daeffec0213fa1cee76ad9d899fe492409f46 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:44 -0600 Subject: [PATCH 269/884] Show usage string for 'git commit-tree -h' Treat an "-h" option as a request for help, rather than a "Not a valid object name" error. "commit-tree -h" could be asking to create a new commit from a treeish named "-h". Strictly speaking, such a pathological ref name is possible, but the user would have to had said something like "tags/-h" to name such a pathological already. commit-tree is usually used in scripts with raw object ids, anyway. For consistency, the "-h" option uses its new meaning even if followed by other arguments. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-commit-tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index 6467077731..ddcb7a4bbb 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -105,7 +105,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); - if (argc < 2) + if (argc < 2 || !strcmp(argv[1], "-h")) usage(commit_tree_usage); if (get_sha1(argv[1], tree_sha1)) die("Not a valid object name %s", argv[1]); From 20c7e3d5cfcc0834fd5d38200e94d15a103ab271 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:45 -0600 Subject: [PATCH 270/884] Show usage string for 'git merge-ours -h' This change is strictly about 'git merge-ours -h' without any other options and arguments. This change cannot break compatibility since merge drivers are always passed '--', among other arguments. Any usage string for this command is a lie, since it ignored its arguments until now. Still, it makes sense to let the user know the expected usage when asked. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-merge-ours.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/builtin-merge-ours.c b/builtin-merge-ours.c index 8f5bbaf402..684411694f 100644 --- a/builtin-merge-ours.c +++ b/builtin-merge-ours.c @@ -10,6 +10,9 @@ #include "git-compat-util.h" #include "builtin.h" +static const char builtin_merge_ours_usage[] = + "git merge-ours ... -- HEAD ..."; + static const char *diff_index_args[] = { "diff-index", "--quiet", "--cached", "HEAD", "--", NULL }; @@ -17,6 +20,9 @@ static const char *diff_index_args[] = { int cmd_merge_ours(int argc, const char **argv, const char *prefix) { + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(builtin_merge_ours_usage); + /* * We need to exit with 2 if the index does not match our HEAD tree, * because the current index is what we will be committing as the From e62b393505616c3ce313f6dac5060d9e1cde8e42 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:46 -0600 Subject: [PATCH 271/884] Show usage string for 'git show-ref -h' This only changes the behavior of "git show-ref -h" without any other options and arguments. "show-ref -h" currently is short for "show-ref --head", which shows all the refs/* and HEAD, as opposed to "show-ref" that shows all the refs/* and not HEAD. Does anybody use "show-ref -h"? It was in Linus's original, most likely only because "it might be handy", not because "the command should not show the HEAD by default for such and such reasons". So I think it is okay if "show-ref -h" (but not "show-ref --head") gives help and exits. If a current script uses "git show-ref -h" without any other arguments, it would have to be adapted by changing "-h" to "--head". Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- Documentation/git-show-ref.txt | 3 +-- builtin-show-ref.c | 10 ++++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index f4429bdc68..70f400b266 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -8,7 +8,7 @@ git-show-ref - List references in a local repository SYNOPSIS -------- [verse] -'git show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] +'git show-ref' [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=]] [--abbrev[=]] [--tags] [--heads] [--] ... 'git show-ref' --exclude-existing[=] < ref-list @@ -30,7 +30,6 @@ the `.git` directory. OPTIONS ------- --h:: --head:: Show the HEAD reference. diff --git a/builtin-show-ref.c b/builtin-show-ref.c index c46550c9c0..17ada88dfb 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -7,7 +7,7 @@ #include "parse-options.h" static const char * const show_ref_usage[] = { - "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=]] [--abbrev[=]] [--tags] [--heads] [--] [pattern*] ", + "git show-ref [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=]] [--abbrev[=]] [--tags] [--heads] [--] [pattern*] ", "git show-ref --exclude-existing[=pattern] < ref-list", NULL }; @@ -183,7 +183,10 @@ static const struct option show_ref_options[] = { OPT_BOOLEAN(0, "heads", &heads_only, "only show heads (can be combined with tags)"), OPT_BOOLEAN(0, "verify", &verify, "stricter reference checking, " "requires exact ref path"), - OPT_BOOLEAN('h', "head", &show_head, "show the HEAD reference"), + { OPTION_BOOLEAN, 'h', NULL, &show_head, NULL, + "show the HEAD reference", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + OPT_BOOLEAN(0, "head", &show_head, "show the HEAD reference"), OPT_BOOLEAN('d', "dereference", &deref_tags, "dereference tags into object IDs"), { OPTION_CALLBACK, 's', "hash", &abbrev, "n", @@ -201,6 +204,9 @@ static const struct option show_ref_options[] = { int cmd_show_ref(int argc, const char **argv, const char *prefix) { + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(show_ref_usage, show_ref_options); + argc = parse_options(argc, argv, prefix, show_ref_options, show_ref_usage, PARSE_OPT_NO_INTERNAL_HELP); From 9c4a036b34acef63ab754f0e27e5e54bd9d9a210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Mon, 9 Nov 2009 21:09:56 +0100 Subject: [PATCH 272/884] Teach the --all option to 'git fetch' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'git remote' is meant for managing remotes and 'git fetch' is meant for actually fetching data from remote repositories. Therefore, it is not logical that you must use 'git remote update' to fetch from more than one repository at once. Add the --all option to 'git fetch', to tell it to attempt to fetch from all remotes. Also, if --all is not given, the argument is allowed to be the name of a group, to allow fetching from all repositories in the group. Other options except -v and -q are silently ignored. Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano --- Documentation/fetch-options.txt | 3 + Documentation/git-fetch.txt | 12 ++- Documentation/pull-fetch-param.txt | 7 ++ builtin-fetch.c | 150 +++++++++++++++++++++++++---- t/t5506-remote-groups.sh | 21 +++- t/t5514-fetch-multiple.sh | 76 +++++++++++++++ 6 files changed, 247 insertions(+), 22 deletions(-) create mode 100755 t/t5514-fetch-multiple.sh diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 28868747da..93d73c3eea 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -1,3 +1,6 @@ +--all:: + Fetch all remotes. + -a:: --append:: Append ref names and object names of fetched refs to the diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index d3164c5c88..334744c2eb 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -10,11 +10,15 @@ SYNOPSIS -------- 'git fetch' ... +'git fetch' + +'git fetch' --all + DESCRIPTION ----------- -Fetches named heads or tags from another repository, along with -the objects necessary to complete them. +Fetches named heads or tags from one or more other repositories, +along with the objects necessary to complete them. The ref names and their object names of fetched refs are stored in `.git/FETCH_HEAD`. This information is left for a later merge @@ -28,6 +32,10 @@ pointed by remote tags that it does not yet have, then fetch those missing tags. If the other end has tags that point at branches you are not interested in, you will not get them. +'git fetch' can fetch from either a single named repository, or +or from several repositories at once if is given and +there is a remotes. entry in the configuration file. +(See linkgit:git-config[1]). OPTIONS ------- diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt index f9811f2473..712b91aab3 100644 --- a/Documentation/pull-fetch-param.txt +++ b/Documentation/pull-fetch-param.txt @@ -4,6 +4,13 @@ (see the section <> below) or the name of a remote (see the section <> below). +ifndef::git-pull[] +:: + A name referring to a list of repositories as the value + of remotes. in the configuration file. + (See linkgit:git-config[1]). +endif::git-pull[] + :: The format of a parameter is an optional plus `{plus}`, followed by the source ref , followed diff --git a/builtin-fetch.c b/builtin-fetch.c index cb48c57ca3..e6bbdc7a17 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -14,6 +14,8 @@ static const char * const builtin_fetch_usage[] = { "git fetch [options] [ ...]", + "git fetch [options] ", + "git fetch --all [options]", NULL }; @@ -23,7 +25,7 @@ enum { TAGS_SET = 2 }; -static int append, force, keep, update_head_ok, verbosity; +static int all, append, force, keep, update_head_ok, verbosity; static int tags = TAGS_DEFAULT; static const char *depth; static const char *upload_pack; @@ -32,6 +34,8 @@ static struct transport *transport; static struct option builtin_fetch_options[] = { OPT__VERBOSITY(&verbosity), + OPT_BOOLEAN(0, "all", &all, + "fetch from all remotes"), OPT_BOOLEAN('a', "append", &append, "append to .git/FETCH_HEAD instead of overwriting"), OPT_STRING(0, "upload-pack", &upload_pack, "PATH", @@ -639,27 +643,89 @@ static void set_option(const char *name, const char *value) name, transport->url); } -int cmd_fetch(int argc, const char **argv, const char *prefix) +static int get_one_remote_for_fetch(struct remote *remote, void *priv) +{ + struct string_list *list = priv; + string_list_append(remote->name, list); + return 0; +} + +struct remote_group_data { + const char *name; + struct string_list *list; +}; + +static int get_remote_group(const char *key, const char *value, void *priv) +{ + struct remote_group_data *g = priv; + + if (!prefixcmp(key, "remotes.") && + !strcmp(key + 8, g->name)) { + /* split list by white space */ + int space = strcspn(value, " \t\n"); + while (*value) { + if (space > 1) { + string_list_append(xstrndup(value, space), + g->list); + } + value += space + (value[space] != '\0'); + space = strcspn(value, " \t\n"); + } + } + + return 0; +} + +static int add_remote_or_group(const char *name, struct string_list *list) +{ + int prev_nr = list->nr; + struct remote_group_data g = { name, list }; + + git_config(get_remote_group, &g); + if (list->nr == prev_nr) { + struct remote *remote; + if (!remote_is_configured(name)) + return 0; + remote = remote_get(name); + string_list_append(remote->name, list); + } + return 1; +} + +static int fetch_multiple(struct string_list *list) +{ + int i, result = 0; + const char *argv[] = { "fetch", NULL, NULL, NULL, NULL }; + int argc = 1; + + if (verbosity >= 2) + argv[argc++] = "-v"; + if (verbosity >= 1) + argv[argc++] = "-v"; + else if (verbosity < 0) + argv[argc++] = "-q"; + + for (i = 0; i < list->nr; i++) { + const char *name = list->items[i].string; + argv[argc] = name; + if (verbosity >= 0) + printf("Fetching %s\n", name); + if (run_command_v_opt(argv, RUN_GIT_CMD)) { + error("Could not fetch %s", name); + result = 1; + } + } + + return result; +} + +static int fetch_one(struct remote *remote, int argc, const char **argv) { - struct remote *remote; int i; static const char **refs = NULL; int ref_nr = 0; int exit_code; - /* Record the command line for the reflog */ - strbuf_addstr(&default_rla, "fetch"); - for (i = 1; i < argc; i++) - strbuf_addf(&default_rla, " %s", argv[i]); - - argc = parse_options(argc, argv, prefix, - builtin_fetch_options, builtin_fetch_usage, 0); - - if (argc == 0) - remote = remote_get(NULL); - else - remote = remote_get(argv[0]); - if (!remote) die("Where do you want to fetch from today?"); @@ -675,10 +741,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (depth) set_option(TRANS_OPT_DEPTH, depth); - if (argc > 1) { + if (argc > 0) { int j = 0; refs = xcalloc(argc + 1, sizeof(const char *)); - for (i = 1; i < argc; i++) { + for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "tag")) { char *ref; i++; @@ -705,3 +771,51 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) transport = NULL; return exit_code; } + +int cmd_fetch(int argc, const char **argv, const char *prefix) +{ + int i; + struct string_list list = { NULL, 0, 0, 0 }; + struct remote *remote; + int result = 0; + + /* Record the command line for the reflog */ + strbuf_addstr(&default_rla, "fetch"); + for (i = 1; i < argc; i++) + strbuf_addf(&default_rla, " %s", argv[i]); + + argc = parse_options(argc, argv, prefix, + builtin_fetch_options, builtin_fetch_usage, 0); + + if (all) { + if (argc == 1) + die("fetch --all does not take a repository argument"); + else if (argc > 1) + die("fetch --all does not make sense with refspecs"); + (void) for_each_remote(get_one_remote_for_fetch, &list); + result = fetch_multiple(&list); + } else if (argc == 0) { + /* No arguments -- use default remote */ + remote = remote_get(NULL); + result = fetch_one(remote, argc, argv); + } else { + /* Single remote or group */ + (void) add_remote_or_group(argv[0], &list); + if (list.nr > 1) { + /* More than one remote */ + if (argc > 1) + die("Fetching a group and specifying refspecs does not make sense"); + result = fetch_multiple(&list); + } else { + /* Zero or one remotes */ + remote = remote_get(argv[0]); + result = fetch_one(remote, argc-1, argv+1); + } + } + + /* All names were strdup()ed or strndup()ed */ + list.strdup_strings = 1; + string_list_clear(&list, 0); + + return result; +} diff --git a/t/t5506-remote-groups.sh b/t/t5506-remote-groups.sh index 2a1806b0b4..b7b7ddaa40 100755 --- a/t/t5506-remote-groups.sh +++ b/t/t5506-remote-groups.sh @@ -51,7 +51,7 @@ test_expect_success 'nonexistant group produces error' ' ! repo_fetched two ' -test_expect_success 'updating group updates all members' ' +test_expect_success 'updating group updates all members (remote update)' ' mark group-all && update_repos && git config --add remotes.all one && @@ -61,7 +61,15 @@ test_expect_success 'updating group updates all members' ' repo_fetched two ' -test_expect_success 'updating group does not update non-members' ' +test_expect_success 'updating group updates all members (fetch)' ' + mark fetch-group-all && + update_repos && + git fetch all && + repo_fetched one && + repo_fetched two +' + +test_expect_success 'updating group does not update non-members (remote update)' ' mark group-some && update_repos && git config --add remotes.some one && @@ -70,6 +78,15 @@ test_expect_success 'updating group does not update non-members' ' ! repo_fetched two ' +test_expect_success 'updating group does not update non-members (fetch)' ' + mark fetch-group-some && + update_repos && + git config --add remotes.some one && + git remote update some && + repo_fetched one && + ! repo_fetched two +' + test_expect_success 'updating remote name updates that remote' ' mark remote-name && update_repos && diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh new file mode 100755 index 0000000000..25244bf8e8 --- /dev/null +++ b/t/t5514-fetch-multiple.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +test_description='fetch --all works correctly' + +. ./test-lib.sh + +setup_repository () { + mkdir "$1" && ( + cd "$1" && + git init && + >file && + git add file && + test_tick && + git commit -m "Initial" && + git checkout -b side && + >elif && + git add elif && + test_tick && + git commit -m "Second" && + git checkout master + ) +} + +test_expect_success setup ' + setup_repository one && + setup_repository two && + ( + cd two && git branch another + ) && + git clone --mirror two three + git clone one test +' + +cat > test/expect << EOF + one/master + one/side + origin/HEAD -> origin/master + origin/master + origin/side + three/another + three/master + three/side + two/another + two/master + two/side +EOF + +test_expect_success 'git fetch --all' ' + (cd test && + git remote add one ../one && + git remote add two ../two && + git remote add three ../three && + git fetch --all && + git branch -r > output && + test_cmp expect output) +' + +test_expect_success 'git fetch --all should continue if a remote has errors' ' + (git clone one test2 && + cd test2 && + git remote add bad ../non-existing && + git remote add one ../one && + git remote add two ../two && + git remote add three ../three && + test_must_fail git fetch --all && + git branch -r > output && + test_cmp ../test/expect output) +' + +test_expect_success 'git fetch --all does not allow non-option arguments' ' + (cd test && + test_must_fail git fetch --all origin && + test_must_fail git fetch --all origin master) +' + +test_done From 16679e373fa85a75c85e6e3b4ae5cd58a89a4114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Mon, 9 Nov 2009 21:10:32 +0100 Subject: [PATCH 273/884] Teach the --multiple option to 'git fetch' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the --multiple option to specify that all arguments are either groups or remotes. The primary reason for adding this option is to allow us to re-implement 'git remote update' using fetch. It would have been nice if this option was not needed, but since the colon in a refspec is optional, it is in general not possible to know whether a single, colon-less argument is a remote or a refspec. Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano --- Documentation/fetch-options.txt | 6 +++++ Documentation/git-fetch.txt | 2 ++ builtin-fetch.c | 11 ++++++++- t/t5514-fetch-multiple.sh | 44 +++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 93d73c3eea..8b0cf58196 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -24,6 +24,12 @@ --keep:: Keep downloaded pack. +ifndef::git-pull[] +--multiple:: + Allow several and arguments to be + specified. No s may be specified. +endif::git-pull[] + ifdef::git-pull[] --no-tags:: endif::git-pull[] diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index 334744c2eb..edb77dc54e 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -12,6 +12,8 @@ SYNOPSIS 'git fetch' +'git fetch' --multiple [ | ]... + 'git fetch' --all diff --git a/builtin-fetch.c b/builtin-fetch.c index e6bbdc7a17..c903c66d47 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -15,6 +15,7 @@ static const char * const builtin_fetch_usage[] = { "git fetch [options] [ ...]", "git fetch [options] ", + "git fetch --multiple [options] [ | ]...", "git fetch --all [options]", NULL }; @@ -25,7 +26,7 @@ enum { TAGS_SET = 2 }; -static int all, append, force, keep, update_head_ok, verbosity; +static int all, append, force, keep, multiple, update_head_ok, verbosity; static int tags = TAGS_DEFAULT; static const char *depth; static const char *upload_pack; @@ -42,6 +43,8 @@ static struct option builtin_fetch_options[] = { "path to upload pack on remote end"), OPT_BOOLEAN('f', "force", &force, "force overwrite of local branch"), + OPT_BOOLEAN('m', "multiple", &multiple, + "fetch from multiple remotes"), OPT_SET_INT('t', "tags", &tags, "fetch all tags and associated objects", TAGS_SET), OPT_SET_INT('n', NULL, &tags, @@ -798,6 +801,12 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) /* No arguments -- use default remote */ remote = remote_get(NULL); result = fetch_one(remote, argc, argv); + } else if (multiple) { + /* All arguments are assumed to be remotes or groups */ + for (i = 0; i < argc; i++) + if (!add_remote_or_group(argv[i], &list)) + die("No such remote or remote group: %s", argv[i]); + result = fetch_multiple(&list); } else { /* Single remote or group */ (void) add_remote_or_group(argv[0], &list); diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh index 25244bf8e8..69c64ab1e4 100755 --- a/t/t5514-fetch-multiple.sh +++ b/t/t5514-fetch-multiple.sh @@ -73,4 +73,48 @@ test_expect_success 'git fetch --all does not allow non-option arguments' ' test_must_fail git fetch --all origin master) ' +cat > expect << EOF + origin/HEAD -> origin/master + origin/master + origin/side + three/another + three/master + three/side +EOF + +test_expect_success 'git fetch --multiple (but only one remote)' ' + (git clone one test3 && + cd test3 && + git remote add three ../three && + git fetch --multiple three && + git branch -r > output && + test_cmp ../expect output) +' + +cat > expect << EOF + one/master + one/side + origin/HEAD -> origin/master + origin/master + origin/side + two/another + two/master + two/side +EOF + +test_expect_success 'git fetch --multiple (two remotes)' ' + (git clone one test4 && + cd test4 && + git remote add one ../one && + git remote add two ../two && + git fetch --multiple one two && + git branch -r > output && + test_cmp ../expect output) +' + +test_expect_success 'git fetch --multiple (bad remote names)' ' + (cd test4 && + test_must_fail git fetch --multiple four) +' + test_done From 7cc91a2f71c95b4e695549b91c4b629f56887a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Mon, 9 Nov 2009 21:11:06 +0100 Subject: [PATCH 274/884] Add the configuration option skipFetchAll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the configuration skipFetchAll option to allow certain remotes to be skipped when doing 'git fetch --all' and 'git remote update'. The existing skipDefaultUpdate variable is still honored (by 'git fetch --all' and 'git remote update'). (If both are set in the configuration file with different values, the value of the last occurrence will be used.) Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano --- Documentation/config.txt | 8 +++++++- builtin-fetch.c | 3 ++- remote.c | 3 ++- t/t5514-fetch-multiple.sh | 40 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index cd1781498e..50a65ab8d1 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1381,7 +1381,13 @@ remote..mirror:: remote..skipDefaultUpdate:: If true, this remote will be skipped by default when updating - using the update subcommand of linkgit:git-remote[1]. + using linkgit:git-fetch[1] or the `update` subcommand of + linkgit:git-remote[1]. + +remote..skipFetchAll:: + If true, this remote will be skipped by default when updating + using linkgit:git-fetch[1] or the `update` subcommand of + linkgit:git-remote[1]. remote..receivepack:: The default program to execute on the remote side when pushing. See diff --git a/builtin-fetch.c b/builtin-fetch.c index c903c66d47..4cbd69075f 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -649,7 +649,8 @@ static void set_option(const char *name, const char *value) static int get_one_remote_for_fetch(struct remote *remote, void *priv) { struct string_list *list = priv; - string_list_append(remote->name, list); + if (!remote->skip_default_update) + string_list_append(remote->name, list); return 0; } diff --git a/remote.c b/remote.c index 73d33f2584..beaf9fb5b2 100644 --- a/remote.c +++ b/remote.c @@ -396,7 +396,8 @@ static int handle_config(const char *key, const char *value, void *cb) remote->mirror = git_config_bool(key, value); else if (!strcmp(subkey, ".skipdefaultupdate")) remote->skip_default_update = git_config_bool(key, value); - + else if (!strcmp(subkey, ".skipfetchall")) + remote->skip_default_update = git_config_bool(key, value); else if (!strcmp(subkey, ".url")) { const char *v; if (git_config_string(&v, key, value)) diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh index 69c64ab1e4..b73733219d 100755 --- a/t/t5514-fetch-multiple.sh +++ b/t/t5514-fetch-multiple.sh @@ -94,9 +94,6 @@ test_expect_success 'git fetch --multiple (but only one remote)' ' cat > expect << EOF one/master one/side - origin/HEAD -> origin/master - origin/master - origin/side two/another two/master two/side @@ -105,6 +102,7 @@ EOF test_expect_success 'git fetch --multiple (two remotes)' ' (git clone one test4 && cd test4 && + git remote rm origin && git remote add one ../one && git remote add two ../two && git fetch --multiple one two && @@ -117,4 +115,40 @@ test_expect_success 'git fetch --multiple (bad remote names)' ' test_must_fail git fetch --multiple four) ' + +test_expect_success 'git fetch --all (skipFetchAll)' ' + (cd test4 && + for b in $(git branch -r) + do + git branch -r -d $b || break + done && + git remote add three ../three && + git config remote.three.skipFetchAll true && + git fetch --all && + git branch -r > output && + test_cmp ../expect output) +' + +cat > expect << EOF + one/master + one/side + three/another + three/master + three/side + two/another + two/master + two/side +EOF + +test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' ' + (cd test4 && + for b in $(git branch -r) + do + git branch -r -d $b || break + done && + git fetch --multiple one two three && + git branch -r > output && + test_cmp ../expect output) +' + test_done From e2d41c64bfbea592daeb3a065f1aa3900a03e07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Mon, 9 Nov 2009 21:11:59 +0100 Subject: [PATCH 275/884] Add missing test for 'git remote update --prune' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano --- t/t5505-remote.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 852ccb5d7d..e931ce6c69 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -365,6 +365,17 @@ test_expect_success 'update with arguments' ' ' +test_expect_success 'update --prune' ' + + (cd one && + git branch -m side2 side3) && + (cd test && + git remote update --prune && + (cd ../one && git branch -m side3 side2) + git rev-parse refs/remotes/origin/side3 && + test_must_fail git rev-parse refs/remotes/origin/side2) +' + cat > one/expect << EOF apis/master apis/side From f2ef6075c9d248523bf658d82065b46d892b5601 Mon Sep 17 00:00:00 2001 From: Jay Soffian Date: Tue, 10 Nov 2009 00:03:31 -0500 Subject: [PATCH 276/884] remote: refactor some logic into get_stale_heads() Move the logic in builtin-remote.c which determines which local heads are stale to remote.c so it can be used by other builtins. Signed-off-by: Jay Soffian Signed-off-by: Junio C Hamano --- builtin-remote.c | 32 ++++++++------------------------ remote.c | 40 ++++++++++++++++++++++++++++++++++++++++ remote.h | 3 +++ 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/builtin-remote.c b/builtin-remote.c index 0777dd719b..b48267bc7b 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -227,32 +227,10 @@ struct ref_states { int queried; }; -static int handle_one_branch(const char *refname, - const unsigned char *sha1, int flags, void *cb_data) -{ - struct ref_states *states = cb_data; - struct refspec refspec; - - memset(&refspec, 0, sizeof(refspec)); - refspec.dst = (char *)refname; - if (!remote_find_tracking(states->remote, &refspec)) { - struct string_list_item *item; - const char *name = abbrev_branch(refspec.src); - /* symbolic refs pointing nowhere were handled already */ - if ((flags & REF_ISSYMREF) || - string_list_has_string(&states->tracked, name) || - string_list_has_string(&states->new, name)) - return 0; - item = string_list_append(name, &states->stale); - item->util = xstrdup(refname); - } - return 0; -} - static int get_ref_states(const struct ref *remote_refs, struct ref_states *states) { struct ref *fetch_map = NULL, **tail = &fetch_map; - struct ref *ref; + struct ref *ref, *stale_refs; int i; for (i = 0; i < states->remote->fetch_refspec_nr; i++) @@ -268,11 +246,17 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat else string_list_append(abbrev_branch(ref->name), &states->tracked); } + stale_refs = get_stale_heads(states->remote, fetch_map); + for (ref = stale_refs; ref; ref = ref->next) { + struct string_list_item *item = + string_list_append(abbrev_branch(ref->name), &states->stale); + item->util = xstrdup(ref->name); + } + free_refs(stale_refs); free_refs(fetch_map); sort_string_list(&states->new); sort_string_list(&states->tracked); - for_each_ref(handle_one_branch, states); sort_string_list(&states->stale); return 0; diff --git a/remote.c b/remote.c index beaf9fb5b2..eae5866674 100644 --- a/remote.c +++ b/remote.c @@ -6,6 +6,7 @@ #include "revision.h" #include "dir.h" #include "tag.h" +#include "string-list.h" static struct refspec s_tag_refspec = { 0, @@ -1587,3 +1588,42 @@ struct ref *guess_remote_head(const struct ref *head, return list; } + +struct stale_heads_info { + struct remote *remote; + struct string_list *ref_names; + struct ref **stale_refs_tail; +}; + +static int get_stale_heads_cb(const char *refname, + const unsigned char *sha1, int flags, void *cb_data) +{ + struct stale_heads_info *info = cb_data; + struct refspec refspec; + memset(&refspec, 0, sizeof(refspec)); + refspec.dst = (char *)refname; + if (!remote_find_tracking(info->remote, &refspec)) { + if (!((flags & REF_ISSYMREF) || + string_list_has_string(info->ref_names, refspec.src))) { + struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail); + hashcpy(ref->new_sha1, sha1); + } + } + return 0; +} + +struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map) +{ + struct ref *ref, *stale_refs = NULL; + struct string_list ref_names = { NULL, 0, 0, 0 }; + struct stale_heads_info info; + info.remote = remote; + info.ref_names = &ref_names; + info.stale_refs_tail = &stale_refs; + for (ref = fetch_map; ref; ref = ref->next) + string_list_append(ref->name, &ref_names); + sort_string_list(&ref_names); + for_each_ref(get_stale_heads_cb, &info); + string_list_clear(&ref_names, 0); + return stale_refs; +} diff --git a/remote.h b/remote.h index 5db842087d..d0aba81ead 100644 --- a/remote.h +++ b/remote.h @@ -154,4 +154,7 @@ struct ref *guess_remote_head(const struct ref *head, const struct ref *refs, int all); +/* Return refs which no longer exist on remote */ +struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map); + #endif From 3cf6134ad016712ecb78186d9079b9cff7b25416 Mon Sep 17 00:00:00 2001 From: Jay Soffian Date: Tue, 10 Nov 2009 00:03:32 -0500 Subject: [PATCH 277/884] teach warn_dangling_symref to take a FILE argument Different callers of warn_dangling_symref() may want to control whether its output goes to stdout or stderr so let it take a FILE argument. Signed-off-by: Jay Soffian Signed-off-by: Junio C Hamano --- builtin-remote.c | 2 +- refs.c | 7 ++++--- refs.h | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/builtin-remote.c b/builtin-remote.c index b48267bc7b..56cd5af933 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -1166,7 +1166,7 @@ static int prune_remote(const char *remote, int dry_run) printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned", abbrev_ref(refname, "refs/remotes/")); - warn_dangling_symref(dangling_msg, refname); + warn_dangling_symref(stdout, dangling_msg, refname); } free_remote_ref_states(&states); diff --git a/refs.c b/refs.c index 808f56bb27..3e73a0a36d 100644 --- a/refs.c +++ b/refs.c @@ -286,6 +286,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) } struct warn_if_dangling_data { + FILE *fp; const char *refname; const char *msg_fmt; }; @@ -304,13 +305,13 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha if (!resolves_to || strcmp(resolves_to, d->refname)) return 0; - printf(d->msg_fmt, refname); + fprintf(d->fp, d->msg_fmt, refname); return 0; } -void warn_dangling_symref(const char *msg_fmt, const char *refname) +void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname) { - struct warn_if_dangling_data data = { refname, msg_fmt }; + struct warn_if_dangling_data data = { fp, refname, msg_fmt }; for_each_rawref(warn_if_dangling_symref, &data); } diff --git a/refs.h b/refs.h index 777b5b7ca6..e141991851 100644 --- a/refs.h +++ b/refs.h @@ -29,7 +29,7 @@ extern int for_each_replace_ref(each_ref_fn, void *); /* can be used to learn about broken ref and symref */ extern int for_each_rawref(each_ref_fn, void *); -extern void warn_dangling_symref(const char *msg_fmt, const char *refname); +extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname); /* * Extra refs will be listed by for_each_ref() before any actual refs From f360d844de4b752b4cba2343b95592ae4d55d09a Mon Sep 17 00:00:00 2001 From: Jay Soffian Date: Tue, 10 Nov 2009 09:15:47 +0100 Subject: [PATCH 278/884] builtin-fetch: add --prune option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Teach fetch to cull stale remote tracking branches after fetching via --prune. Signed-off-by: Jay Soffian Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano --- Documentation/fetch-options.txt | 4 ++++ builtin-fetch.c | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 8b0cf58196..500637a08b 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -28,6 +28,10 @@ ifndef::git-pull[] --multiple:: Allow several and arguments to be specified. No s may be specified. + +--prune:: + After fetching, remove any remote tracking branches which + no longer exist on the remote. endif::git-pull[] ifdef::git-pull[] diff --git a/builtin-fetch.c b/builtin-fetch.c index 4cbd69075f..81974ea363 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -26,7 +26,7 @@ enum { TAGS_SET = 2 }; -static int all, append, force, keep, multiple, update_head_ok, verbosity; +static int all, append, force, keep, multiple, prune, update_head_ok, verbosity; static int tags = TAGS_DEFAULT; static const char *depth; static const char *upload_pack; @@ -49,6 +49,8 @@ static struct option builtin_fetch_options[] = { "fetch all tags and associated objects", TAGS_SET), OPT_SET_INT('n', NULL, &tags, "do not fetch all tags (--no-tags)", TAGS_UNSET), + OPT_BOOLEAN('p', "prune", &prune, + "prune tracking branches no longer on remote"), OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), OPT_BOOLEAN('u', "update-head-ok", &update_head_ok, "allow updating of HEAD ref"), @@ -492,6 +494,28 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map) return ret; } +static int prune_refs(struct transport *transport, struct ref *ref_map) +{ + int result = 0; + struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map); + const char *dangling_msg = dry_run + ? " (%s will become dangling)\n" + : " (%s has become dangling)\n"; + + for (ref = stale_refs; ref; ref = ref->next) { + if (!dry_run) + result |= delete_ref(ref->name, NULL, 0); + if (verbosity >= 0) { + fprintf(stderr, " x %-*s %-*s -> %s\n", + SUMMARY_WIDTH, "[deleted]", + REFCOL_WIDTH, "(none)", prettify_refname(ref->name)); + warn_dangling_symref(stderr, dangling_msg, ref->name); + } + } + free_refs(stale_refs); + return result; +} + static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata) { @@ -616,6 +640,8 @@ static int do_fetch(struct transport *transport, free_refs(ref_map); return 1; } + if (prune) + prune_refs(transport, ref_map); free_refs(ref_map); /* if neither --no-tags nor --tags was specified, do automated tag @@ -699,9 +725,11 @@ static int add_remote_or_group(const char *name, struct string_list *list) static int fetch_multiple(struct string_list *list) { int i, result = 0; - const char *argv[] = { "fetch", NULL, NULL, NULL, NULL }; + const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL }; int argc = 1; + if (prune) + argv[argc++] = "--prune"; if (verbosity >= 2) argv[argc++] = "-v"; if (verbosity >= 1) From 28a1540132ab98a82cb6ed2cb98458e4e7926acd Mon Sep 17 00:00:00 2001 From: Jay Soffian Date: Tue, 10 Nov 2009 09:19:43 +0100 Subject: [PATCH 279/884] builtin-fetch: add --dry-run option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Teach fetch --dry-run as users of "git remote prune" switching to "git fetch --prune" may expect it. Unfortunately OPT__DRY_RUN() cannot be used as fetch already uses "-n" for something else. Signed-off-by: Jay Soffian Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano --- Documentation/fetch-options.txt | 5 +++++ builtin-fetch.c | 14 ++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 500637a08b..ab6419fe6e 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -12,6 +12,11 @@ `git clone` with `--depth=` option (see linkgit:git-clone[1]) by the specified number of commits. +ifndef::git-pull[] +--dry-run:: + Show what would be done, without making any changes. +endif::git-pull[] + -f:: --force:: When 'git-fetch' is used with `:` diff --git a/builtin-fetch.c b/builtin-fetch.c index 81974ea363..76ec5eab48 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -26,7 +26,7 @@ enum { TAGS_SET = 2 }; -static int all, append, force, keep, multiple, prune, update_head_ok, verbosity; +static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; static int tags = TAGS_DEFAULT; static const char *depth; static const char *upload_pack; @@ -51,6 +51,8 @@ static struct option builtin_fetch_options[] = { "do not fetch all tags (--no-tags)", TAGS_UNSET), OPT_BOOLEAN('p', "prune", &prune, "prune tracking branches no longer on remote"), + OPT_BOOLEAN(0, "dry-run", &dry_run, + "dry run"), OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), OPT_BOOLEAN('u', "update-head-ok", &update_head_ok, "allow updating of HEAD ref"), @@ -187,6 +189,8 @@ static int s_update_ref(const char *action, char *rla = getenv("GIT_REFLOG_ACTION"); static struct ref_lock *lock; + if (dry_run) + return 0; if (!rla) rla = default_rla.buf; snprintf(msg, sizeof(msg), "%s: %s", rla, action); @@ -312,7 +316,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, char note[1024]; const char *what, *kind; struct ref *rm; - char *url, *filename = git_path("FETCH_HEAD"); + char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); fp = fopen(filename, "a"); if (!fp) @@ -617,7 +621,7 @@ static int do_fetch(struct transport *transport, die("Don't know how to fetch from %s", transport->url); /* if not appending, truncate FETCH_HEAD */ - if (!append) { + if (!append && !dry_run) { char *filename = git_path("FETCH_HEAD"); FILE *fp = fopen(filename, "w"); if (!fp) @@ -725,9 +729,11 @@ static int add_remote_or_group(const char *name, struct string_list *list) static int fetch_multiple(struct string_list *list) { int i, result = 0; - const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL }; + const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL, NULL }; int argc = 1; + if (dry_run) + argv[argc++] = "--dry-run"; if (prune) argv[argc++] = "--prune"; if (verbosity >= 2) From 8db355964d89c19eb262ffe38e57e5a610e1cc05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Tue, 10 Nov 2009 09:21:32 +0100 Subject: [PATCH 280/884] Re-implement 'git remote update' using 'git fetch' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order not to duplicate functionality, re-implement 'git remote update' in terms of 'git fetch'. Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano --- builtin-remote.c | 88 +++++++++++++++--------------------------------- 1 file changed, 28 insertions(+), 60 deletions(-) diff --git a/builtin-remote.c b/builtin-remote.c index 56cd5af933..fb0d66d8c5 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -1173,88 +1173,56 @@ static int prune_remote(const char *remote, int dry_run) return result; } -static int get_one_remote_for_update(struct remote *remote, void *priv) +static int get_remote_default(const char *key, const char *value, void *priv) { - struct string_list *list = priv; - if (!remote->skip_default_update) - string_list_append(remote->name, list); - return 0; -} - -static struct remote_group { - const char *name; - struct string_list *list; -} remote_group; - -static int get_remote_group(const char *key, const char *value, void *num_hits) -{ - if (!prefixcmp(key, "remotes.") && - !strcmp(key + 8, remote_group.name)) { - /* split list by white space */ - int space = strcspn(value, " \t\n"); - while (*value) { - if (space > 1) { - string_list_append(xstrndup(value, space), - remote_group.list); - ++*((int *)num_hits); - } - value += space + (value[space] != '\0'); - space = strcspn(value, " \t\n"); - } + if (strcmp(key, "remotes.default") == 0) { + int *found = priv; + *found = 1; } - return 0; } static int update(int argc, const char **argv) { - int i, result = 0, prune = 0; - struct string_list list = { NULL, 0, 0, 0 }; - static const char *default_argv[] = { NULL, "default", NULL }; + int i, prune = 0; struct option options[] = { OPT_GROUP("update specific options"), OPT_BOOLEAN('p', "prune", &prune, "prune remotes after fetching"), OPT_END() }; + const char **fetch_argv; + int fetch_argc = 0; + int default_defined = 0; + + fetch_argv = xmalloc(sizeof(char *) * (argc+5)); argc = parse_options(argc, argv, NULL, options, builtin_remote_usage, PARSE_OPT_KEEP_ARGV0); + + fetch_argv[fetch_argc++] = "fetch"; + + if (prune) + fetch_argv[fetch_argc++] = "--prune"; + if (verbose) + fetch_argv[fetch_argc++] = "-v"; if (argc < 2) { - argc = 2; - argv = default_argv; + fetch_argv[fetch_argc++] = "default"; + } else { + fetch_argv[fetch_argc++] = "--multiple"; + for (i = 1; i < argc; i++) + fetch_argv[fetch_argc++] = argv[i]; } - remote_group.list = &list; - for (i = 1; i < argc; i++) { - int groups_found = 0; - remote_group.name = argv[i]; - result = git_config(get_remote_group, &groups_found); - if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) { - struct remote *remote; - if (!remote_is_configured(argv[i])) - die("No such remote or remote group: %s", - argv[i]); - remote = remote_get(argv[i]); - string_list_append(remote->name, remote_group.list); - } + if (strcmp(fetch_argv[fetch_argc-1], "default") == 0) { + git_config(get_remote_default, &default_defined); + if (!default_defined) + fetch_argv[fetch_argc-1] = "--all"; } - if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default")) - result = for_each_remote(get_one_remote_for_update, &list); + fetch_argv[fetch_argc] = NULL; - for (i = 0; i < list.nr; i++) { - int err = fetch_remote(list.items[i].string); - result |= err; - if (!err && prune) - result |= prune_remote(list.items[i].string, 0); - } - - /* all names were strdup()ed or strndup()ed */ - list.strdup_strings = 1; - string_list_clear(&list, 0); - - return result; + return run_command_v_opt(fetch_argv, RUN_GIT_CMD); } static int get_one_entry(struct remote *remote, void *priv) From 466d1f151a8a0bfde63f0fa5aba91bb8e0efe946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20N=C4=9Bmec?= Date: Tue, 10 Nov 2009 19:11:51 +0100 Subject: [PATCH 281/884] git-update-index.txt: Document the --really-refresh option. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the description next to --assume-unchanged because this option is only useful in a special case of using that option. Signed-off-by: Štěpán Němec Signed-off-by: Junio C Hamano --- Documentation/git-update-index.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 25e0bbea86..6052484ab9 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -99,6 +99,10 @@ in the index e.g. when merging in a commit; thus, in case the assumed-untracked file is changed upstream, you will need to handle the situation manually. +--really-refresh:: + Like '--refresh', but checks stat information unconditionally, + without regard to the "assume unchanged" setting. + -g:: --again:: Runs 'git-update-index' itself on the paths whose index @@ -308,7 +312,7 @@ Configuration ------------- The command honors `core.filemode` configuration variable. If -your repository is on an filesystem whose executable bits are +your repository is on a filesystem whose executable bits are unreliable, this should be set to 'false' (see linkgit:git-config[1]). This causes the command to ignore differences in file modes recorded in the index and the file mode on the filesystem if they differ only on From 6586b1f3465a93e908664ea24fa2f5ff47a587e3 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:47 -0600 Subject: [PATCH 282/884] check-ref-format: update usage string 'git check-ref-format' has learned --branch and --print options since the usage string was last updated. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-check-ref-format.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c index f9381e07ea..96382e3447 100644 --- a/builtin-check-ref-format.c +++ b/builtin-check-ref-format.c @@ -7,6 +7,10 @@ #include "builtin.h" #include "strbuf.h" +static const char builtin_check_ref_format_usage[] = +"git check-ref-format [--print] \n" +" or: git check-ref-format --branch "; + int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { if (argc == 3 && !strcmp(argv[1], "--branch")) { @@ -18,6 +22,6 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) exit(0); } if (argc != 2) - usage("git check-ref-format refname"); + usage(builtin_check_ref_format_usage); return !!check_ref_format(argv[1]); } From d629c40b0b8b63b0c8b33d52dcba0ed202d898d7 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:58 -0600 Subject: [PATCH 283/884] merge: do not setup worktree twice Builtins do not need to run setup_worktree() for themselves, since the builtin machinery runs it for them. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-merge.c | 1 - 1 file changed, 1 deletion(-) diff --git a/builtin-merge.c b/builtin-merge.c index b6b84286b2..e95c5dc717 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -840,7 +840,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix) const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list **remotes = &remoteheads; - setup_work_tree(); if (file_exists(git_path("MERGE_HEAD"))) die("You have not concluded your merge. (MERGE_HEAD exists)"); if (read_cache_unmerged()) From f01d74960363736caaf4d98d39555e99bdc72c25 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:05:00 -0600 Subject: [PATCH 284/884] http-fetch: add missing initialization of argv0_path According to c6dfb39 (remote-curl: add missing initialization of argv0_path, 2009-10-13), programs with "main" must call this to work correctly on MinGW. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- http-fetch.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/http-fetch.c b/http-fetch.c index e8f44babd9..88f7dc8845 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "exec_cmd.h" #include "walker.h" int main(int argc, const char **argv) @@ -19,8 +20,8 @@ int main(int argc, const char **argv) int get_verbosely = 0; int get_recover = 0; + git_extract_argv0_path(argv[0]); prefix = setup_git_directory(); - git_config(git_default_config, NULL); while (arg < argc && argv[arg][0] == '-') { From aeda85a81547b05c36939defb420e5f02bf05e51 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:48 -0600 Subject: [PATCH 285/884] Show usage string for 'git check-ref-format -h' This only changes the behavior of "git check-ref-format -h" without any other options and arguments. This change cannot be breaking backward compatibility, since any valid refname must contain a /. Most existing scripts use arguments such as "heads/$foo". If some script checks the refname "-h" alone, git check-ref-format will still exit with nonzero status, and the only detrimental side-effect will be a usage string sent to stderr. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-check-ref-format.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c index e3e7bdf52f..0a576afd0e 100644 --- a/builtin-check-ref-format.c +++ b/builtin-check-ref-format.c @@ -31,6 +31,9 @@ static void collapse_slashes(char *dst, const char *src) int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(builtin_check_ref_format_usage); + if (argc == 3 && !strcmp(argv[1], "--branch")) { struct strbuf sb = STRBUF_INIT; From 71a04a8b52b8a89addd31da5b2272d572fdc7e40 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:49 -0600 Subject: [PATCH 286/884] Show usage string for 'git fast-import -h' Let "git fast-import -h" (with no other arguments) print usage before exiting, even when run outside any repository. Cc: Shawn O. Pearce Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- fast-import.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fast-import.c b/fast-import.c index 6faaaacb68..f4f1de6dd7 100644 --- a/fast-import.c +++ b/fast-import.c @@ -2405,6 +2405,9 @@ int main(int argc, const char **argv) git_extract_argv0_path(argv[0]); + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(fast_import_usage); + setup_git_directory(); git_config(git_pack_config, NULL); if (!pack_compression_seen && core_compression_seen) From e9dd085d93fb6695f1d28572b48374ef818cf9c3 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:50 -0600 Subject: [PATCH 287/884] Show usage string for 'git get-tar-commit-id -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-tar-tree.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 8b3a35e12d..3f1e7012db 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -11,6 +11,9 @@ static const char tar_tree_usage[] = "git tar-tree [--remote=] [basedir]\n" "*** Note that this command is now deprecated; use \"git archive\" instead."; +static const char builtin_get_tar_commit_id_usage[] = +"git get-tar-commit-id < "; + int cmd_tar_tree(int argc, const char **argv, const char *prefix) { /* @@ -81,6 +84,9 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) char *content = buffer + RECORDSIZE; ssize_t n; + if (argc != 1) + usage(builtin_get_tar_commit_id_usage); + n = read_in_full(0, buffer, HEADERSIZE); if (n < HEADERSIZE) die("git get-tar-commit-id: read error"); From 9a2861e32a147d89f22961bd7d3257b8776de4d2 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:51 -0600 Subject: [PATCH 288/884] Show usage string for 'git imap-send -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- imap-send.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/imap-send.c b/imap-send.c index 3847fd151d..04e537406e 100644 --- a/imap-send.c +++ b/imap-send.c @@ -93,6 +93,8 @@ struct msg_data { unsigned int crlf:1; }; +static const char imap_send_usage[] = "git imap-send < "; + #define DRV_OK 0 #define DRV_MSG_BAD -1 #define DRV_BOX_BAD -2 @@ -1491,6 +1493,9 @@ int main(int argc, char **argv) git_extract_argv0_path(argv[0]); + if (argc != 1) + usage(imap_send_usage); + /* init the random number generator */ arc4_init(); From aa481d38b059a093829bbe0745e29244a896c499 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:52 -0600 Subject: [PATCH 289/884] Show usage string for 'git mailsplit -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-mailsplit.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c index dfe5b151e6..207e358ed1 100644 --- a/builtin-mailsplit.c +++ b/builtin-mailsplit.c @@ -231,6 +231,8 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix) continue; } else if ( arg[1] == 'f' ) { nr = strtol(arg+2, NULL, 10); + } else if ( arg[1] == 'h' ) { + usage(git_mailsplit_usage); } else if ( arg[1] == 'b' && !arg[2] ) { allow_bare = 1; } else if (!strcmp(arg, "--keep-cr")) { From ae5bdda36cddeb837dc43804966bc99baa385772 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:53 -0600 Subject: [PATCH 290/884] Show usage string for 'git merge-one-file -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- git-merge-one-file.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index 9c2c1b7202..d067894bf4 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -16,6 +16,18 @@ # been handled already by git read-tree, but that one doesn't # do any merges that might change the tree layout. +USAGE=' ' +USAGE="$USAGE " +LONG_USAGE="Usage: git merge-one-file $USAGE + +Blob ids and modes should be empty for missing files." + +if ! test "$#" -eq 7 +then + echo "$LONG_USAGE" + exit 1 +fi + case "${1:-.}${2:-.}${3:-.}" in # # Deleted in both or deleted in one and unchanged in the other From 7006b5beceb296e7e853806622204b2b55649fc5 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:54 -0600 Subject: [PATCH 291/884] Show usage string for 'git rev-parse -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-rev-parse.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 45bead6545..24ee8b3242 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -426,6 +426,13 @@ static void die_no_single_rev(int quiet) die("Needed a single revision"); } +static const char builtin_rev_parse_usage[] = +"git rev-parse --parseopt [options] -- [...]\n" +" or: git rev-parse --sq-quote [...]\n" +" or: git rev-parse [options] [...]\n" +"\n" +"Run \"git rev-parse --parseopt -h\" for more information on the first usage."; + int cmd_rev_parse(int argc, const char **argv, const char *prefix) { int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0; @@ -438,6 +445,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (argc > 1 && !strcmp("--sq-quote", argv[1])) return cmd_sq_quote(argc - 2, argv + 2); + if (argc > 1 && !strcmp("-h", argv[1])) + usage(builtin_rev_parse_usage); + prefix = setup_git_directory(); git_config(git_default_config, NULL); for (i = 1; i < argc; i++) { From 03c5c10263fb0d80151d108a4c61253e1d0a8de6 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:55 -0600 Subject: [PATCH 292/884] Show usage string for 'git show-index -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- show-index.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/show-index.c b/show-index.c index 45bb535773..63f9da5323 100644 --- a/show-index.c +++ b/show-index.c @@ -1,6 +1,9 @@ #include "cache.h" #include "pack.h" +static const char show_index_usage[] = +"git show-index < "; + int main(int argc, char **argv) { int i; @@ -8,6 +11,8 @@ int main(int argc, char **argv) unsigned int version; static unsigned int top_index[256]; + if (argc != 1) + usage(show_index_usage); if (fread(top_index, 2 * 4, 1, stdin) != 1) die("unable to read header"); if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) { From 1507301204180a17f915b929fc8377e69aef979d Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:56 -0600 Subject: [PATCH 293/884] Show usage string for 'git unpack-file -h' "unpack-file -h" could be asking to save the contents of a blob named "-h". Strictly speaking, such a pathological ref name is possible, but the user would have to had said something like "tags/-h" to name such a pathological ref already. When used in scripts, unpack-file is typically not passed a user-supplied tag name directly. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- unpack-file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unpack-file.c b/unpack-file.c index ac9cbf7cd8..e9d8934691 100644 --- a/unpack-file.c +++ b/unpack-file.c @@ -28,7 +28,7 @@ int main(int argc, char **argv) git_extract_argv0_path(argv[0]); - if (argc != 2) + if (argc != 2 || !strcmp(argv[1], "-h")) usage("git unpack-file "); if (get_sha1(argv[1], sha1)) die("Not a valid object name %s", argv[1]); From 4751f11224600ab25adb0a200fec55a734bc2936 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:57 -0600 Subject: [PATCH 294/884] Show usage string for 'git stripspace -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-stripspace.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/builtin-stripspace.c b/builtin-stripspace.c index 1fd2205d53..4d3b93fedb 100644 --- a/builtin-stripspace.c +++ b/builtin-stripspace.c @@ -73,9 +73,11 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) struct strbuf buf = STRBUF_INIT; int strip_comments = 0; - if (argc > 1 && (!strcmp(argv[1], "-s") || + if (argc == 2 && (!strcmp(argv[1], "-s") || !strcmp(argv[1], "--strip-comments"))) strip_comments = 1; + else if (argc > 1) + usage("git stripspace [-s | --strip-comments] < "); if (strbuf_read(&buf, 0, 1024) < 0) die_errno("could not read the input"); From 616f86d71325da1ce692f8e878fe066758f88554 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:59 -0600 Subject: [PATCH 295/884] Let 'git http-fetch -h' show usage outside any git repository Delay search for a git directory until option parsing has finished. None of the functions used in option parsing look for or read any files other than stdin, so this is safe. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- http-fetch.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/http-fetch.c b/http-fetch.c index 88f7dc8845..ffd0ad7e29 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -2,6 +2,9 @@ #include "exec_cmd.h" #include "walker.h" +static const char http_fetch_usage[] = "git http-fetch " +"[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url"; + int main(int argc, const char **argv) { const char *prefix; @@ -21,8 +24,6 @@ int main(int argc, const char **argv) int get_recover = 0; git_extract_argv0_path(argv[0]); - prefix = setup_git_directory(); - git_config(git_default_config, NULL); while (arg < argc && argv[arg][0] == '-') { if (argv[arg][1] == 't') { @@ -38,6 +39,8 @@ int main(int argc, const char **argv) } else if (argv[arg][1] == 'w') { write_ref = &argv[arg + 1]; arg++; + } else if (argv[arg][1] == 'h') { + usage(http_fetch_usage); } else if (!strcmp(argv[arg], "--recover")) { get_recover = 1; } else if (!strcmp(argv[arg], "--stdin")) { @@ -45,10 +48,8 @@ int main(int argc, const char **argv) } arg++; } - if (argc < arg + 2 - commits_on_stdin) { - usage("git http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url"); - return 1; - } + if (argc != arg + 2 - commits_on_stdin) + usage(http_fetch_usage); if (commits_on_stdin) { commits = walker_targets_stdin(&commit_id, &write_ref); } else { @@ -56,6 +57,11 @@ int main(int argc, const char **argv) commits = 1; } url = argv[arg]; + + prefix = setup_git_directory(); + + git_config(git_default_config, NULL); + if (url && url[strlen(url)-1] != '/') { rewritten_url = xmalloc(strlen(url)+2); strcpy(rewritten_url, url); From 02bc5b03f54b51f8b45c81cce74284ced602e6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Sat, 7 Nov 2009 10:51:56 +0100 Subject: [PATCH 296/884] format-patch: Always generate a patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Jeff King recently reinstated -p to suppress the default diffstat (as -p used to work before 68daa64, about 14 months ago). However, -p is also needed in combination with certain options (e.g. --stat or --numstat) in order to produce any patch at all. The documentation does not mention this. Since the purpose of format-patch is to produce a patch that can be emailed, it does not make sense that certain combination of options will suppress the generation of the patch itself. Therefore: * Update 'git format-patch' to always generate a patch. * Since the --name-only, --name-status, and --check suppresses the generation of the patch, disallow those options, and remove the description of them in the documentation. * Remove the reference to -p in the description of -U. * Remove the descriptions of the options that are synonyms for -p plus another option (--patch-with-raw and --patch-with-stat). * While at it, slightly tweak the description of -p itself to say that it generates "plain patches", so that you can think of -p as "plain patch" as an mnemonic aid. Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 16 +++++++++++++--- builtin-log.c | 23 ++++++++++++++++------- t/t4014-format-patch.sh | 18 ++++++++++++++++++ 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 9276faeb11..c58d0853fc 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -14,7 +14,7 @@ endif::git-format-patch[] ifdef::git-format-patch[] -p:: - Generate patches without diffstat. + Generate plain patches without any diffstats. endif::git-format-patch[] ifndef::git-format-patch[] @@ -27,14 +27,19 @@ endif::git-format-patch[] -U:: --unified=:: Generate diffs with lines of context instead of - the usual three. Implies "-p". + the usual three. +ifndef::git-format-patch[] + Implies "-p". +endif::git-format-patch[] --raw:: Generate the raw format. {git-diff-core? This is the default.} +ifndef::git-format-patch[] --patch-with-raw:: Synonym for "-p --raw". +endif::git-format-patch[] --patience:: Generate a diff using the "patience diff" algorithm. @@ -71,21 +76,24 @@ endif::git-format-patch[] Output a condensed summary of extended header information such as creations, renames and mode changes. +ifndef::git-format-patch[] --patch-with-stat:: Synonym for "-p --stat". - {git-format-patch? This is the default.} +endif::git-format-patch[] -z:: NUL-line termination on output. This affects the --raw output field terminator. Also output from commands such as "git-log" will be delimited with NUL between commits. +ifndef::git-format-patch[] --name-only:: Show only names of changed files. --name-status:: Show only names and status of changed files. See the description of the `--diff-filter` option on what the status letters mean. +endif::git-format-patch[] --color:: Show colored diff. @@ -115,11 +123,13 @@ override configuration settings. Turn off rename detection, even when the configuration file gives the default to do so. +ifndef::git-format-patch[] --check:: Warn if changes introduce trailing whitespace or an indent that uses a space before a tab. Exits with non-zero status if problems are found. Not compatible with --exit-code. +endif::git-format-patch[] --full-index:: Instead of the first handful of characters, show the full diff --git a/builtin-log.c b/builtin-log.c index 7b91c91423..ce7ab810c8 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -921,10 +921,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback }, OPT_BOOLEAN(0, "no-binary", &no_binary_diff, "don't output binary diffs"), - OPT_BOOLEAN('p', NULL, &use_patch_format, - "show patch format instead of default (patch + stat)"), OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream, "don't include a patch matching a commit upstream"), + OPT_BOOLEAN('p', NULL, &use_patch_format, + "show patch format instead of default (patch + stat)"), OPT_GROUP("Messaging"), { OPTION_CALLBACK, 0, "add-header", NULL, "header", "add email header", PARSE_OPT_NONEG, @@ -1030,11 +1030,20 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (argc > 1) die ("unrecognized argument: %s", argv[1]); - if (use_patch_format) - rev.diffopt.output_format |= DIFF_FORMAT_PATCH; - else if (!rev.diffopt.output_format || - rev.diffopt.output_format == DIFF_FORMAT_PATCH) - rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH; + if (rev.diffopt.output_format & DIFF_FORMAT_NAME) + die("--name-only does not make sense"); + if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS) + die("--name-status does not make sense"); + if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF) + die("--check does not make sense"); + + if (!use_patch_format && + (!rev.diffopt.output_format || + rev.diffopt.output_format == DIFF_FORMAT_PATCH)) + rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY; + + /* Always generate a patch */ + rev.diffopt.output_format |= DIFF_FORMAT_PATCH; if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff) DIFF_OPT_SET(&rev.diffopt, BINARY); diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index cab6ce2e97..5689d590fd 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -536,4 +536,22 @@ test_expect_success 'format-patch --signoff' ' grep "^Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" ' +echo "fatal: --name-only does not make sense" > expect.name-only +echo "fatal: --name-status does not make sense" > expect.name-status +echo "fatal: --check does not make sense" > expect.check + +test_expect_success 'options no longer allowed for format-patch' ' + test_must_fail git format-patch --name-only 2> output && + test_cmp expect.name-only output && + test_must_fail git format-patch --name-status 2> output && + test_cmp expect.name-status output && + test_must_fail git format-patch --check 2> output && + test_cmp expect.check output' + +test_expect_success 'format-patch --numstat should produce a patch' ' + git format-patch --numstat --stdout master..side | + grep "^diff --git a/" | + wc -l | + xargs test 6 = ' + test_done From d4cb003fffe2af3b868f57d75a352714e7ab03ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Sat, 7 Nov 2009 10:52:29 +0100 Subject: [PATCH 297/884] format-patch documentation: Remove diff options that are not useful MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To simplify reading the documentation for format-patch, remove the description of common diff options that are not useful for the purpose of the command (i.e. "Prepare patches for e-mail submission"). Specifically, this removes the description of the following options: --raw -z --color --no-color --color-words --diff-filter -S --pickaxe-all --pickaxe-regex -R --relative --exit-code --quiet Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index c58d0853fc..a03f1a793c 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -32,9 +32,11 @@ ifndef::git-format-patch[] Implies "-p". endif::git-format-patch[] +ifndef::git-format-patch[] --raw:: Generate the raw format. {git-diff-core? This is the default.} +endif::git-format-patch[] ifndef::git-format-patch[] --patch-with-raw:: @@ -81,19 +83,18 @@ ifndef::git-format-patch[] Synonym for "-p --stat". endif::git-format-patch[] +ifndef::git-format-patch[] -z:: NUL-line termination on output. This affects the --raw output field terminator. Also output from commands such as "git-log" will be delimited with NUL between commits. -ifndef::git-format-patch[] --name-only:: Show only names of changed files. --name-status:: Show only names and status of changed files. See the description of the `--diff-filter` option on what the status letters mean. -endif::git-format-patch[] --color:: Show colored diff. @@ -118,6 +119,7 @@ The regex can also be set via a diff driver or configuration option, see linkgit:gitattributes[1] or linkgit:git-config[1]. Giving it explicitly overrides any diff driver or configuration setting. Diff drivers override configuration settings. +endif::git-format-patch[] --no-renames:: Turn off rename detection, even when the configuration @@ -157,6 +159,7 @@ endif::git-format-patch[] -C:: Detect copies as well as renames. See also `--find-copies-harder`. +ifndef::git-format-patch[] --diff-filter=[ACDMRTUXB*]:: Select only files that are Added (`A`), Copied (`C`), Deleted (`D`), Modified (`M`), Renamed (`R`), have their @@ -168,6 +171,7 @@ endif::git-format-patch[] paths are selected if there is any file that matches other criteria in the comparison; if there is no file that matches other criteria, nothing is selected. +endif::git-format-patch[] --find-copies-harder:: For performance reasons, by default, `-C` option finds copies only @@ -185,6 +189,7 @@ endif::git-format-patch[] the number of rename/copy targets exceeds the specified number. +ifndef::git-format-patch[] -S:: Look for differences that introduce or remove an instance of . Note that this is different than the string simply @@ -199,11 +204,13 @@ endif::git-format-patch[] --pickaxe-regex:: Make the not a plain string but an extended POSIX regex to match. +endif::git-format-patch[] -O:: Output the patch in the order specified in the , which has one shell glob pattern per line. +ifndef::git-format-patch[] -R:: Swap two inputs; that is, show differences from index or on-disk file to tree contents. @@ -215,6 +222,7 @@ endif::git-format-patch[] not in a subdirectory (e.g. in a bare repository), you can name which subdirectory to make the output relative to by giving a as an argument. +endif::git-format-patch[] -a:: --text:: @@ -239,6 +247,7 @@ endif::git-format-patch[] Show the context between diff hunks, up to the specified number of lines, thereby fusing hunks that are close to each other. +ifndef::git-format-patch[] --exit-code:: Make the program exit with codes similar to diff(1). That is, it exits with 1 if there were differences and @@ -246,6 +255,7 @@ endif::git-format-patch[] --quiet:: Disable all output of the program. Implies --exit-code. +endif::git-format-patch[] --ext-diff:: Allow an external diff helper to be executed. If you set an From dce5ef142046e107e20aa03ca5a353032bbf9653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Sat, 7 Nov 2009 10:53:07 +0100 Subject: [PATCH 298/884] format-patch documentation: Fix formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Format git commands and options consistently using back quotes (i.e. a fixed font in the resulting HTML document). Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 36 +++++++++++------------ Documentation/git-format-patch.txt | 46 +++++++++++++++--------------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index a03f1a793c..9398329706 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -29,7 +29,7 @@ endif::git-format-patch[] Generate diffs with lines of context instead of the usual three. ifndef::git-format-patch[] - Implies "-p". + Implies `-p`. endif::git-format-patch[] ifndef::git-format-patch[] @@ -40,7 +40,7 @@ endif::git-format-patch[] ifndef::git-format-patch[] --patch-with-raw:: - Synonym for "-p --raw". + Synonym for `-p --raw`. endif::git-format-patch[] --patience:: @@ -48,19 +48,19 @@ endif::git-format-patch[] --stat[=width[,name-width]]:: Generate a diffstat. You can override the default - output width for 80-column terminal by "--stat=width". + output width for 80-column terminal by `--stat=width`. The width of the filename part can be controlled by giving another width to it separated by a comma. --numstat:: - Similar to \--stat, but shows number of added and + Similar to `\--stat`, but shows number of added and deleted lines in decimal notation and pathname without abbreviation, to make it more machine friendly. For binary files, outputs two `-` instead of saying `0 0`. --shortstat:: - Output only the last line of the --stat format containing total + Output only the last line of the `--stat` format containing total number of modified files, as well as number of added and deleted lines. @@ -68,11 +68,11 @@ endif::git-format-patch[] Output the distribution of relative amount of changes (number of lines added or removed) for each sub-directory. Directories with changes below a cut-off percent (3% by default) are not shown. The cut-off percent - can be set with "--dirstat=limit". Changes in a child directory is not - counted for the parent directory, unless "--cumulative" is used. + can be set with `--dirstat=limit`. Changes in a child directory is not + counted for the parent directory, unless `--cumulative` is used. --dirstat-by-file[=limit]:: - Same as --dirstat, but counts changed files instead of lines. + Same as `--dirstat`, but counts changed files instead of lines. --summary:: Output a condensed summary of extended header information @@ -80,14 +80,14 @@ endif::git-format-patch[] ifndef::git-format-patch[] --patch-with-stat:: - Synonym for "-p --stat". + Synonym for `-p --stat`. endif::git-format-patch[] ifndef::git-format-patch[] -z:: - NUL-line termination on output. This affects the --raw + NUL-line termination on output. This affects the `--raw` output field terminator. Also output from commands such - as "git-log" will be delimited with NUL between commits. + as `git-log` will be delimited with NUL between commits. --name-only:: Show only names of changed files. @@ -139,16 +139,16 @@ endif::git-format-patch[] line when generating patch format output. --binary:: - In addition to --full-index, output "binary diff" that - can be applied with "git apply". + In addition to `--full-index`, output a binary diff that + can be applied with `git-apply`. --abbrev[=]:: Instead of showing the full 40-byte hexadecimal object name in diff-raw format output and diff-tree header lines, show only a partial prefix. This is - independent of --full-index option above, which controls + independent of the `--full-index` option above, which controls the diff-patch output format. Non default number of - digits can be specified with --abbrev=. + digits can be specified with `--abbrev=`. -B:: Break complete rewrite changes into pairs of delete and create. @@ -183,7 +183,7 @@ endif::git-format-patch[] `-C` option has the same effect. -l:: - -M and -C options require O(n^2) processing time where n + The `-M` and `-C` options require O(n^2) processing time where n is the number of potential rename/copy targets. This option prevents rename/copy detection from running if the number of rename/copy targets exceeds the specified @@ -197,7 +197,7 @@ ifndef::git-format-patch[] linkgit:gitdiffcore[7] for more details. --pickaxe-all:: - When -S finds a change, show all the changes in that + When `-S` finds a change, show all the changes in that changeset, not just the files that contain the change in . @@ -254,7 +254,7 @@ ifndef::git-format-patch[] 0 means no differences. --quiet:: - Disable all output of the program. Implies --exit-code. + Disable all output of the program. Implies `--exit-code`. endif::git-format-patch[] --ext-diff:: diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 687e667598..f1fd0df08a 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -43,28 +43,28 @@ There are two ways to specify which commits to operate on. The first rule takes precedence in the case of a single . To apply the second rule, i.e., format everything since the beginning of -history up until , use the '\--root' option: "git format-patch -\--root ". If you want to format only itself, you -can do this with "git format-patch -1 ". +history up until , use the '\--root' option: `git format-patch +\--root `. If you want to format only itself, you +can do this with `git format-patch -1 `. By default, each output file is numbered sequentially from 1, and uses the first line of the commit message (massaged for pathname safety) as -the filename. With the --numbered-files option, the output file names +the filename. With the `--numbered-files` option, the output file names will only be numbers, without the first line of the commit appended. The names of the output files are printed to standard -output, unless the --stdout option is specified. +output, unless the `--stdout` option is specified. -If -o is specified, output files are created in . Otherwise +If `-o` is specified, output files are created in . Otherwise they are created in the current working directory. By default, the subject of a single patch is "[PATCH] First Line" and the subject when multiple patches are output is "[PATCH n/m] First -Line". To force 1/1 to be added for a single patch, use -n. To omit -patch numbers from the subject, use -N +Line". To force 1/1 to be added for a single patch, use `-n`. To omit +patch numbers from the subject, use `-N`. -If given --thread, 'git-format-patch' will generate In-Reply-To and -References headers to make the second and subsequent patch mails appear -as replies to the first mail; this also generates a Message-Id header to +If given `--thread`, `git-format-patch` will generate `In-Reply-To` and +`References` headers to make the second and subsequent patch mails appear +as replies to the first mail; this also generates a `Message-Id` header to reference. OPTIONS @@ -112,7 +112,7 @@ include::diff-options.txt[] --attach[=]:: Create multipart/mixed attachment, the first part of which is the commit message and the patch itself in the - second part, with "Content-Disposition: attachment". + second part, with `Content-Disposition: attachment`. --no-attach:: Disable the creation of an attachment, overriding the @@ -121,13 +121,13 @@ include::diff-options.txt[] --inline[=]:: Create multipart/mixed attachment, the first part of which is the commit message and the patch itself in the - second part, with "Content-Disposition: inline". + second part, with `Content-Disposition: inline`. --thread[=