From d7de00f7e0d2374cb7933d2ee1ebe5273a8acf53 Mon Sep 17 00:00:00 2001 From: Timo Hirvonen Date: Sat, 24 Jun 2006 20:26:49 +0300 Subject: --name-only, --name-status, --check and -s are mutually exclusive Signed-off-by: Timo Hirvonen Signed-off-by: Junio C Hamano --- diff.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/diff.c b/diff.c index 1c131ff4dc..5358fd0c14 100644 --- a/diff.c +++ b/diff.c @@ -1433,6 +1433,19 @@ void diff_setup(struct diff_options *options) int diff_setup_done(struct diff_options *options) { + int count = 0; + + if (options->output_format & DIFF_FORMAT_NAME) + count++; + if (options->output_format & DIFF_FORMAT_NAME_STATUS) + count++; + if (options->output_format & DIFF_FORMAT_CHECKDIFF) + count++; + if (options->output_format & DIFF_FORMAT_NO_OUTPUT) + count++; + if (count > 1) + die("--name-only, --name-status, --check and -s are mutually exclusive"); + if ((options->find_copies_harder && options->detect_rename != DIFF_DETECT_COPY) || (0 <= options->rename_limit && !options->detect_rename)) -- cgit v1.2.3 From 5faf64cd28bf79f0c2939717d2ba117498717059 Mon Sep 17 00:00:00 2001 From: Timo Hirvonen Date: Sat, 24 Jun 2006 20:28:42 +0300 Subject: Remove awkward compatibility warts Signed-off-by: Timo Hirvonen Signed-off-by: Junio C Hamano --- builtin-diff-files.c | 7 ------- builtin-diff.c | 7 ------- 2 files changed, 14 deletions(-) diff --git a/builtin-diff-files.c b/builtin-diff-files.c index a655eea91e..3361898ada 100644 --- a/builtin-diff-files.c +++ b/builtin-diff-files.c @@ -47,12 +47,5 @@ int cmd_diff_files(int argc, const char **argv, char **envp) if (rev.pending.nr || rev.min_age != -1 || rev.max_age != -1) usage(diff_files_usage); - /* - * Backward compatibility wart - "diff-files -s" used to - * defeat the common diff option "-s" which asked for - * DIFF_FORMAT_NO_OUTPUT. - */ - if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) - rev.diffopt.output_format = DIFF_FORMAT_RAW; return run_diff_files(&rev, silent); } diff --git a/builtin-diff.c b/builtin-diff.c index d520c7ca29..059eb6de49 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -56,13 +56,6 @@ static int builtin_diff_files(struct rev_info *revs, if (revs->max_count < 0 && (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) revs->combine_merges = revs->dense_combined_merges = 1; - /* - * Backward compatibility wart - "diff-files -s" used to - * defeat the common diff option "-s" which asked for - * DIFF_FORMAT_NO_OUTPUT. - */ - if (revs->diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) - revs->diffopt.output_format = DIFF_FORMAT_RAW; return run_diff_files(revs, silent); } -- cgit v1.2.3 From f8a2c0d14f5326e157b12ec549b864ddb492621d Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Wed, 5 Jul 2006 22:56:37 +1000 Subject: gitk: Allow the user to set some colors This makes the colors for the diff old/new lines and hunk headers configurable, as well as the background and foreground (text color) of the various panes. There is now a GUI in the edit->preferences window to set them. Signed-off-by: Paul Mackerras --- gitk | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 101 insertions(+), 23 deletions(-) diff --git a/gitk b/gitk index ba4644f450..d1adb9de47 100755 --- a/gitk +++ b/gitk @@ -386,6 +386,7 @@ proc makewindow {} { global rowctxmenu mergemax wrapcomment global highlight_files gdttype global searchstring sstring + global bgcolor fgcolor bglist fglist diffcolors menu .bar .bar add cascade -label "File" -menu .bar.file @@ -446,18 +447,19 @@ proc makewindow {} { .ctop add .ctop.top set canv .ctop.top.clist.canv canvas $canv -height $geometry(canvh) -width $geometry(canv1) \ - -bg white -bd 0 \ + -background $bgcolor -bd 0 \ -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll" .ctop.top.clist add $canv set canv2 .ctop.top.clist.canv2 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \ - -bg white -bd 0 -yscrollincr $linespc + -background $bgcolor -bd 0 -yscrollincr $linespc .ctop.top.clist add $canv2 set canv3 .ctop.top.clist.canv3 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \ - -bg white -bd 0 -yscrollincr $linespc + -background $bgcolor -bd 0 -yscrollincr $linespc .ctop.top.clist add $canv3 bind .ctop.top.clist {resizeclistpanes %W %w} + lappend bglist $canv $canv2 $canv3 set sha1entry .ctop.top.bar.sha1 set entries $sha1entry @@ -563,19 +565,22 @@ proc makewindow {} { trace add variable searchstring write incrsearch pack $sstring -side left -expand 1 -fill x set ctext .ctop.cdet.left.ctext - text $ctext -bg white -state disabled -font $textfont \ + text $ctext -background $bgcolor -foreground $fgcolor \ + -state disabled -font $textfont \ -width $geometry(ctextw) -height $geometry(ctexth) \ -yscrollcommand scrolltext -wrap none scrollbar .ctop.cdet.left.sb -command "$ctext yview" pack .ctop.cdet.left.sb -side right -fill y pack $ctext -side left -fill both -expand 1 .ctop.cdet add .ctop.cdet.left + lappend bglist $ctext + lappend fglist $ctext $ctext tag conf comment -wrap $wrapcomment $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa" - $ctext tag conf hunksep -fore blue - $ctext tag conf d0 -fore red - $ctext tag conf d1 -fore "#00a000" + $ctext tag conf hunksep -fore [lindex $diffcolors 2] + $ctext tag conf d0 -fore [lindex $diffcolors 0] + $ctext tag conf d1 -fore [lindex $diffcolors 1] $ctext tag conf m0 -fore red $ctext tag conf m1 -fore blue $ctext tag conf m2 -fore green @@ -608,11 +613,15 @@ proc makewindow {} { pack .ctop.cdet.right.mode -side top -fill x set cflist .ctop.cdet.right.cfiles set indent [font measure $mainfont "nn"] - text $cflist -width $geometry(cflistw) -background white -font $mainfont \ + text $cflist -width $geometry(cflistw) \ + -background $bgcolor -foreground $fgcolor \ + -font $mainfont \ -tabs [list $indent [expr {2 * $indent}]] \ -yscrollcommand ".ctop.cdet.right.sb set" \ -cursor [. cget -cursor] \ -spacing1 1 -spacing3 1 + lappend bglist $cflist + lappend fglist $cflist scrollbar .ctop.cdet.right.sb -command "$cflist yview" pack .ctop.cdet.right.sb -side right -fill y pack $cflist -side left -fill both -expand 1 @@ -747,6 +756,7 @@ proc savestuff {w} { global maxwidth showneartags global viewname viewfiles viewargs viewperm nextviewnum global cmitmode wrapcomment + global colors bgcolor fgcolor diffcolors if {$stuffsaved} return if {![winfo viewable .]} return @@ -761,6 +771,10 @@ proc savestuff {w} { puts $f [list set cmitmode $cmitmode] puts $f [list set wrapcomment $wrapcomment] puts $f [list set showneartags $showneartags] + puts $f [list set bgcolor $bgcolor] + puts $f [list set fgcolor $fgcolor] + puts $f [list set colors $colors] + puts $f [list set diffcolors $diffcolors] puts $f "set geometry(width) [winfo width .ctop]" puts $f "set geometry(height) [winfo height .ctop]" puts $f "set geometry(canv1) [expr {[winfo width $canv]-2}]" @@ -2870,11 +2884,11 @@ proc drawlines {id} { } proc drawcmittext {id row col rmx} { - global linespc canv canv2 canv3 canvy0 + global linespc canv canv2 canv3 canvy0 fgcolor global commitlisted commitinfo rowidlist global rowtextx idpos idtags idheads idotherrefs global linehtag linentag linedtag - global mainfont canvxmax boldrows boldnamerows + global mainfont canvxmax boldrows boldnamerows fgcolor set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}] set x [xc $row $col] @@ -2882,7 +2896,7 @@ proc drawcmittext {id row col rmx} { set orad [expr {$linespc / 3}] set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \ [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \ - -fill $ofill -outline black -width 1] + -fill $ofill -outline $fgcolor -width 1 -tags circle] $canv raise $t $canv bind $t <1> {selcanvline {} %x %y} set xt [xc $row [llength [lindex $rowidlist $row]]] @@ -2910,13 +2924,13 @@ proc drawcmittext {id row col rmx} { lappend nfont bold } } - set linehtag($row) [$canv create text $xt $y -anchor w \ - -text $headline -font $font] + set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \ + -text $headline -font $font -tags text] $canv bind $linehtag($row) "rowmenu %X %Y $id" - set linentag($row) [$canv2 create text 3 $y -anchor w \ - -text $name -font $nfont] - set linedtag($row) [$canv3 create text 3 $y -anchor w \ - -text $date -font $mainfont] + set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \ + -text $name -font $nfont -tags text] + set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \ + -text $date -font $mainfont -tags text] set xr [expr {$xt + [font measure $mainfont $headline]}] if {$xr > $canvxmax} { set canvxmax $xr @@ -3138,7 +3152,7 @@ proc bindline {t id} { proc drawtags {id x xt y1} { global idtags idheads idotherrefs global linespc lthickness - global canv mainfont commitrow rowtextx curview + global canv mainfont commitrow rowtextx curview fgcolor set marks {} set ntags 0 @@ -3201,8 +3215,8 @@ proc drawtags {id x xt y1} { -width 0 -fill "#ffddaa" -tags tag.$id } } - set t [$canv create text $xl $y1 -anchor w -text $tag \ - -font $mainfont -tags tag.$id] + set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \ + -font $mainfont -tags [list tag.$id text]] if {$ntags >= 0} { $canv bind $t <1> [list showtag $tag 1] } @@ -3223,10 +3237,11 @@ proc xcoord {i level ln} { } proc show_status {msg} { - global canv mainfont + global canv mainfont fgcolor clear_display - $canv create text 3 3 -anchor nw -text $msg -font $mainfont -tags textitems + $canv create text 3 3 -anchor nw -text $msg -font $mainfont \ + -tags text -fill $fgcolor } proc finishcommits {} { @@ -4574,7 +4589,8 @@ proc linehover {} { set t [$canv create rectangle $x0 $y0 $x1 $y1 \ -fill \#ffff80 -outline black -width 1 -tags hover] $canv raise $t - set t [$canv create text $x $y -anchor nw -text $text -tags hover -font $mainfont] + set t [$canv create text $x $y -anchor nw -text $text -tags hover \ + -font $mainfont] $canv raise $t } @@ -5242,6 +5258,7 @@ proc doquit {} { proc doprefs {} { global maxwidth maxgraphpct diffopts global oldprefs prefstop showneartags + global bgcolor fgcolor ctext diffcolors set top .gitkprefs set prefstop $top @@ -5265,6 +5282,7 @@ proc doprefs {} { -font optionfont spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct grid x $top.maxpctl $top.maxpct -sticky w + label $top.ddisp -text "Diff display options" grid $top.ddisp - -sticky w -pady 10 label $top.diffoptl -text "Options for diff program" \ @@ -5276,6 +5294,34 @@ proc doprefs {} { checkbutton $top.ntag.b -variable showneartags pack $top.ntag.b $top.ntag.l -side left grid x $top.ntag -sticky w + + label $top.cdisp -text "Colors: press to choose" + grid $top.cdisp - -sticky w -pady 10 + label $top.bg -padx 40 -relief sunk -background $bgcolor + button $top.bgbut -text "Background" -font optionfont \ + -command [list choosecolor bgcolor 0 $top.bg background setbg] + grid x $top.bgbut $top.bg -sticky w + label $top.fg -padx 40 -relief sunk -background $fgcolor + button $top.fgbut -text "Foreground" -font optionfont \ + -command [list choosecolor fgcolor 0 $top.fg 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 "Diff: old lines" -font optionfont \ + -command [list choosecolor diffcolors 0 $top.diffold "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 "Diff: new lines" -font optionfont \ + -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \ + [list $ctext tag conf d1 -foreground]] + grid x $top.diffnewbut $top.diffnew -sticky w + label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2] + button $top.hunksepbut -text "Diff: hunk header" -font optionfont \ + -command [list choosecolor diffcolors 2 $top.hunksep \ + "diff hunk header" \ + [list $ctext tag conf hunksep -foreground]] + grid x $top.hunksepbut $top.hunksep -sticky w + frame $top.buts button $top.buts.ok -text "OK" -command prefsok button $top.buts.can -text "Cancel" -command prefscan @@ -5285,6 +5331,35 @@ proc doprefs {} { grid $top.buts - - -pady 10 -sticky ew } +proc choosecolor {v vi w x cmd} { + global $v + + set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \ + -title "Gitk: choose color for $x"] + if {$c eq {}} return + $w conf -background $c + lset $v $vi $c + eval $cmd $c +} + +proc setbg {c} { + global bglist + + foreach w $bglist { + $w conf -background $c + } +} + +proc setfg {c} { + global fglist canv + + foreach w $fglist { + $w conf -foreground $c + } + allcanvs itemconf text -fill $c + $canv itemconf circle -outline $c +} + proc prefscan {} { global maxwidth maxgraphpct diffopts global oldprefs prefstop showneartags @@ -5620,6 +5695,9 @@ set wrapcomment "none" set showneartags 1 set colors {green red blue magenta darkgrey brown orange} +set bgcolor white +set fgcolor black +set diffcolors {red "#00a000" blue} catch {source ~/.gitk} -- cgit v1.2.3 From 8a48571ce52e5182ea32ed168e298882f8c470b3 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Thu, 6 Jul 2006 10:21:23 +1000 Subject: gitk: Show the currently checked-out head in bold font Signed-off-by: Paul Mackerras --- gitk | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/gitk b/gitk index d1adb9de47..7d540c1b05 100755 --- a/gitk +++ b/gitk @@ -312,7 +312,7 @@ proc getcommit {id} { proc readrefs {} { global tagids idtags headids idheads tagcontents - global otherrefids idotherrefs + global otherrefids idotherrefs mainhead foreach v {tagids idtags headids idheads otherrefids idotherrefs} { catch {unset $v} @@ -358,6 +358,13 @@ proc readrefs {} { } } close $refd + set mainhead {} + catch { + set thehead [exec git symbolic-ref HEAD] + if {[string match "refs/heads/*" $thehead]} { + set mainhead [string range $thehead 11 end] + } + } } proc show_error {w top msg} { @@ -3150,9 +3157,9 @@ proc bindline {t id} { } proc drawtags {id x xt y1} { - global idtags idheads idotherrefs + global idtags idheads idotherrefs mainhead global linespc lthickness - global canv mainfont commitrow rowtextx curview fgcolor + global canv mainfont commitrow rowtextx curview fgcolor bgcolor set marks {} set ntags 0 @@ -3177,8 +3184,14 @@ proc drawtags {id x xt y1} { set yb [expr {$yt + $linespc - 1}] set xvals {} set wvals {} + set i -1 foreach tag $marks { - set wid [font measure $mainfont $tag] + incr i + if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} { + set wid [font measure [concat $mainfont bold] $tag] + } else { + set wid [font measure $mainfont $tag] + } lappend xvals $xt lappend wvals $wid set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}] @@ -3189,6 +3202,7 @@ proc drawtags {id x xt y1} { foreach tag $marks x $xvals wid $wvals { set xl [expr {$x + $delta}] set xr [expr {$x + $delta + $wid + $lthickness}] + set font $mainfont if {[incr ntags -1] >= 0} { # draw a tag set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \ @@ -3200,6 +3214,9 @@ proc drawtags {id x xt y1} { # draw a head or other ref if {[incr nheads -1] >= 0} { set col green + if {$tag eq $mainhead} { + lappend font bold + } } else { set col "#ddddff" } @@ -3216,7 +3233,7 @@ proc drawtags {id x xt y1} { } } set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \ - -font $mainfont -tags [list tag.$id text]] + -font $font -tags [list tag.$id text]] if {$ntags >= 0} { $canv bind $t <1> [list showtag $tag 1] } -- cgit v1.2.3 From 6ece0d3030ae2dc5b49fcfcc83df4966a52e0d0b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 5 Jul 2006 17:41:39 -0700 Subject: upload-pack: use object pointer not copy of sha1 to keep track of has/needs. This does not change what happens in the command in any way, but is to prepare for further work. Signed-off-by: Junio C Hamano --- upload-pack.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/upload-pack.c b/upload-pack.c index b18eb9ba0d..d98d59f921 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -18,8 +18,8 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n #define MAX_NEEDS 256 static int nr_has = 0, nr_needs = 0, multi_ack = 0, nr_our_refs = 0; static int use_thin_pack = 0; -static unsigned char has_sha1[MAX_HAS][20]; -static unsigned char needs_sha1[MAX_NEEDS][20]; +static struct object *has_tip[MAX_HAS]; +static struct object *needs_tip[MAX_NEEDS]; static unsigned int timeout = 0; static int use_sideband = 0; @@ -119,7 +119,7 @@ static void create_pack_file(void) else { for (i = 0; i < nr_needs; i++) { *p++ = buf; - memcpy(buf, sha1_to_hex(needs_sha1[i]), 41); + memcpy(buf, sha1_to_hex(needs_tip[i]->sha1), 41); buf += 41; } } @@ -127,7 +127,7 @@ static void create_pack_file(void) for (i = 0; i < nr_has; i++) { *p++ = buf; *buf++ = '^'; - memcpy(buf, sha1_to_hex(has_sha1[i]), 41); + memcpy(buf, sha1_to_hex(has_tip[i]->sha1), 41); buf += 41; } *p++ = NULL; @@ -336,7 +336,7 @@ static int got_sha1(char *hex, unsigned char *sha1) parents = parents->next) parents->item->object.flags |= THEY_HAVE; } - memcpy(has_sha1[nr_has++], sha1, 20); + has_tip[nr_has++] = o; } return 1; } @@ -395,23 +395,21 @@ static int receive_needs(void) needs = 0; for (;;) { struct object *o; - unsigned char dummy[20], *sha1_buf; + unsigned char sha1_buf[20]; len = packet_read_line(0, line, sizeof(line)); reset_timeout(); if (!len) return needs; - sha1_buf = dummy; if (needs == MAX_NEEDS) { fprintf(stderr, "warning: supporting only a max of %d requests. " "sending everything instead.\n", MAX_NEEDS); } - else if (needs < MAX_NEEDS) - sha1_buf = needs_sha1[needs]; - if (strncmp("want ", line, 5) || get_sha1_hex(line+5, sha1_buf)) + if (strncmp("want ", line, 5) || + 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")) @@ -434,6 +432,8 @@ static int receive_needs(void) die("git-upload-pack: not our ref %s", line+5); if (!(o->flags & WANTED)) { o->flags |= WANTED; + if (needs < MAX_NEEDS) + needs_tip[needs] = o; needs++; } } -- cgit v1.2.3 From b1e9fff7e76c13cc16f0b4a4eda19600320bd7e7 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 5 Jul 2006 18:00:02 -0700 Subject: upload-pack: lift MAX_NEEDS and MAX_HAS limitation By using object_array data structure, lift the old limitation of MAX_HAS/MAX_NEEDS. While we are at it, rename the variables that hold the objects we use to compute common ancestor to match the message used at the protocol level. What the other end has and we also do are "have"s, and what the other end asks for are "want"s. Signed-off-by: Junio C Hamano --- upload-pack.c | 93 ++++++++++++++++++++++++++--------------------------------- 1 file changed, 41 insertions(+), 52 deletions(-) diff --git a/upload-pack.c b/upload-pack.c index d98d59f921..5a911d6fb5 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -14,12 +14,10 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n #define THEY_HAVE (1U << 0) #define OUR_REF (1U << 1) #define WANTED (1U << 2) -#define MAX_HAS 256 -#define MAX_NEEDS 256 -static int nr_has = 0, nr_needs = 0, multi_ack = 0, nr_our_refs = 0; +static int multi_ack = 0, nr_our_refs = 0; static int use_thin_pack = 0; -static struct object *has_tip[MAX_HAS]; -static struct object *needs_tip[MAX_NEEDS]; +static struct object_array have_obj; +static struct object_array want_obj; static unsigned int timeout = 0; static int use_sideband = 0; @@ -79,7 +77,7 @@ static void create_pack_file(void) */ int lp_pipe[2], pu_pipe[2], pe_pipe[2]; pid_t pid_rev_list, pid_pack_objects; - int create_full_pack = (nr_our_refs == nr_needs && !nr_has); + int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr); char data[8193], progress[128]; char abort_msg[] = "aborting due to possible repository " "corruption on the remote side."; @@ -103,7 +101,7 @@ static void create_pack_file(void) use_thin_pack = 0; /* no point doing it */ } else - args = nr_has + nr_needs + 5; + args = have_obj.nr + want_obj.nr + 5; p = xmalloc(args * sizeof(char *)); argv = (const char **) p; buf = xmalloc(args * 45); @@ -114,20 +112,22 @@ static void create_pack_file(void) close(lp_pipe[1]); *p++ = "rev-list"; *p++ = use_thin_pack ? "--objects-edge" : "--objects"; - if (create_full_pack || MAX_NEEDS <= nr_needs) + if (create_full_pack) *p++ = "--all"; else { - for (i = 0; i < nr_needs; i++) { + for (i = 0; i < want_obj.nr; i++) { + struct object *o = want_obj.objects[i].item; *p++ = buf; - memcpy(buf, sha1_to_hex(needs_tip[i]->sha1), 41); + memcpy(buf, sha1_to_hex(o->sha1), 41); buf += 41; } } if (!create_full_pack) - for (i = 0; i < nr_has; i++) { + for (i = 0; i < have_obj.nr; i++) { + struct object *o = have_obj.objects[i].item; *p++ = buf; *buf++ = '^'; - memcpy(buf, sha1_to_hex(has_tip[i]->sha1), 41); + memcpy(buf, sha1_to_hex(o->sha1), 41); buf += 41; } *p++ = NULL; @@ -316,28 +316,29 @@ static void create_pack_file(void) static int got_sha1(char *hex, unsigned char *sha1) { + struct object *o; + if (get_sha1_hex(hex, sha1)) die("git-upload-pack: expected SHA1 object, got '%s'", hex); if (!has_sha1_file(sha1)) return 0; - if (nr_has < MAX_HAS) { - struct object *o = lookup_object(sha1); - if (!(o && o->parsed)) - o = parse_object(sha1); - if (!o) - die("oops (%s)", sha1_to_hex(sha1)); - if (o->type == TYPE_COMMIT) { - struct commit_list *parents; - if (o->flags & THEY_HAVE) - return 0; - o->flags |= THEY_HAVE; - for (parents = ((struct commit*)o)->parents; - parents; - parents = parents->next) - parents->item->object.flags |= THEY_HAVE; - } - has_tip[nr_has++] = o; + + o = lookup_object(sha1); + if (!(o && o->parsed)) + o = parse_object(sha1); + if (!o) + die("oops (%s)", sha1_to_hex(sha1)); + if (o->type == TYPE_COMMIT) { + struct commit_list *parents; + if (o->flags & THEY_HAVE) + return 0; + o->flags |= THEY_HAVE; + for (parents = ((struct commit*)o)->parents; + parents; + parents = parents->next) + parents->item->object.flags |= THEY_HAVE; } + add_object_array(o, NULL, &have_obj); return 1; } @@ -355,26 +356,24 @@ static int get_common_commits(void) reset_timeout(); if (!len) { - if (nr_has == 0 || multi_ack) + if (have_obj.nr == 0 || multi_ack) packet_write(1, "NAK\n"); continue; } len = strip(line, len); if (!strncmp(line, "have ", 5)) { if (got_sha1(line+5, sha1) && - (multi_ack || nr_has == 1)) { - if (nr_has >= MAX_HAS) - multi_ack = 0; + (multi_ack || have_obj.nr == 1)) { packet_write(1, "ACK %s%s\n", - sha1_to_hex(sha1), - multi_ack ? " continue" : ""); + sha1_to_hex(sha1), + multi_ack ? " continue" : ""); if (multi_ack) memcpy(last_sha1, sha1, 20); } continue; } if (!strcmp(line, "done")) { - if (nr_has > 0) { + if (have_obj.nr > 0) { if (multi_ack) packet_write(1, "ACK %s\n", sha1_to_hex(last_sha1)); @@ -387,26 +386,18 @@ static int get_common_commits(void) } } -static int receive_needs(void) +static void receive_needs(void) { static char line[1000]; - int len, needs; + int len; - needs = 0; for (;;) { struct object *o; unsigned char sha1_buf[20]; len = packet_read_line(0, line, sizeof(line)); reset_timeout(); if (!len) - return needs; - - if (needs == MAX_NEEDS) { - fprintf(stderr, - "warning: supporting only a max of %d requests. " - "sending everything instead.\n", - MAX_NEEDS); - } + return; if (strncmp("want ", line, 5) || get_sha1_hex(line+5, sha1_buf)) @@ -432,9 +423,7 @@ static int receive_needs(void) die("git-upload-pack: not our ref %s", line+5); if (!(o->flags & WANTED)) { o->flags |= WANTED; - if (needs < MAX_NEEDS) - needs_tip[needs] = o; - needs++; + add_object_array(o, NULL, &want_obj); } } } @@ -470,8 +459,8 @@ static int upload_pack(void) head_ref(send_ref); for_each_ref(send_ref); packet_flush(1); - nr_needs = receive_needs(); - if (!nr_needs) + receive_needs(); + if (!want_obj.nr) return 0; get_common_commits(); create_pack_file(); -- cgit v1.2.3 From a0764cb838c2f1885fb58ca794c21523fb05c825 Mon Sep 17 00:00:00 2001 From: Matthias Lederhofer Date: Tue, 18 Jul 2006 19:14:51 +0200 Subject: upload-pack: fix timeout in create_pack_file Signed-off-by: Matthias Lederhofer Signed-off-by: Junio C Hamano --- upload-pack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/upload-pack.c b/upload-pack.c index b18eb9ba0d..638e257c94 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -178,6 +178,8 @@ static void create_pack_file(void) ssize_t sz; int pe, pu, pollsize; + reset_timeout(); + pollsize = 0; pe = pu = -1; -- cgit v1.2.3 From bb6b8e4f877e8b59b3756960e29d1955072dab56 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 17 Jul 2006 15:04:47 -0700 Subject: sha1_file.c: expose map_sha1_file() interface. This exposes map_sha1_file() interface to mmap a loose object file, and legacy_loose_object() function, split from unpack_sha1_header(). They will be used in the next patch to reuse the deflated data from new-style loose object files when generating packs. Signed-off-by: Junio C Hamano --- cache.h | 2 ++ sha1_file.c | 35 ++++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/cache.h b/cache.h index eee5fc9f8d..01835ef432 100644 --- a/cache.h +++ b/cache.h @@ -240,6 +240,8 @@ extern int move_temp_to_file(const char *tmpfile, char *filename); extern int has_sha1_pack(const unsigned char *sha1); extern int has_sha1_file(const unsigned char *sha1); +extern void *map_sha1_file(const unsigned char *sha1, unsigned long *); +extern int legacy_loose_object(unsigned char *); extern int has_pack_file(const unsigned char *sha1); extern int has_pack_index(const unsigned char *sha1); diff --git a/sha1_file.c b/sha1_file.c index 43bc2ea0cf..51e07d5a45 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -646,8 +646,7 @@ int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long siz return memcmp(sha1, real_sha1, 20) ? -1 : 0; } -static void *map_sha1_file_internal(const unsigned char *sha1, - unsigned long *size) +void *map_sha1_file(const unsigned char *sha1, unsigned long *size) { struct stat st; void *map; @@ -684,10 +683,26 @@ static void *map_sha1_file_internal(const unsigned char *sha1, return map; } +int legacy_loose_object(unsigned char *map) +{ + unsigned int word; + + /* + * Is it a zlib-compressed buffer? If so, the first byte + * must be 0x78 (15-bit window size, deflated), and the + * first 16-bit word is evenly divisible by 31 + */ + word = (map[0] << 8) + map[1]; + if (map[0] == 0x78 && !(word % 31)) + return 1; + else + return 0; +} + static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz) { unsigned char c; - unsigned int word, bits; + unsigned int bits; unsigned long size; static const char *typename[8] = { NULL, /* OBJ_EXT */ @@ -703,13 +718,7 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon stream->next_out = buffer; stream->avail_out = bufsiz; - /* - * Is it a zlib-compressed buffer? If so, the first byte - * must be 0x78 (15-bit window size, deflated), and the - * first 16-bit word is evenly divisible by 31 - */ - word = (map[0] << 8) + map[1]; - if (map[0] == 0x78 && !(word % 31)) { + if (legacy_loose_object(map)) { inflateInit(stream); return inflate(stream, 0); } @@ -1246,7 +1255,7 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep z_stream stream; char hdr[128]; - map = map_sha1_file_internal(sha1, &mapsize); + map = map_sha1_file(sha1, &mapsize); if (!map) { struct pack_entry e; @@ -1291,7 +1300,7 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size if (find_pack_entry(sha1, &e)) return read_packed_sha1(sha1, type, size); - map = map_sha1_file_internal(sha1, &mapsize); + map = map_sha1_file(sha1, &mapsize); if (map) { buf = unpack_sha1_file(map, mapsize, type, size); munmap(map, mapsize); @@ -1629,7 +1638,7 @@ int write_sha1_to_fd(int fd, const unsigned char *sha1) { int retval; unsigned long objsize; - void *buf = map_sha1_file_internal(sha1, &objsize); + void *buf = map_sha1_file(sha1, &objsize); if (buf) { retval = write_buffer(fd, buf, objsize); -- cgit v1.2.3 From ceec1361ebbc4613813ba42976503f5e304cfacf Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 17 Jul 2006 15:06:23 -0700 Subject: pack-objects: reuse deflated data from new-style loose objects. When packing an object without deltifying, if the data is stored in a loose object that is encoded with a new style header, copy it without inflating and deflating. Signed-off-by: Junio C Hamano --- pack-objects.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pack-objects.c b/pack-objects.c index 861c7f08ff..e52e9774e5 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -269,6 +269,22 @@ static unsigned long write_object(struct sha1file *f, * and we do not need to deltify it. */ + if (!entry->in_pack && !entry->delta) { + unsigned char *map; + unsigned long mapsize; + map = map_sha1_file(entry->sha1, &mapsize); + if (map && !legacy_loose_object(map)) { + /* We can copy straight into the pack file */ + sha1write(f, map, mapsize); + munmap(map, mapsize); + written++; + reused++; + return mapsize; + } + if (map) + munmap(map, mapsize); + } + if (! to_reuse) { buf = read_sha1_file(entry->sha1, type, &size); if (!buf) -- cgit v1.2.3 From 8e27364128b091e76059229d4f009bc7f869fa9f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 17 Jul 2006 15:10:10 -0700 Subject: unpack-objects: read configuration data upon startup. With this, unpack-objects will write out the loose objects with new-style headers when core.legacyheaders configuration is set to false. One unfortunate thing is that we still need inflate/deflate cycle when unpacking, even for objects in the pack stream that are not deltified, because it is not possible to determine the boundary of objects in the encoded stream cheaply without inflating it first. Signed-off-by: Junio C Hamano --- unpack-objects.c | 1 + 1 file changed, 1 insertion(+) diff --git a/unpack-objects.c b/unpack-objects.c index 48c1ee7968..b23fe58ce1 100644 --- a/unpack-objects.c +++ b/unpack-objects.c @@ -266,6 +266,7 @@ int main(int argc, char **argv) unsigned char sha1[20]; setup_git_directory(); + git_config(git_default_config); quiet = !isatty(2); -- cgit v1.2.3 From 5be01bc8d5cf9c63827311ad76dd8b66353253e1 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 29 Jul 2006 22:43:40 +0200 Subject: gitweb: whitespace cleanup Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 58 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index e5fca63b9c..323dfca3f3 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -36,7 +36,7 @@ our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; # location for temporary files needed for diffs our $git_temp = "/tmp/gitweb"; if (! -d $git_temp) { - mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp"); + mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp"); } # target of the home link on top of all pages @@ -104,7 +104,7 @@ if (defined $project) { die_error(undef, "No such project."); } $rss_link = ""; + "$my_uri?" . esc_param("p=$project;a=rss") . "\" type=\"application/rss+xml\"/>"; $ENV{'GIT_DIR'} = "$projectroot/$project"; } else { git_project_list(); @@ -303,7 +303,7 @@ sub git_header_html { } else { $content_type = 'text/html'; } - print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); + print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); print < @@ -1138,17 +1138,17 @@ sub git_summary { "\n" . ""; if (defined($comment)) { - print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, esc_html($comment)); + print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, esc_html($comment)); } print "\n" . ""; if ($tag{'type'} eq "tag") { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | "; + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | "; } print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); if ($tag{'reftype'} eq "commit") { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); } print "\n" . ""; @@ -1457,17 +1457,17 @@ sub git_tags { "\n" . ""; if (defined($comment)) { - print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, $comment); + print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, $comment); } print "\n" . ""; if ($tag{'type'} eq "tag") { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | "; + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | "; } print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); if ($tag{'reftype'} eq "commit") { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); } print "\n" . ""; @@ -1613,13 +1613,14 @@ sub git_blob_plain_mimetype { sub git_blob_plain { if (!defined $hash) { - if (defined $file_name) { - my $base = $hash_base || git_read_head($project); - $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file."); - } else { - die_error(undef, "No file name defined."); - } - } + if (defined $file_name) { + my $base = $hash_base || git_read_head($project); + $hash = git_get_hash_by_path($base, $file_name, "blob") + or die_error(undef, "Error lookup file."); + } else { + die_error(undef, "No file name defined."); + } + } my $type = shift; open my $fd, "-|", "$GIT cat-file blob $hash" or die_error("Couldn't cat $file_name, $hash"); @@ -1644,13 +1645,14 @@ sub git_blob_plain { sub git_blob { if (!defined $hash) { - if (defined $file_name) { - my $base = $hash_base || git_read_head($project); - $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file."); - } else { - die_error(undef, "No file name defined."); - } - } + if (defined $file_name) { + my $base = $hash_base || git_read_head($project); + $hash = git_get_hash_by_path($base, $file_name, "blob") + or die_error(undef, "Error lookup file."); + } else { + die_error(undef, "No file name defined."); + } + } my $have_blame = git_get_project_config_bool ('blame'); open my $fd, "-|", "$GIT cat-file blob $hash" or die_error(undef, "Open failed."); my $mimetype = git_blob_plain_mimetype($fd, $file_name); @@ -1677,7 +1679,7 @@ sub git_blob { print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain") . "
\n"; } print "\n". - "
" . + "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "
\n"; } else { @@ -2305,7 +2307,7 @@ sub git_commitdiff { my $status = $5; my $file = validate_input(unquote($6)); if ($status eq "A") { - print "
" . file_type($to_mode) . ":" . + print "
" . file_type($to_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id) . "(new)" . "
\n"; git_diff_print(undef, "/dev/null", $to_id, "b/$file"); @@ -2359,7 +2361,7 @@ sub git_commitdiff_plain { "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n". "Subject: $co{'title'}\n"; if (defined $tagname) { - print "X-Git-Tag: $tagname\n"; + print "X-Git-Tag: $tagname\n"; } print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" . "\n"; -- cgit v1.2.3 From b9182987a80f7e820cbe1f8c7c4dc26f8586e8cd Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 30 Jul 2006 18:28:34 -0700 Subject: gitweb: Use list for of open for running git commands, thorougly. Use list form of open for running git commands and reading their output through pipe, for example open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash instead of open my $fd, "-|", "$GIT rev-list --header --parents $hash" Single letter options use ' instead of " as quotes, according to style used in list form of magic "-|" open. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 323dfca3f3..8f7341f458 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -388,7 +388,7 @@ sub die_error { sub git_get_type { my $hash = shift; - open my $fd, "-|", "$GIT cat-file -t $hash" or return; + open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return; my $type = <$fd>; close $fd or return; chomp $type; @@ -440,7 +440,7 @@ sub git_read_tag { my %tag; my @comment; - open my $fd, "-|", "$GIT cat-file tag $tag_id" or return; + open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return; $tag{'id'} = $tag_id; while (my $line = <$fd>) { chomp $line; @@ -512,7 +512,7 @@ sub git_read_commit { @commit_lines = @$commit_text; } else { $/ = "\0"; - open my $fd, "-|", "$GIT rev-list --header --parents --max-count=1 $commit_id" or return; + open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return; @commit_lines = split '\n', <$fd>; close $fd or return; $/ = "\n"; @@ -610,7 +610,7 @@ sub git_diff_print { if (defined $from) { $from_tmp = "$git_temp/gitweb_" . $$ . "_from"; open my $fd2, "> $from_tmp"; - open my $fd, "-|", "$GIT cat-file blob $from"; + open my $fd, "-|", $GIT, "cat-file", "blob", $from; my @file = <$fd>; print $fd2 @file; close $fd2; @@ -621,7 +621,7 @@ sub git_diff_print { if (defined $to) { $to_tmp = "$git_temp/gitweb_" . $$ . "_to"; open my $fd2, "> $to_tmp"; - open my $fd, "-|", "$GIT cat-file blob $to"; + open my $fd, "-|", $GIT, "cat-file", "blob", $to; my @file = <$fd>; print $fd2 @file; close $fd2; @@ -1062,7 +1062,8 @@ sub git_summary { "owner$owner\n" . "last change$cd{'rfc2822'}\n" . "\n"; - open my $fd, "-|", "$GIT rev-list --max-count=17 " . git_read_head($project) or die_error(undef, "Open failed."); + open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) + or die_error(undef, "Open failed."); my (@revlist) = map { chomp; $_ } <$fd>; close $fd; print "
\n" . @@ -1526,7 +1527,7 @@ sub git_get_hash_by_path { my $tree = $base; my @parts = split '/', $path; while (my $part = shift @parts) { - open my $fd, "-|", "$GIT ls-tree $tree" or die_error(undef, "Open git-ls-tree failed."); + open my $fd, "-|", $GIT, "ls-tree", $tree or die_error(undef, "Open git-ls-tree failed."); my (@entries) = map { chomp; $_ } <$fd>; close $fd or return undef; foreach my $line (@entries) { @@ -1622,7 +1623,7 @@ sub git_blob_plain { } } my $type = shift; - open my $fd, "-|", "$GIT cat-file blob $hash" or die_error("Couldn't cat $file_name, $hash"); + open my $fd, "-|", $GIT, "cat-file", "blob", $hash or die_error("Couldn't cat $file_name, $hash"); $type ||= git_blob_plain_mimetype($fd, $file_name); @@ -1654,7 +1655,7 @@ sub git_blob { } } my $have_blame = git_get_project_config_bool ('blame'); - open my $fd, "-|", "$GIT cat-file blob $hash" or die_error(undef, "Open failed."); + open my $fd, "-|", $GIT, "cat-file", "blob", $hash or die_error(undef, "Open failed."); my $mimetype = git_blob_plain_mimetype($fd, $file_name); if ($mimetype !~ m/^text\//) { close $fd; @@ -1718,7 +1719,7 @@ sub git_tree { } } $/ = "\0"; - open my $fd, "-|", "$GIT ls-tree -z $hash" or die_error(undef, "Open git-ls-tree failed."); + open my $fd, "-|", $GIT, "ls-tree", '-z', $hash or die_error(undef, "Open git-ls-tree failed."); chomp (my (@entries) = <$fd>); close $fd or die_error(undef, "Reading tree failed."); $/ = "\n"; @@ -1799,7 +1800,8 @@ sub git_tree { sub git_rss { # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ - open my $fd, "-|", "$GIT rev-list --max-count=150 " . git_read_head($project) or die_error(undef, "Open failed."); + open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) + or die_error(undef, "Open failed."); my (@revlist) = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading rev-list failed."); print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); @@ -1819,7 +1821,7 @@ sub git_rss { last; } my %cd = date_str($co{'committer_epoch'}); - open $fd, "-|", "$GIT diff-tree -r $co{'parent'} $co{'id'}" or next; + open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next; my @difftree = map { chomp; $_ } <$fd>; close $fd or next; print "\n" . @@ -1907,7 +1909,7 @@ sub git_log { " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "
\n"; my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed."); + open my $fd, "-|", $GIT, "rev-list", $limit, $hash or die_error(undef, "Open failed."); my (@revlist) = map { chomp; $_ } <$fd>; close $fd; @@ -1992,15 +1994,14 @@ sub git_commit { my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); my @difftree; - my $root = ""; my $parent = $co{'parent'}; if (!defined $parent) { - $root = " --root"; - $parent = ""; + $parent = "--root"; } - open my $fd, "-|", "$GIT diff-tree -r -M $root $parent $hash" or die_error(undef, "Open failed."); + open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash + or die_error(undef, "Open failed."); @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading diff-tree failed."); + close $fd or die_error(undef, "Reading git-diff-tree failed."); # non-textual hash id's can be cached my $expires; @@ -2244,7 +2245,8 @@ sub git_commitdiff { if (!defined $hash_parent) { $hash_parent = $co{'parent'}; } - open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed."); + open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash + or die_error(undef, "Open failed."); my (@difftree) = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading diff-tree failed."); @@ -2334,14 +2336,15 @@ sub git_commitdiff { sub git_commitdiff_plain { mkdir($git_temp, 0700); - open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed."); + open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash + or die_error(undef, "Open failed."); my (@difftree) = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading diff-tree failed."); # try to figure out the next tag after this commit my $tagname; my $refs = read_info_ref("tags"); - open $fd, "-|", "$GIT rev-list HEAD"; + open $fd, "-|", $GIT, "rev-list", "HEAD"; chomp (my (@commits) = <$fd>); close $fd; foreach my $commit (@commits) { @@ -2419,7 +2422,7 @@ sub git_history { git_print_page_path($file_name, $ftype); open my $fd, "-|", - "$GIT rev-list --full-history $hash_base -- \'$file_name\'"; + $GIT, "rev-list", "--full-history", $hash_base, "--", "\'$file_name\'"; print "\n"; my $alternate = 0; while (my $line = <$fd>) { @@ -2506,7 +2509,7 @@ sub git_search { my $alternate = 0; if ($commit_search) { $/ = "\0"; - open my $fd, "-|", "$GIT rev-list --header --parents $hash" or next; + open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash or next; while (my $commit_text = <$fd>) { if (!grep m/$searchtext/i, $commit_text) { next; @@ -2627,7 +2630,7 @@ sub git_shortlog { " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "
\n"; my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed."); + open my $fd, "-|", $GIT, "rev-list", $limit, $hash or die_error(undef, "Open failed."); my (@revlist) = map { chomp; $_ } <$fd>; close $fd; -- cgit v1.2.3 From dda754f7d4fa1bccd9f3349d73304856144a7cd2 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 29 Jul 2006 22:55:01 +0200 Subject: gitweb: simplify git_get_hash_by_path Simplify git_get_hash_by_path by using git-ls-tree to do path limiting, instead of finding correct ttree and parsing unconstrained git-ls-tree output. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 8f7341f458..9c214f534b 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -1525,29 +1525,15 @@ sub git_get_hash_by_path { my $path = shift || return undef; my $tree = $base; - my @parts = split '/', $path; - while (my $part = shift @parts) { - open my $fd, "-|", $GIT, "ls-tree", $tree or die_error(undef, "Open git-ls-tree failed."); - my (@entries) = map { chomp; $_ } <$fd>; - close $fd or return undef; - foreach my $line (@entries) { - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; - my $t_mode = $1; - my $t_type = $2; - my $t_hash = $3; - my $t_name = validate_input(unquote($4)); - if ($t_name eq $part) { - if (!(@parts)) { - return $t_hash; - } - if ($t_type eq "tree") { - $tree = $t_hash; - } - last; - } - } - } + + open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path + or die_error(undef, "Open git-ls-tree failed."); + my $line = <$fd>; + close $fd or return undef; + + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + return $3; } sub mimetype_guess_file { -- cgit v1.2.3 From 044bfdc8cbc4955f20afd7f9a245dfc20527ecbd Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 29 Jul 2006 23:01:00 +0200 Subject: gitweb: More explicit error messages for open "-|" Use more explicit error messages when failing magical "-|" open, stating at least the name of the git command that failed. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 9c214f534b..bf3e2ca5d1 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -1063,7 +1063,7 @@ sub git_summary { "\n" . "
last change$cd{'rfc2822'}
\n"; open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) - or die_error(undef, "Open failed."); + or die_error(undef, "Open git-rev-list failed."); my (@revlist) = map { chomp; $_ } <$fd>; close $fd; print "
\n" . @@ -1271,7 +1271,7 @@ sub git_blame2 { die_error("400 Bad Request", "object is not a blob"); } open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base) - or die_error(undef, "Open failed"); + or die_error(undef, "Open git-blame failed."); git_header_html(); print "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . @@ -1333,7 +1333,7 @@ sub git_blame { or die_error(undef, "Error lookup file."); } open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base) - or die_error(undef, "Open failed."); + or die_error(undef, "Open git-annotate failed."); git_header_html(); print "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . @@ -1609,7 +1609,8 @@ sub git_blob_plain { } } my $type = shift; - open my $fd, "-|", $GIT, "cat-file", "blob", $hash or die_error("Couldn't cat $file_name, $hash"); + open my $fd, "-|", $GIT, "cat-file", "blob", $hash + or die_error("Couldn't cat $file_name, $hash"); $type ||= git_blob_plain_mimetype($fd, $file_name); @@ -1641,7 +1642,8 @@ sub git_blob { } } my $have_blame = git_get_project_config_bool ('blame'); - open my $fd, "-|", $GIT, "cat-file", "blob", $hash or die_error(undef, "Open failed."); + open my $fd, "-|", $GIT, "cat-file", "blob", $hash + or die_error(undef, "Couldn't cat $file_name, $hash."); my $mimetype = git_blob_plain_mimetype($fd, $file_name); if ($mimetype !~ m/^text\//) { close $fd; @@ -1705,7 +1707,8 @@ sub git_tree { } } $/ = "\0"; - open my $fd, "-|", $GIT, "ls-tree", '-z', $hash or die_error(undef, "Open git-ls-tree failed."); + open my $fd, "-|", $GIT, "ls-tree", '-z', $hash + or die_error(undef, "Open git-ls-tree failed."); chomp (my (@entries) = <$fd>); close $fd or die_error(undef, "Reading tree failed."); $/ = "\n"; @@ -1787,7 +1790,7 @@ sub git_tree { sub git_rss { # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) - or die_error(undef, "Open failed."); + or die_error(undef, "Open git-rev-list failed."); my (@revlist) = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading rev-list failed."); print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); @@ -1895,7 +1898,8 @@ sub git_log { " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "
\n"; my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", $GIT, "rev-list", $limit, $hash or die_error(undef, "Open failed."); + open my $fd, "-|", $GIT, "rev-list", $limit, $hash + or die_error(undef, "Open git-rev-list failed."); my (@revlist) = map { chomp; $_ } <$fd>; close $fd; @@ -1985,7 +1989,7 @@ sub git_commit { $parent = "--root"; } open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash - or die_error(undef, "Open failed."); + or die_error(undef, "Open git-diff-tree failed."); @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-diff-tree failed."); @@ -2232,7 +2236,7 @@ sub git_commitdiff { $hash_parent = $co{'parent'}; } open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash - or die_error(undef, "Open failed."); + or die_error(undef, "Open git-diff-tree failed."); my (@difftree) = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading diff-tree failed."); @@ -2323,7 +2327,7 @@ sub git_commitdiff { sub git_commitdiff_plain { mkdir($git_temp, 0700); open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash - or die_error(undef, "Open failed."); + or die_error(undef, "Open git-diff-tree failed."); my (@difftree) = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading diff-tree failed."); @@ -2616,7 +2620,8 @@ sub git_shortlog { " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "
\n"; my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", $GIT, "rev-list", $limit, $hash or die_error(undef, "Open failed."); + open my $fd, "-|", $GIT, "rev-list", $limit, $hash + or die_error(undef, "Open git-rev-list failed."); my (@revlist) = map { chomp; $_ } <$fd>; close $fd; -- cgit v1.2.3 From cefda27f741a948a78ce27180f5332c2d19199c4 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 30 Jul 2006 04:08:17 +0200 Subject: gitweb: Cleanup - chomp $line in consistent style Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index bf3e2ca5d1..e7ae65adfc 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -635,7 +635,7 @@ sub git_diff_print { $/ = "\n"; } else { while (my $line = <$fd>) { - chomp($line); + chomp $line; my $char = substr($line, 0, 1); my $diff_class = ""; if ($char eq '+') { @@ -944,7 +944,7 @@ sub read_info_ref { # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} open my $fd, "$projectroot/$project/info/refs" or return; while (my $line = <$fd>) { - chomp($line); + chomp $line; if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) { if (defined $refs{$1}) { $refs{$1} .= " / $2"; -- cgit v1.2.3 From 0881d2d1303a067a02e3811d43a1aed04a47945e Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 30 Jul 2006 14:58:11 +0200 Subject: gitweb: Cleanup - chomp @lines in consistent style Use 'my @lines = map { chomp; $_ } <$fd>;' form to read all lines of git command output into array without trailing newlines. It has advantage over 'chomp (my (@lines) = <$fd>);' in that it does not modify array. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index e7ae65adfc..98505551cd 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -1064,7 +1064,7 @@ sub git_summary { "\n"; open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) or die_error(undef, "Open git-rev-list failed."); - my (@revlist) = map { chomp; $_ } <$fd>; + my @revlist = map { chomp; $_ } <$fd>; close $fd; print "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog"), -class => "title"}, "shortlog") . @@ -1709,7 +1709,7 @@ sub git_tree { $/ = "\0"; open my $fd, "-|", $GIT, "ls-tree", '-z', $hash or die_error(undef, "Open git-ls-tree failed."); - chomp (my (@entries) = <$fd>); + my @entries = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading tree failed."); $/ = "\n"; @@ -1791,7 +1791,7 @@ sub git_rss { # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) or die_error(undef, "Open git-rev-list failed."); - my (@revlist) = map { chomp; $_ } <$fd>; + my @revlist = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading rev-list failed."); print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); print "\n". @@ -1900,7 +1900,7 @@ sub git_log { my $limit = sprintf("--max-count=%i", (100 * ($page+1))); open my $fd, "-|", $GIT, "rev-list", $limit, $hash or die_error(undef, "Open git-rev-list failed."); - my (@revlist) = map { chomp; $_ } <$fd>; + my @revlist = map { chomp; $_ } <$fd>; close $fd; if ($hash ne $head || $page) { @@ -1983,14 +1983,13 @@ sub git_commit { my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); - my @difftree; my $parent = $co{'parent'}; if (!defined $parent) { $parent = "--root"; } open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash or die_error(undef, "Open git-diff-tree failed."); - @difftree = map { chomp; $_ } <$fd>; + my @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-diff-tree failed."); # non-textual hash id's can be cached @@ -2237,7 +2236,7 @@ sub git_commitdiff { } open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash or die_error(undef, "Open git-diff-tree failed."); - my (@difftree) = map { chomp; $_ } <$fd>; + my @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading diff-tree failed."); # non-textual hash id's can be cached @@ -2328,14 +2327,14 @@ sub git_commitdiff_plain { mkdir($git_temp, 0700); open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash or die_error(undef, "Open git-diff-tree failed."); - my (@difftree) = map { chomp; $_ } <$fd>; + my @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading diff-tree failed."); # try to figure out the next tag after this commit my $tagname; my $refs = read_info_ref("tags"); open $fd, "-|", $GIT, "rev-list", "HEAD"; - chomp (my (@commits) = <$fd>); + my @commits = map { chomp; $_ } <$fd>; close $fd; foreach my $commit (@commits) { if (defined $refs->{$commit}) { @@ -2622,7 +2621,7 @@ sub git_shortlog { my $limit = sprintf("--max-count=%i", (100 * ($page+1))); open my $fd, "-|", $GIT, "rev-list", $limit, $hash or die_error(undef, "Open git-rev-list failed."); - my (@revlist) = map { chomp; $_ } <$fd>; + my @revlist = map { chomp; $_ } <$fd>; close $fd; if ($hash ne $head || $page) { -- cgit v1.2.3 From b18f9bf462cd37bc19368cb7d5c539e019d8fd67 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 30 Jul 2006 14:59:57 +0200 Subject: gitweb: Add git_page_nav for later use Adds git_page_nav subroutine to factor out the generation of the navigation bar. Based on Sven Verdoolaege code Message-Id: <20050618113121.GA13122@pc117b.liacs.nl> http://marc.theaimsgroup.com/?l=git&m=111909432415478&w=2 I tried for the refactored navbar generate the same result. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 98505551cd..d0d3f3efb1 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -385,6 +385,40 @@ sub die_error { exit; } +sub git_page_nav { + my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_; + $extra = '' if !defined $extra; # pager or formats + + my @navs = qw(summary shortlog log commit commitdiff tree); + if ($suppress) { + @navs = grep { $_ ne $suppress } @navs; + } + + my %arg = map { $_, ''} @navs; + if (defined $head) { + for (qw(commit commitdiff)) { + $arg{$_} = ";h=$head"; + } + if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) { + for (qw(shortlog log)) { + $arg{$_} = ";h=$head"; + } + } + } + $arg{tree} .= ";h=$treehead" if defined $treehead; + $arg{tree} .= ";hb=$treebase" if defined $treebase; + + print "
\n" . + (join " | ", + map { $_ eq $current + ? $_ + : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_") + } + @navs); + print "
$extra
\n" . + "
\n"; +} + sub git_get_type { my $hash = shift; -- cgit v1.2.3 From 0d83ddc40f356aa0b5618a6571f5146818ec2b41 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 30 Jul 2006 15:01:07 +0200 Subject: gitweb: Navbar refactoring - use git_page_nav to generate navigation bar Use git_page_nav subroutine to generate navigation bar. Additional navigation (either formats or pager/pagination) is put into variables. Corrects error in git_search where hash parameter was added to "summary" link instead of to "log" link. Might differ from previous version by additional "
" in navigation bar. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 235 +++++++++++++++--------------------------------------- 1 file changed, 66 insertions(+), 169 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index d0d3f3efb1..2b35763eef 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -1081,15 +1081,7 @@ sub git_summary { my $refs = read_info_ref(); git_header_html(); - print "
\n" . - "summary". - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree")}, "tree") . - "

\n" . - "
\n"; + git_page_nav('summary','', $head); print "
 
\n"; print "\n" . "\n" . @@ -1251,15 +1243,7 @@ sub git_print_page_path { sub git_tag { my $head = git_read_head($project); git_header_html(); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "
\n" . - "
\n" . - "
\n"; + git_page_nav('','', $head,undef,$head); my %tag = git_read_tag($hash); print "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($tag{'name'})) . "\n" . @@ -1307,19 +1291,13 @@ sub git_blame2 { open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base) or die_error(undef, "Open git-blame failed."); git_header_html(); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "
\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head") . "
\n"; - print "
\n". - "
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . - "
\n"; + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); + git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + print "
" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . + "
\n"; git_print_page_path($file_name, $ftype); my @rev_color = (qw(light dark)); my $num_colors = scalar(@rev_color); @@ -1369,19 +1347,13 @@ sub git_blame { open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base) or die_error(undef, "Open git-annotate failed."); git_header_html(); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "
\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head") . "
\n"; - print "
\n". - "
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . - "
\n"; + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); + git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + print "
" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . + "
\n"; git_print_page_path($file_name); print "
\n"; print <\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "
\n" . - "
\n" . - "
\n"; + git_page_nav('','', $head,undef,$head); my $taglist = git_read_refs("refs/tags"); print "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . @@ -1515,15 +1479,7 @@ sub git_tags { sub git_heads { my $head = git_read_head($project); git_header_html(); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "
\n" . - "
\n" . - "
\n"; + git_page_nav('','', $head,undef,$head); my $taglist = git_read_refs("refs/heads"); print "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . @@ -1684,25 +1640,20 @@ sub git_blob { return git_blob_plain($mimetype); } git_header_html(); + my $formats_nav = ''; if (defined $hash_base && (my %co = git_read_commit($hash_base))) { - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "
\n"; if (defined $file_name) { if ($have_blame) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | "; + $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | "; } - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head") . "
\n"; + $formats_nav .= + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head"); } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain") . "
\n"; + $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain"); } - print "
\n". - "
" . + git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + print "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "
\n"; } else { @@ -1757,15 +1708,7 @@ sub git_tree { my $base = ""; if (defined $hash_base && (my %co = git_read_commit($hash_base))) { $base_key = ";hb=$hash_base"; - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash_base")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash_base")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | tree" . - "

\n" . - "
\n"; + git_page_nav('tree','', $hash_base); print "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . "
\n"; @@ -1922,14 +1865,6 @@ sub git_log { $page = 0; } my $refs = read_info_ref(); - git_header_html(); - print "
\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . - " | log" . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "
\n"; my $limit = sprintf("--max-count=%i", (100 * ($page+1))); open my $fd, "-|", $GIT, "rev-list", $limit, $hash @@ -1937,25 +1872,28 @@ sub git_log { my @revlist = map { chomp; $_ } <$fd>; close $fd; + my $paging_nav = ''; if ($hash ne $head || $page) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "HEAD"); + $paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "HEAD"); } else { - print "HEAD"; + $paging_nav .= "HEAD"; } if ($page > 0) { - print " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev"); + $paging_nav .= " ⋅ " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev"); } else { - print " ⋅ prev"; + $paging_nav .= " ⋅ prev"; } if ($#revlist >= (100 * ($page+1)-1)) { - print " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next"); + $paging_nav .= " ⋅ " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next"); } else { - print " ⋅ next"; + $paging_nav .= " ⋅ next"; } - print "
\n" . - "
\n"; + + git_header_html(); + git_page_nav('log','', $hash,undef,undef, $paging_nav); + if (!@revlist) { print "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . @@ -2037,21 +1975,14 @@ sub git_commit { $ref = " " . esc_html($refs->{$co{'id'}}) . ""; } git_header_html(undef, $expires); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . - " | commit"; - if (defined $co{'parent'}) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff"); - } - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "\n" . - "
\n"; + my $formats_nav = ''; if (defined $file_name && defined $co{'parent'}) { my $parent = $co{'parent'}; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame") . "\n"; + $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame"); } - print "
\n"; + git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', + $hash, $co{'tree'}, $hash, + $formats_nav); if (defined $co{'parent'}) { print "
\n" . @@ -2223,16 +2154,9 @@ sub git_blobdiff { mkdir($git_temp, 0700); git_header_html(); if (defined $hash_base && (my %co = git_read_commit($hash_base))) { - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . - "
\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain") . - "
\n"; + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); + git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); print "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" . "
\n"; @@ -2284,15 +2208,9 @@ sub git_commitdiff { $ref = " " . esc_html($refs->{$co{'id'}}) . ""; } git_header_html(undef, $expires); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . - " | commitdiff" . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "
\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain") . "\n" . - "
\n"; + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); + git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); print "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . "
\n"; @@ -2424,15 +2342,7 @@ sub git_history { } my $refs = read_info_ref(); git_header_html(); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . - "

\n" . - "
\n"; + git_page_nav('','', $hash_base,$co{'tree'},$hash_base); print "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" . "
\n"; @@ -2515,15 +2425,7 @@ sub git_search { $pickaxe_search = 1; } git_header_html(); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary;h=$hash")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . - "

\n" . - "
\n"; + git_page_nav('','', $hash,$co{'tree'},$hash); print "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" . @@ -2643,14 +2545,6 @@ sub git_shortlog { $page = 0; } my $refs = read_info_ref(); - git_header_html(); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | shortlog" . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "
\n"; my $limit = sprintf("--max-count=%i", (100 * ($page+1))); open my $fd, "-|", $GIT, "rev-list", $limit, $hash @@ -2658,25 +2552,28 @@ sub git_shortlog { my @revlist = map { chomp; $_ } <$fd>; close $fd; + my $paging_nav = ''; if ($hash ne $head || $page) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "HEAD"); + $paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "HEAD"); } else { - print "HEAD"; + $paging_nav .= "HEAD"; } if ($page > 0) { - print " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev"); + $paging_nav .= " ⋅ " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev"); } else { - print " ⋅ prev"; + $paging_nav .= " ⋅ prev"; } if ($#revlist >= (100 * ($page+1)-1)) { - print " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next"); + $paging_nav .= " ⋅ " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next"); } else { - print " ⋅ next"; + $paging_nav .= " ⋅ next"; } - print "
\n" . - "
\n"; + + git_header_html(); + git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); + print "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . "
\n"; -- cgit v1.2.3 From 3dc1383290f9db3371a13ae8009ce4fcd5ffc93a Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 30 Jul 2006 15:02:27 +0200 Subject: gitweb: Replace form-feed character by ^L From 2be5cab10486cba804ccae063e93b146288054fe Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 30 Jul 2006 13:11:56 +0200 Subject: [PATCH] Replace FORM FEED (FF) character (014, 12, 0xc) by it's textual representation '^L'. This character is used for example in GNU GPL 'COPYING' file. With this patch "blob" output for COPYING passes XHTML validation. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 1 + 1 file changed, 1 insertion(+) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 2b35763eef..79275f38d3 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -250,6 +250,7 @@ sub esc_html { my $str = shift; $str = decode("utf8", $str, Encode::FB_DEFAULT); $str = escapeHTML($str); + $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) return $str; } -- cgit v1.2.3 From 7d91010df4d8c3d6f38734dc37cf7013a490670e Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 30 Jul 2006 17:47:22 +0200 Subject: gitweb: Show project descriptions with utf-8 characters in project list correctly Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 79275f38d3..673db1f639 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -958,7 +958,7 @@ sub git_project_list { } $alternate ^= 1; print "
\n" . - "\n" . + "\n" . "\n"; print "\n" . " - - - - - -HTML - } # while (my $line = <$fd>) - print "
description" . esc_html($descr) . "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "$pr->{'descr'}" . esc_html($pr->{'descr'}) . "" . chop_str($pr->{'owner'}, 15) . "{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "" . -- cgit v1.2.3 From 898a893fc9bd6510022e6248af39ca4f521e40d6 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 30 Jul 2006 16:14:43 +0200 Subject: gitweb: Add "\n" after
in git_page_nav Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 673db1f639..03002f04f3 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -416,7 +416,7 @@ sub git_page_nav { : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_") } @navs); - print "
$extra
\n" . + print "
\n$extra
\n" . "\n"; } -- cgit v1.2.3 From 43ffc06d98e944670b71869ba7126003597a2fa0 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 30 Jul 2006 17:49:00 +0200 Subject: gitweb: Pager refactoring - use git_get_paging_nav for pagination Add git_get_paging_nav subroutine which returns string with pager (paging nav) for shortlog and log actions. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 68 ++++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 03002f04f3..8e5e4c83c5 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -420,6 +420,36 @@ sub git_page_nav { "\n"; } +sub git_get_paging_nav { + my ($project, $action, $hash, $head, $page, $nrevs) = @_; + my $paging_nav; + + + if ($hash ne $head || $page) { + $paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD"); + } else { + $paging_nav .= "HEAD"; + } + + if ($page > 0) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)), + -accesskey => "p", -title => "Alt-p"}, "prev"); + } else { + $paging_nav .= " ⋅ prev"; + } + + if ($nrevs >= (100 * ($page+1)-1)) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)), + -accesskey => "n", -title => "Alt-n"}, "next"); + } else { + $paging_nav .= " ⋅ next"; + } + + return $paging_nav; +} + sub git_get_type { my $hash = shift; @@ -1873,24 +1903,7 @@ sub git_log { my @revlist = map { chomp; $_ } <$fd>; close $fd; - my $paging_nav = ''; - if ($hash ne $head || $page) { - $paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "HEAD"); - } else { - $paging_nav .= "HEAD"; - } - if ($page > 0) { - $paging_nav .= " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev"); - } else { - $paging_nav .= " ⋅ prev"; - } - if ($#revlist >= (100 * ($page+1)-1)) { - $paging_nav .= " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next"); - } else { - $paging_nav .= " ⋅ next"; - } + my $paging_nav = git_get_paging_nav($project, 'log', $hash, $head, $page, $#revlist); git_header_html(); git_page_nav('log','', $hash,undef,undef, $paging_nav); @@ -2553,24 +2566,7 @@ sub git_shortlog { my @revlist = map { chomp; $_ } <$fd>; close $fd; - my $paging_nav = ''; - if ($hash ne $head || $page) { - $paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "HEAD"); - } else { - $paging_nav .= "HEAD"; - } - if ($page > 0) { - $paging_nav .= " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev"); - } else { - $paging_nav .= " ⋅ prev"; - } - if ($#revlist >= (100 * ($page+1)-1)) { - $paging_nav .= " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next"); - } else { - $paging_nav .= " ⋅ next"; - } + my $paging_nav = git_get_paging_nav($project, 'shortlog', $hash, $head, $page, $#revlist); git_header_html(); git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); -- cgit v1.2.3 From 6855f42ecd8cfb77ec7494ac9eea9b1ef2d634a0 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 30 Jul 2006 20:31:00 +0200 Subject: gitweb: Remove $project from git_get_paging_nav arguments Remove $project from arguments passed to git_get_paging_nav subroutine: it did not depend only on arguments, using $my_uri global variable (and now $project global variable). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 8e5e4c83c5..3134b9ce56 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -421,7 +421,7 @@ sub git_page_nav { } sub git_get_paging_nav { - my ($project, $action, $hash, $head, $page, $nrevs) = @_; + my ($action, $hash, $head, $page, $nrevs) = @_; my $paging_nav; @@ -1903,7 +1903,7 @@ sub git_log { my @revlist = map { chomp; $_ } <$fd>; close $fd; - my $paging_nav = git_get_paging_nav($project, 'log', $hash, $head, $page, $#revlist); + my $paging_nav = git_get_paging_nav('log', $hash, $head, $page, $#revlist); git_header_html(); git_page_nav('log','', $hash,undef,undef, $paging_nav); @@ -2566,7 +2566,7 @@ sub git_shortlog { my @revlist = map { chomp; $_ } <$fd>; close $fd; - my $paging_nav = git_get_paging_nav($project, 'shortlog', $hash, $head, $page, $#revlist); + my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist); git_header_html(); git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); -- cgit v1.2.3 From 27fb8c40c6b634c67f5b5fb3b354a207cc10b343 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 30 Jul 2006 20:32:01 +0200 Subject: gitweb: Headers refactoring - use git_header_div for header divs Add git_header_div subroutine which prints "header" divs, now with class "header" (class "title" is taken, and has set CSS style, changing appereance and maing layout wrong), and use it thorough gitweb.cgi. Change header linking to project summary from empty (  as a contents of link) to having $project as contents/name of link. Sometimes a little reordering. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 100 ++++++++++++++++++++++-------------------------------- 1 file changed, 40 insertions(+), 60 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 3134b9ce56..1012869048 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -420,6 +420,19 @@ sub git_page_nav { "\n"; } +sub git_header_div { + my ($action, $title, $hash, $hash_base) = @_; + my $rest = ''; + + $rest .= ";h=$hash" if $hash; + $rest .= ";hb=$hash_base" if $hash_base; + + print "
\n" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"), + -class => "title"}, $title ? $title : $action) . "\n" . + "
\n"; +} + sub git_get_paging_nav { my ($action, $hash, $head, $page, $nrevs) = @_; my $paging_nav; @@ -1123,9 +1136,7 @@ sub git_summary { or die_error(undef, "Open git-rev-list failed."); my @revlist = map { chomp; $_ } <$fd>; close $fd; - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog"), -class => "title"}, "shortlog") . - "
\n"; + git_header_div('shortlog'); my $i = 16; print "\n"; my $alternate = 0; @@ -1169,9 +1180,7 @@ sub git_summary { my $taglist = git_read_refs("refs/tags"); if (defined @$taglist) { - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags"), -class => "title"}, "tags") . - "
\n"; + git_header_div('tags'); my $i = 16; print "
\n"; my $alternate = 0; @@ -1221,9 +1230,7 @@ sub git_summary { my $headlist = git_read_refs("refs/heads"); if (defined @$headlist) { - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads"), -class => "title"}, "heads") . - "
\n"; + git_header_div('heads'); my $i = 16; print "
\n"; my $alternate = 0; @@ -1276,9 +1283,7 @@ sub git_tag { git_header_html(); git_page_nav('','', $head,undef,$head); my %tag = git_read_tag($hash); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($tag{'name'})) . "\n" . - "
\n"; + git_header_div('commit', esc_html($tag{'name'}), $hash); print "
\n" . "
\n" . "\n" . @@ -1326,9 +1331,7 @@ sub git_blame2 { $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - print "
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . - "
\n"; + git_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype); my @rev_color = (qw(light dark)); my $num_colors = scalar(@rev_color); @@ -1382,9 +1385,7 @@ sub git_blame { $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - print "
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . - "
\n"; + git_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name); print "
\n"; print <\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "
\n"; + git_header_div('summary', $project); print "
\n"; + + my $taglist = git_read_refs("refs/tags"); my $alternate = 0; if (defined @$taglist) { foreach my $entry (@$taglist) { @@ -1511,11 +1511,10 @@ sub git_heads { my $head = git_read_head($project); git_header_html(); git_page_nav('','', $head,undef,$head); - my $taglist = git_read_refs("refs/heads"); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "
\n"; + hit_header_div('summary', $project); print "
\n"; + + my $taglist = git_read_refs("refs/heads"); my $alternate = 0; if (defined @$taglist) { foreach my $entry (@$taglist) { @@ -1684,9 +1683,7 @@ sub git_blob { $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain"); } git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - print "
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . - "
\n"; + git_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print "
\n" . "

\n" . @@ -1740,9 +1737,7 @@ sub git_tree { if (defined $hash_base && (my %co = git_read_commit($hash_base))) { $base_key = ";hb=$hash_base"; git_page_nav('tree','', $hash_base); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . - "
\n"; + git_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { print "
\n"; print "

\n"; @@ -1909,10 +1904,9 @@ sub git_log { git_page_nav('log','', $hash,undef,undef, $paging_nav); if (!@revlist) { - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "
\n"; my %co = git_read_commit($hash); + + git_header_div('summary', $project); print "
Last change $co{'age_string'}.

\n"; } for (my $i = ($page * 100); $i <= $#revlist; $i++) { @@ -1924,10 +1918,10 @@ sub git_log { my %co = git_read_commit($commit); next if !%co; my %ad = date_str($co{'author_epoch'}); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "title"}, - "$co{'age_string'}" . esc_html($co{'title'}) . $ref) . "\n"; - print "
\n"; + git_header_div('commit', + "$co{'age_string'}" . + esc_html($co{'title'}) . $ref, + $commit); print "
\n" . "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . @@ -1999,13 +1993,9 @@ sub git_commit { $formats_nav); if (defined $co{'parent'}) { - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . - "
\n"; + git_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); } else { - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "
\n"; + git_header_div('tree', esc_html($co{'title'}), $co{'tree'}, $hash); } print "
\n" . "
\n"; @@ -2171,9 +2161,7 @@ sub git_blobdiff { my $formats_nav = $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "
\n"; + git_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print "
\n" . "

\n" . @@ -2225,9 +2213,7 @@ sub git_commitdiff { my $formats_nav = $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . - "
\n"; + git_header_div('commit', esc_html($co{'title'}) . $ref, $hash); print "
\n"; my $comment = $co{'comment'}; my $empty = 0; @@ -2357,9 +2343,7 @@ sub git_history { my $refs = read_info_ref(); git_header_html(); git_page_nav('','', $hash_base,$co{'tree'},$hash_base); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "
\n"; + git_header_div('commit', esc_html($co{'title'}), $hash_base); if (!defined $hash && defined $file_name) { $hash = git_get_hash_by_path($hash_base, $file_name); } @@ -2440,10 +2424,8 @@ sub git_search { } git_header_html(); git_page_nav('','', $hash,$co{'tree'},$hash); + git_header_div('commit', esc_html($co{'title'}), $hash); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "
\n"; print "
\n"; my $alternate = 0; if ($commit_search) { @@ -2570,10 +2552,8 @@ sub git_shortlog { git_header_html(); git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); + git_header_div('summary', $project); - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "
\n"; print "
\n"; my $alternate = 0; for (my $i = ($page * 100); $i <= $#revlist; $i++) { -- cgit v1.2.3 From 7ca84b506281495b9ec43801d08b7209b08be92d Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 30 Jul 2006 22:36:04 +0200 Subject: gitweb: Remove characters entities entirely when shortening string Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 1 + 1 file changed, 1 insertion(+) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 1012869048..1ff29bc291 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -776,6 +776,7 @@ sub chop_str { my $tail = $2; if (length($tail) > 4) { $tail = " ..."; + $body =~ s/&[^;]$//; # remove chopped character entities } return "$body$tail"; } -- cgit v1.2.3 From 594e212bc849039a204deef1d16c2eddcc451532 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 31 Jul 2006 02:21:52 +0200 Subject: gitweb: Ref refactoring - use git_get_referencing for marking tagged/head commits Use git_get_referencing to get HTML code for markers showing which refs (tags and heads) point to current commit. It would be much easier to change format of markers in one or two places than thorough the gitweb.cgi file. Added comment about read_info_ref subroutine: for $type == "" (empty argument) it saves only last path part of ref name e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb'. Some reordering. Added $ref in one place. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 53 ++++++++++++++++++++++------------------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 1ff29bc291..2f1731870d 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -1024,6 +1024,8 @@ sub read_info_ref { open my $fd, "$projectroot/$project/info/refs" or return; while (my $line = <$fd>) { chomp $line; + # attention: for $type == "" it saves only last path part of ref name + # e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb' if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) { if (defined $refs{$1}) { $refs{$1} .= " / $2"; @@ -1036,6 +1038,16 @@ sub read_info_ref { return \%refs; } +sub git_get_referencing { + my ($refs, $id) = @_; + + if (defined $refs->{$id}) { + return ' ' . esc_html($refs->{$id}) . ''; + } else { + return ""; + } +} + sub git_read_refs { my $ref_dir = shift; my @reflist; @@ -1151,10 +1163,7 @@ sub git_summary { } $alternate ^= 1; if ($i-- > 0) { - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " " . esc_html($refs->{$commit}) . ""; - } + my $ref = git_get_referencing($refs, $commit); print "\n" . "\n" . "
$co{'age_string'}" . esc_html(chop_str($co{'author_name'}, 10)) . ""; @@ -1728,10 +1737,7 @@ sub git_tree { $/ = "\n"; my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$hash_base}) { - $ref = " " . esc_html($refs->{$hash_base}) . ""; - } + my $ref = git_get_referencing($refs, $hash_base); git_header_html(); my $base_key = ""; my $base = ""; @@ -1912,10 +1918,7 @@ sub git_log { } for (my $i = ($page * 100); $i <= $#revlist; $i++) { my $commit = $revlist[$i]; - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " " . esc_html($refs->{$commit}) . ""; - } + my $ref = git_get_referencing($refs, $commit); my %co = git_read_commit($commit); next if !%co; my %ad = date_str($co{'author_epoch'}); @@ -1979,16 +1982,13 @@ sub git_commit { $expires = "+1d"; } my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$co{'id'}}) { - $ref = " " . esc_html($refs->{$co{'id'}}) . ""; - } - git_header_html(undef, $expires); + my $ref = git_get_referencing($refs, $co{'id'}); my $formats_nav = ''; if (defined $file_name && defined $co{'parent'}) { my $parent = $co{'parent'}; $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame"); } + git_header_html(undef, $expires); git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', $hash, $co{'tree'}, $hash, $formats_nav); @@ -1996,7 +1996,7 @@ sub git_commit { if (defined $co{'parent'}) { git_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); } else { - git_header_div('tree', esc_html($co{'title'}), $co{'tree'}, $hash); + git_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash); } print "
\n" . "\n"; @@ -2206,13 +2206,10 @@ sub git_commitdiff { $expires = "+1d"; } my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$co{'id'}}) { - $ref = " " . esc_html($refs->{$co{'id'}}) . ""; - } - git_header_html(undef, $expires); + my $ref = git_get_referencing($refs, $co{'id'}); my $formats_nav = $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); + git_header_html(undef, $expires); git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); git_header_div('commit', esc_html($co{'title'}) . $ref, $hash); print "
\n"; @@ -2364,10 +2361,7 @@ sub git_history { if (!%co) { next; } - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " " . esc_html($refs->{$commit}) . ""; - } + my $ref = git_get_referencing($refs, $commit); if ($alternate) { print "
\n"; } else { @@ -2559,10 +2553,7 @@ sub git_shortlog { my $alternate = 0; for (my $i = ($page * 100); $i <= $#revlist; $i++) { my $commit = $revlist[$i]; - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " " . esc_html($refs->{$commit}) . ""; - } + my $ref = git_get_referencing($refs, $commit); my %co = git_read_commit($commit); my %ad = date_str($co{'author_epoch'}); if ($alternate) { -- cgit v1.2.3 From 16da134b1f9b0fbd1d328999875d34d02e5470b4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 30 Jul 2006 20:25:18 +0200 Subject: read-trees: refactor the unpack_trees() part Basically, the options are passed by a struct unpack_trees_options now. That's all. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Makefile | 4 +- builtin-read-tree.c | 577 +++++++++------------------------------------------- unpack-trees.c | 395 +++++++++++++++++++++++++++++++++++ unpack-trees.h | 30 +++ 4 files changed, 521 insertions(+), 485 deletions(-) create mode 100644 unpack-trees.c create mode 100644 unpack-trees.h diff --git a/Makefile b/Makefile index 15864e23bd..b2332761ba 100644 --- a/Makefile +++ b/Makefile @@ -221,7 +221,7 @@ LIB_H = \ blob.h cache.h commit.h csum-file.h delta.h \ diff.h object.h pack.h pkt-line.h quote.h refs.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ - tree-walk.h log-tree.h dir.h path-list.h + tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -236,7 +236,7 @@ LIB_OBJS = \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ - alloc.o merge-file.o path-list.o $(DIFF_OBJS) + alloc.o merge-file.o path-list.o unpack-trees.o $(DIFF_OBJS) BUILTIN_OBJS = \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 49c10bf221..6bc042f852 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -11,411 +11,17 @@ #include "tree.h" #include "tree-walk.h" #include "cache-tree.h" -#include -#include +#include "unpack-trees.h" #include "builtin.h" -static int reset = 0; -static int merge = 0; -static int update = 0; -static int index_only = 0; -static int nontrivial_merge = 0; -static int trivial_merges_only = 0; -static int aggressive = 0; -static int verbose_update = 0; -static volatile int progress_update = 0; -static const char *prefix = NULL; - -static int head_idx = -1; -static int merge_size = 0; - static struct object_list *trees = NULL; -static struct cache_entry df_conflict_entry; - -struct tree_entry_list { - struct tree_entry_list *next; - unsigned directory : 1; - unsigned executable : 1; - unsigned symlink : 1; - unsigned int mode; - const char *name; - const unsigned char *sha1; -}; - -static struct tree_entry_list df_conflict_list; - -typedef int (*merge_fn_t)(struct cache_entry **src); - -static struct tree_entry_list *create_tree_entry_list(struct tree *tree) -{ - struct tree_desc desc; - struct name_entry one; - struct tree_entry_list *ret = NULL; - struct tree_entry_list **list_p = &ret; - - desc.buf = tree->buffer; - desc.size = tree->size; - - while (tree_entry(&desc, &one)) { - struct tree_entry_list *entry; - - entry = xmalloc(sizeof(struct tree_entry_list)); - entry->name = one.path; - entry->sha1 = one.sha1; - entry->mode = one.mode; - entry->directory = S_ISDIR(one.mode) != 0; - entry->executable = (one.mode & S_IXUSR) != 0; - entry->symlink = S_ISLNK(one.mode) != 0; - entry->next = NULL; - - *list_p = entry; - list_p = &entry->next; - } - return ret; -} - -static int entcmp(const char *name1, int dir1, const char *name2, int dir2) -{ - int len1 = strlen(name1); - int len2 = strlen(name2); - int len = len1 < len2 ? len1 : len2; - int ret = memcmp(name1, name2, len); - unsigned char c1, c2; - if (ret) - return ret; - c1 = name1[len]; - c2 = name2[len]; - if (!c1 && dir1) - c1 = '/'; - if (!c2 && dir2) - c2 = '/'; - ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; - if (c1 && c2 && !ret) - ret = len1 - len2; - return ret; -} - -static int unpack_trees_rec(struct tree_entry_list **posns, int len, - const char *base, merge_fn_t fn, int *indpos) -{ - int baselen = strlen(base); - int src_size = len + 1; - do { - int i; - const char *first; - int firstdir = 0; - int pathlen; - unsigned ce_size; - struct tree_entry_list **subposns; - struct cache_entry **src; - int any_files = 0; - int any_dirs = 0; - char *cache_name; - int ce_stage; - - /* Find the first name in the input. */ - - first = NULL; - cache_name = NULL; - - /* Check the cache */ - if (merge && *indpos < active_nr) { - /* This is a bit tricky: */ - /* If the index has a subdirectory (with - * contents) as the first name, it'll get a - * filename like "foo/bar". But that's after - * "foo", so the entry in trees will get - * handled first, at which point we'll go into - * "foo", and deal with "bar" from the index, - * because the base will be "foo/". The only - * way we can actually have "foo/bar" first of - * all the things is if the trees don't - * contain "foo" at all, in which case we'll - * handle "foo/bar" without going into the - * directory, but that's fine (and will return - * an error anyway, with the added unknown - * file case. - */ - - cache_name = active_cache[*indpos]->name; - if (strlen(cache_name) > baselen && - !memcmp(cache_name, base, baselen)) { - cache_name += baselen; - first = cache_name; - } else { - cache_name = NULL; - } - } - -#if DBRT_DEBUG > 1 - if (first) - printf("index %s\n", first); -#endif - for (i = 0; i < len; i++) { - if (!posns[i] || posns[i] == &df_conflict_list) - continue; -#if DBRT_DEBUG > 1 - printf("%d %s\n", i + 1, posns[i]->name); -#endif - if (!first || entcmp(first, firstdir, - posns[i]->name, - posns[i]->directory) > 0) { - first = posns[i]->name; - firstdir = posns[i]->directory; - } - } - /* No name means we're done */ - if (!first) - return 0; - - pathlen = strlen(first); - ce_size = cache_entry_size(baselen + pathlen); - - src = xcalloc(src_size, sizeof(struct cache_entry *)); - - subposns = xcalloc(len, sizeof(struct tree_list_entry *)); - - if (cache_name && !strcmp(cache_name, first)) { - any_files = 1; - src[0] = active_cache[*indpos]; - remove_cache_entry_at(*indpos); - } - - for (i = 0; i < len; i++) { - struct cache_entry *ce; - - if (!posns[i] || - (posns[i] != &df_conflict_list && - strcmp(first, posns[i]->name))) { - continue; - } - - if (posns[i] == &df_conflict_list) { - src[i + merge] = &df_conflict_entry; - continue; - } - - if (posns[i]->directory) { - struct tree *tree = lookup_tree(posns[i]->sha1); - any_dirs = 1; - parse_tree(tree); - subposns[i] = create_tree_entry_list(tree); - posns[i] = posns[i]->next; - src[i + merge] = &df_conflict_entry; - continue; - } - - if (!merge) - ce_stage = 0; - else if (i + 1 < head_idx) - ce_stage = 1; - else if (i + 1 > head_idx) - ce_stage = 3; - else - ce_stage = 2; - - ce = xcalloc(1, ce_size); - ce->ce_mode = create_ce_mode(posns[i]->mode); - ce->ce_flags = create_ce_flags(baselen + pathlen, - ce_stage); - memcpy(ce->name, base, baselen); - memcpy(ce->name + baselen, first, pathlen + 1); - - any_files = 1; - - memcpy(ce->sha1, posns[i]->sha1, 20); - src[i + merge] = ce; - subposns[i] = &df_conflict_list; - posns[i] = posns[i]->next; - } - if (any_files) { - if (merge) { - int ret; - -#if DBRT_DEBUG > 1 - printf("%s:\n", first); - for (i = 0; i < src_size; i++) { - printf(" %d ", i); - if (src[i]) - printf("%s\n", sha1_to_hex(src[i]->sha1)); - else - printf("\n"); - } -#endif - ret = fn(src); - -#if DBRT_DEBUG > 1 - printf("Added %d entries\n", ret); -#endif - *indpos += ret; - } else { - for (i = 0; i < src_size; i++) { - if (src[i]) { - add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); - } - } - } - } - if (any_dirs) { - char *newbase = xmalloc(baselen + 2 + pathlen); - memcpy(newbase, base, baselen); - memcpy(newbase + baselen, first, pathlen); - newbase[baselen + pathlen] = '/'; - newbase[baselen + pathlen + 1] = '\0'; - if (unpack_trees_rec(subposns, len, newbase, fn, - indpos)) - return -1; - free(newbase); - } - free(subposns); - free(src); - } while (1); -} - static void reject_merge(struct cache_entry *ce) { - die("Entry '%s' would be overwritten by merge. Cannot merge.", + die("Entry '%s' would be overwritten by merge. Cannot merge.", ce->name); } -/* Unlink the last component and attempt to remove leading - * directories, in case this unlink is the removal of the - * last entry in the directory -- empty directories are removed. - */ -static void unlink_entry(char *name) -{ - char *cp, *prev; - - if (unlink(name)) - return; - prev = NULL; - while (1) { - int status; - cp = strrchr(name, '/'); - if (prev) - *prev = '/'; - if (!cp) - break; - - *cp = 0; - status = rmdir(name); - if (status) { - *cp = '/'; - break; - } - prev = cp; - } -} - -static void progress_interval(int signum) -{ - progress_update = 1; -} - -static void setup_progress_signal(void) -{ - struct sigaction sa; - struct itimerval v; - - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = progress_interval; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sigaction(SIGALRM, &sa, NULL); - - v.it_interval.tv_sec = 1; - v.it_interval.tv_usec = 0; - v.it_value = v.it_interval; - setitimer(ITIMER_REAL, &v, NULL); -} - -static struct checkout state; -static void check_updates(struct cache_entry **src, int nr) -{ - unsigned short mask = htons(CE_UPDATE); - unsigned last_percent = 200, cnt = 0, total = 0; - - if (update && verbose_update) { - for (total = cnt = 0; cnt < nr; cnt++) { - struct cache_entry *ce = src[cnt]; - if (!ce->ce_mode || ce->ce_flags & mask) - total++; - } - - /* Don't bother doing this for very small updates */ - if (total < 250) - total = 0; - - if (total) { - fprintf(stderr, "Checking files out...\n"); - setup_progress_signal(); - progress_update = 1; - } - cnt = 0; - } - - while (nr--) { - struct cache_entry *ce = *src++; - - if (total) { - if (!ce->ce_mode || ce->ce_flags & mask) { - unsigned percent; - cnt++; - percent = (cnt * 100) / total; - if (percent != last_percent || - progress_update) { - fprintf(stderr, "%4u%% (%u/%u) done\r", - percent, cnt, total); - last_percent = percent; - progress_update = 0; - } - } - } - if (!ce->ce_mode) { - if (update) - unlink_entry(ce->name); - continue; - } - if (ce->ce_flags & mask) { - ce->ce_flags &= ~mask; - if (update) - checkout_entry(ce, &state, NULL); - } - } - if (total) { - signal(SIGALRM, SIG_IGN); - fputc('\n', stderr); - } -} - -static int unpack_trees(merge_fn_t fn) -{ - int indpos = 0; - unsigned len = object_list_length(trees); - struct tree_entry_list **posns; - int i; - struct object_list *posn = trees; - merge_size = len; - - if (len) { - posns = xmalloc(len * sizeof(struct tree_entry_list *)); - for (i = 0; i < len; i++) { - posns[i] = create_tree_entry_list((struct tree *) posn->item); - posn = posn->next; - } - if (unpack_trees_rec(posns, len, prefix ? prefix : "", - fn, &indpos)) - return -1; - } - - if (trivial_merges_only && nontrivial_merge) - die("Merge requires file-level merging"); - - check_updates(active_cache, active_nr); - return 0; -} - static int list_tree(unsigned char *sha1) { struct tree *tree = parse_tree_indirect(sha1); @@ -440,11 +46,12 @@ 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 void verify_uptodate(struct cache_entry *ce) +static void verify_uptodate(struct cache_entry *ce, + struct unpack_trees_options *o) { struct stat st; - if (index_only || reset) + if (o->index_only || o->reset) return; if (!lstat(ce->name, &st)) { @@ -453,7 +60,7 @@ static void verify_uptodate(struct cache_entry *ce) return; errno = 0; } - if (reset) { + if (o->reset) { ce->ce_flags |= htons(CE_UPDATE); return; } @@ -472,18 +79,20 @@ static void invalidate_ce_path(struct cache_entry *ce) * We do not want to remove or overwrite a working tree file that * is not tracked. */ -static void verify_absent(const char *path, const char *action) +static void verify_absent(const char *path, const char *action, + struct unpack_trees_options *o) { struct stat st; - if (index_only || reset || !update) + if (o->index_only || o->reset || !o->update) return; if (!lstat(path, &st)) die("Untracked working tree file '%s' " "would be %s by merge.", path, action); } -static int merged_entry(struct cache_entry *merge, struct cache_entry *old) +static int merged_entry(struct cache_entry *merge, struct cache_entry *old, + struct unpack_trees_options *o) { merge->ce_flags |= htons(CE_UPDATE); if (old) { @@ -497,12 +106,12 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old) if (same(old, merge)) { *merge = *old; } else { - verify_uptodate(old); + verify_uptodate(old, o); invalidate_ce_path(old); } } else { - verify_absent(merge->name, "overwritten"); + verify_absent(merge->name, "overwritten", o); invalidate_ce_path(merge); } @@ -511,12 +120,13 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old) return 1; } -static int deleted_entry(struct cache_entry *ce, struct cache_entry *old) +static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, + struct unpack_trees_options *o) { if (old) - verify_uptodate(old); + verify_uptodate(old, o); else - verify_absent(ce->name, "removed"); + verify_absent(ce->name, "removed", o); ce->ce_mode = 0; add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); invalidate_ce_path(ce); @@ -545,11 +155,12 @@ static void show_stage_entry(FILE *o, } #endif -static int threeway_merge(struct cache_entry **stages) +static int threeway_merge(struct cache_entry **stages, + struct unpack_trees_options *o) { struct cache_entry *index; - struct cache_entry *head; - struct cache_entry *remote = stages[head_idx + 1]; + struct cache_entry *head; + struct cache_entry *remote = stages[o->head_idx + 1]; int count; int head_match = 0; int remote_match = 0; @@ -562,7 +173,7 @@ static int threeway_merge(struct cache_entry **stages) int no_anc_exists = 1; int i; - for (i = 1; i < head_idx; i++) { + for (i = 1; i < o->head_idx; i++) { if (!stages[i]) any_anc_missing = 1; else { @@ -573,14 +184,14 @@ static int threeway_merge(struct cache_entry **stages) } index = stages[0]; - head = stages[head_idx]; + head = stages[o->head_idx]; - if (head == &df_conflict_entry) { + if (head == &o->df_conflict_entry) { df_conflict_head = 1; head = NULL; } - if (remote == &df_conflict_entry) { + if (remote == &o->df_conflict_entry) { df_conflict_remote = 1; remote = NULL; } @@ -596,7 +207,7 @@ static int threeway_merge(struct cache_entry **stages) * and #14. */ if (!same(remote, head)) { - for (i = 1; i < head_idx; i++) { + for (i = 1; i < o->head_idx; i++) { if (same(stages[i], head)) { head_match = i; } @@ -614,7 +225,7 @@ static int threeway_merge(struct cache_entry **stages) if (remote && !df_conflict_head && head_match && !remote_match) { if (index && !same(index, remote) && !same(index, head)) reject_merge(index); - return merged_entry(remote, index); + return merged_entry(remote, index, o); } /* * If we have an entry in the index cache, then we want to @@ -627,10 +238,10 @@ static int threeway_merge(struct cache_entry **stages) if (head) { /* #5ALT, #15 */ if (same(head, remote)) - return merged_entry(head, index); + return merged_entry(head, index, o); /* #13, #3ALT */ if (!df_conflict_remote && remote_match && !head_match) - return merged_entry(head, index); + return merged_entry(head, index, o); } /* #1 */ @@ -640,7 +251,7 @@ static int threeway_merge(struct cache_entry **stages) /* Under the new "aggressive" rule, we resolve mostly trivial * cases that we historically had git-merge-one-file resolve. */ - if (aggressive) { + if (o->aggressive) { int head_deleted = !head && !df_conflict_head; int remote_deleted = !remote && !df_conflict_remote; /* @@ -651,35 +262,35 @@ static int threeway_merge(struct cache_entry **stages) (head_deleted && remote && remote_match) || (remote_deleted && head && head_match)) { if (index) - return deleted_entry(index, index); + return deleted_entry(index, index, o); else if (path) - verify_absent(path, "removed"); + verify_absent(path, "removed", o); return 0; } /* * Added in both, identically. */ if (no_anc_exists && head && remote && same(head, remote)) - return merged_entry(head, index); + return merged_entry(head, index, o); } /* Below are "no merge" cases, which require that the index be * up-to-date to avoid the files getting overwritten with - * conflict resolution files. + * conflict resolution files. */ if (index) { - verify_uptodate(index); + verify_uptodate(index, o); } else if (path) - verify_absent(path, "overwritten"); + verify_absent(path, "overwritten", o); - nontrivial_merge = 1; + o->nontrivial_merge = 1; /* #2, #3, #4, #6, #7, #9, #11. */ count = 0; if (!head_match || !remote_match) { - for (i = 1; i < head_idx; i++) { + for (i = 1; i < o->head_idx; i++) { if (stages[i]) { keep_entry(stages[i]); count++; @@ -708,14 +319,15 @@ static int threeway_merge(struct cache_entry **stages) * "carry forward" rule, please see . * */ -static int twoway_merge(struct cache_entry **src) +static int twoway_merge(struct cache_entry **src, + struct unpack_trees_options *o) { struct cache_entry *current = src[0]; struct cache_entry *oldtree = src[1], *newtree = src[2]; - if (merge_size != 2) + if (o->merge_size != 2) return error("Cannot do a twoway merge of %d trees", - merge_size); + o->merge_size); if (current) { if ((!oldtree && !newtree) || /* 4 and 5 */ @@ -730,12 +342,12 @@ static int twoway_merge(struct cache_entry **src) } else if (oldtree && !newtree && same(current, oldtree)) { /* 10 or 11 */ - return deleted_entry(oldtree, current); + return deleted_entry(oldtree, current, o); } else if (oldtree && newtree && same(current, oldtree) && !same(current, newtree)) { /* 20 or 21 */ - return merged_entry(newtree, current); + return merged_entry(newtree, current, o); } else { /* all other failures */ @@ -749,9 +361,9 @@ static int twoway_merge(struct cache_entry **src) } } else if (newtree) - return merged_entry(newtree, current); + return merged_entry(newtree, current, o); else - return deleted_entry(oldtree, current); + return deleted_entry(oldtree, current, o); } /* @@ -760,20 +372,21 @@ static int twoway_merge(struct cache_entry **src) * Keep the index entries at stage0, collapse stage1 but make sure * stage0 does not have anything there. */ -static int bind_merge(struct cache_entry **src) +static int bind_merge(struct cache_entry **src, + struct unpack_trees_options *o) { struct cache_entry *old = src[0]; struct cache_entry *a = src[1]; - if (merge_size != 1) + if (o->merge_size != 1) return error("Cannot do a bind merge of %d trees\n", - merge_size); + o->merge_size); if (a && old) die("Entry '%s' overlaps. Cannot bind.", a->name); if (!a) return keep_entry(old); else - return merged_entry(a, NULL); + return merged_entry(a, NULL, o); } /* @@ -782,19 +395,20 @@ static int bind_merge(struct cache_entry **src) * The rule is: * - take the stat information from stage0, take the data from stage1 */ -static int oneway_merge(struct cache_entry **src) +static int oneway_merge(struct cache_entry **src, + struct unpack_trees_options *o) { struct cache_entry *old = src[0]; struct cache_entry *a = src[1]; - if (merge_size != 1) + if (o->merge_size != 1) return error("Cannot do a oneway merge of %d trees", - merge_size); + o->merge_size); if (!a) - return deleted_entry(old, old); + return deleted_entry(old, old, o); if (old && same(old, a)) { - if (reset) { + if (o->reset) { struct stat st; if (lstat(old->name, &st) || ce_match_stat(old, &st, 1)) @@ -802,7 +416,7 @@ static int oneway_merge(struct cache_entry **src) } return keep_entry(old); } - return merged_entry(a, old); + return merged_entry(a, old, o); } static int read_cache_unmerged(void) @@ -874,14 +488,12 @@ int cmd_read_tree(int argc, const char **argv, const char *prefix) { int i, newfd, stage = 0; unsigned char sha1[20]; - merge_fn_t fn = NULL; + struct unpack_trees_options opts; - df_conflict_list.next = &df_conflict_list; - state.base_dir = ""; - state.force = 1; - state.quiet = 1; - state.refresh_cache = 1; + memset(&opts, 0, sizeof(opts)); + opts.head_idx = -1; + setup_git_directory(); git_config(git_default_config); newfd = hold_lock_file_for_update(&lock_file, get_index_file()); @@ -890,8 +502,6 @@ int cmd_read_tree(int argc, const char **argv, const char *prefix) git_config(git_default_config); - merge = 0; - reset = 0; for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -899,12 +509,12 @@ int cmd_read_tree(int argc, const char **argv, const char *prefix) * the working tree. */ if (!strcmp(arg, "-u")) { - update = 1; + opts.update = 1; continue; } if (!strcmp(arg, "-v")) { - verbose_update = 1; + opts.verbose_update = 1; continue; } @@ -912,7 +522,7 @@ int cmd_read_tree(int argc, const char **argv, const char *prefix) * not even look at the working tree. */ if (!strcmp(arg, "-i")) { - index_only = 1; + opts.index_only = 1; continue; } @@ -921,10 +531,10 @@ int cmd_read_tree(int argc, const char **argv, const char *prefix) * given subdirectory. */ if (!strncmp(arg, "--prefix=", 9)) { - if (stage || merge || prefix) + if (stage || opts.merge || opts.prefix) usage(read_tree_usage); - prefix = arg + 9; - merge = 1; + opts.prefix = arg + 9; + opts.merge = 1; stage = 1; if (read_cache_unmerged()) die("you need to resolve your current index first"); @@ -936,38 +546,38 @@ int cmd_read_tree(int argc, const char **argv, const char *prefix) * correspond to them. */ if (!strcmp(arg, "--reset")) { - if (stage || merge || prefix) + if (stage || opts.merge || opts.prefix) usage(read_tree_usage); - reset = 1; - merge = 1; + opts.reset = 1; + opts.merge = 1; stage = 1; read_cache_unmerged(); continue; } if (!strcmp(arg, "--trivial")) { - trivial_merges_only = 1; + opts.trivial_merges_only = 1; continue; } if (!strcmp(arg, "--aggressive")) { - aggressive = 1; + opts.aggressive = 1; continue; } /* "-m" stands for "merge", meaning we start in stage 1 */ if (!strcmp(arg, "-m")) { - if (stage || merge || prefix) + if (stage || opts.merge || opts.prefix) usage(read_tree_usage); if (read_cache_unmerged()) die("you need to resolve your current index first"); stage = 1; - merge = 1; + opts.merge = 1; continue; } /* using -u and -i at the same time makes no sense */ - if (1 < index_only + update) + if (1 < opts.index_only + opts.update) usage(read_tree_usage); if (get_sha1(arg, sha1)) @@ -976,52 +586,53 @@ int cmd_read_tree(int argc, const char **argv, const char *prefix) die("failed to unpack tree object %s", arg); stage++; } - if ((update||index_only) && !merge) + if ((opts.update||opts.index_only) && !opts.merge) usage(read_tree_usage); - if (prefix) { - int pfxlen = strlen(prefix); + if (opts.prefix) { + int pfxlen = strlen(opts.prefix); int pos; - if (prefix[pfxlen-1] != '/') + if (opts.prefix[pfxlen-1] != '/') die("prefix must end with /"); if (stage != 2) die("binding merge takes only one tree"); - pos = cache_name_pos(prefix, pfxlen); + pos = cache_name_pos(opts.prefix, pfxlen); if (0 <= pos) die("corrupt index file"); pos = -pos-1; if (pos < active_nr && - !strncmp(active_cache[pos]->name, prefix, pfxlen)) - die("subdirectory '%s' already exists.", prefix); - pos = cache_name_pos(prefix, pfxlen-1); + !strncmp(active_cache[pos]->name, opts.prefix, pfxlen)) + die("subdirectory '%s' already exists.", opts.prefix); + pos = cache_name_pos(opts.prefix, pfxlen-1); if (0 <= pos) - die("file '%.*s' already exists.", pfxlen-1, prefix); + die("file '%.*s' already exists.", + pfxlen-1, opts.prefix); } - if (merge) { + if (opts.merge) { if (stage < 2) die("just how do you expect me to merge %d trees?", stage-1); switch (stage - 1) { case 1: - fn = prefix ? bind_merge : oneway_merge; + opts.fn = opts.prefix ? bind_merge : oneway_merge; break; case 2: - fn = twoway_merge; + opts.fn = twoway_merge; break; case 3: default: - fn = threeway_merge; + opts.fn = threeway_merge; cache_tree_free(&active_cache_tree); break; } if (stage - 1 >= 3) - head_idx = stage - 2; + opts.head_idx = stage - 2; else - head_idx = 1; + opts.head_idx = 1; } - unpack_trees(fn); + unpack_trees(trees, &opts); /* * When reading only one tree (either the most basic form, @@ -1029,7 +640,7 @@ int cmd_read_tree(int argc, const char **argv, const char *prefix) * valid cache-tree because the index must match exactly * what came from the tree. */ - if (trees && trees->item && !prefix && (!merge || (stage == 2))) { + if (trees && trees->item && !opts.prefix && (!opts.merge || (stage == 2))) { cache_tree_free(&active_cache_tree); prime_cache_tree(); } diff --git a/unpack-trees.c b/unpack-trees.c new file mode 100644 index 0000000000..20251822e4 --- /dev/null +++ b/unpack-trees.c @@ -0,0 +1,395 @@ +#include +#include +#include "cache.h" +#include "tree.h" +#include "tree-walk.h" +#include "unpack-trees.h" + +struct tree_entry_list { + struct tree_entry_list *next; + unsigned directory : 1; + unsigned executable : 1; + unsigned symlink : 1; + unsigned int mode; + const char *name; + const unsigned char *sha1; +}; + +static struct tree_entry_list *create_tree_entry_list(struct tree *tree) +{ + struct tree_desc desc; + struct name_entry one; + struct tree_entry_list *ret = NULL; + struct tree_entry_list **list_p = &ret; + + desc.buf = tree->buffer; + desc.size = tree->size; + + while (tree_entry(&desc, &one)) { + struct tree_entry_list *entry; + + entry = xmalloc(sizeof(struct tree_entry_list)); + entry->name = one.path; + entry->sha1 = one.sha1; + entry->mode = one.mode; + entry->directory = S_ISDIR(one.mode) != 0; + entry->executable = (one.mode & S_IXUSR) != 0; + entry->symlink = S_ISLNK(one.mode) != 0; + entry->next = NULL; + + *list_p = entry; + list_p = &entry->next; + } + return ret; +} + +static int entcmp(const char *name1, int dir1, const char *name2, int dir2) +{ + int len1 = strlen(name1); + int len2 = strlen(name2); + int len = len1 < len2 ? len1 : len2; + int ret = memcmp(name1, name2, len); + unsigned char c1, c2; + if (ret) + return ret; + c1 = name1[len]; + c2 = name2[len]; + if (!c1 && dir1) + c1 = '/'; + if (!c2 && dir2) + c2 = '/'; + ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; + if (c1 && c2 && !ret) + ret = len1 - len2; + return ret; +} + +static int unpack_trees_rec(struct tree_entry_list **posns, int len, + const char *base, struct unpack_trees_options *o, + int *indpos, + struct tree_entry_list *df_conflict_list) +{ + int baselen = strlen(base); + int src_size = len + 1; + do { + int i; + const char *first; + int firstdir = 0; + int pathlen; + unsigned ce_size; + struct tree_entry_list **subposns; + struct cache_entry **src; + int any_files = 0; + int any_dirs = 0; + char *cache_name; + int ce_stage; + + /* Find the first name in the input. */ + + first = NULL; + cache_name = NULL; + + /* Check the cache */ + if (o->merge && *indpos < active_nr) { + /* This is a bit tricky: */ + /* If the index has a subdirectory (with + * contents) as the first name, it'll get a + * filename like "foo/bar". But that's after + * "foo", so the entry in trees will get + * handled first, at which point we'll go into + * "foo", and deal with "bar" from the index, + * because the base will be "foo/". The only + * way we can actually have "foo/bar" first of + * all the things is if the trees don't + * contain "foo" at all, in which case we'll + * handle "foo/bar" without going into the + * directory, but that's fine (and will return + * an error anyway, with the added unknown + * file case. + */ + + cache_name = active_cache[*indpos]->name; + if (strlen(cache_name) > baselen && + !memcmp(cache_name, base, baselen)) { + cache_name += baselen; + first = cache_name; + } else { + cache_name = NULL; + } + } + +#if DBRT_DEBUG > 1 + if (first) + printf("index %s\n", first); +#endif + for (i = 0; i < len; i++) { + if (!posns[i] || posns[i] == df_conflict_list) + continue; +#if DBRT_DEBUG > 1 + printf("%d %s\n", i + 1, posns[i]->name); +#endif + if (!first || entcmp(first, firstdir, + posns[i]->name, + posns[i]->directory) > 0) { + first = posns[i]->name; + firstdir = posns[i]->directory; + } + } + /* No name means we're done */ + if (!first) + return 0; + + pathlen = strlen(first); + ce_size = cache_entry_size(baselen + pathlen); + + src = xcalloc(src_size, sizeof(struct cache_entry *)); + + subposns = xcalloc(len, sizeof(struct tree_list_entry *)); + + if (cache_name && !strcmp(cache_name, first)) { + any_files = 1; + src[0] = active_cache[*indpos]; + remove_cache_entry_at(*indpos); + } + + for (i = 0; i < len; i++) { + struct cache_entry *ce; + + if (!posns[i] || + (posns[i] != df_conflict_list && + strcmp(first, posns[i]->name))) { + continue; + } + + if (posns[i] == df_conflict_list) { + src[i + o->merge] = o->df_conflict_entry; + continue; + } + + if (posns[i]->directory) { + struct tree *tree = lookup_tree(posns[i]->sha1); + any_dirs = 1; + parse_tree(tree); + subposns[i] = create_tree_entry_list(tree); + posns[i] = posns[i]->next; + src[i + o->merge] = o->df_conflict_entry; + continue; + } + + if (!o->merge) + ce_stage = 0; + else if (i + 1 < o->head_idx) + ce_stage = 1; + else if (i + 1 > o->head_idx) + ce_stage = 3; + else + ce_stage = 2; + + ce = xcalloc(1, ce_size); + ce->ce_mode = create_ce_mode(posns[i]->mode); + ce->ce_flags = create_ce_flags(baselen + pathlen, + ce_stage); + memcpy(ce->name, base, baselen); + memcpy(ce->name + baselen, first, pathlen + 1); + + any_files = 1; + + memcpy(ce->sha1, posns[i]->sha1, 20); + src[i + o->merge] = ce; + subposns[i] = df_conflict_list; + posns[i] = posns[i]->next; + } + if (any_files) { + if (o->merge) { + int ret; + +#if DBRT_DEBUG > 1 + printf("%s:\n", first); + for (i = 0; i < src_size; i++) { + printf(" %d ", i); + if (src[i]) + printf("%s\n", sha1_to_hex(src[i]->sha1)); + else + printf("\n"); + } +#endif + ret = o->fn(src, o); + +#if DBRT_DEBUG > 1 + printf("Added %d entries\n", ret); +#endif + *indpos += ret; + } else { + for (i = 0; i < src_size; i++) { + if (src[i]) { + add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); + } + } + } + } + if (any_dirs) { + char *newbase = xmalloc(baselen + 2 + pathlen); + memcpy(newbase, base, baselen); + memcpy(newbase + baselen, first, pathlen); + newbase[baselen + pathlen] = '/'; + newbase[baselen + pathlen + 1] = '\0'; + if (unpack_trees_rec(subposns, len, newbase, o, + indpos, df_conflict_list)) + return -1; + free(newbase); + } + free(subposns); + free(src); + } while (1); +} + +/* Unlink the last component and attempt to remove leading + * directories, in case this unlink is the removal of the + * last entry in the directory -- empty directories are removed. + */ +static void unlink_entry(char *name) +{ + char *cp, *prev; + + if (unlink(name)) + return; + prev = NULL; + while (1) { + int status; + cp = strrchr(name, '/'); + if (prev) + *prev = '/'; + if (!cp) + break; + + *cp = 0; + status = rmdir(name); + if (status) { + *cp = '/'; + break; + } + prev = cp; + } +} + +static volatile int progress_update = 0; + +static void progress_interval(int signum) +{ + progress_update = 1; +} + +static void setup_progress_signal(void) +{ + struct sigaction sa; + struct itimerval v; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = progress_interval; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + + v.it_interval.tv_sec = 1; + v.it_interval.tv_usec = 0; + v.it_value = v.it_interval; + setitimer(ITIMER_REAL, &v, NULL); +} + +static struct checkout state; +static void check_updates(struct cache_entry **src, int nr, + struct unpack_trees_options *o) +{ + unsigned short mask = htons(CE_UPDATE); + unsigned last_percent = 200, cnt = 0, total = 0; + + if (o->update && o->verbose_update) { + for (total = cnt = 0; cnt < nr; cnt++) { + struct cache_entry *ce = src[cnt]; + if (!ce->ce_mode || ce->ce_flags & mask) + total++; + } + + /* Don't bother doing this for very small updates */ + if (total < 250) + total = 0; + + if (total) { + fprintf(stderr, "Checking files out...\n"); + setup_progress_signal(); + progress_update = 1; + } + cnt = 0; + } + + while (nr--) { + struct cache_entry *ce = *src++; + + if (total) { + if (!ce->ce_mode || ce->ce_flags & mask) { + unsigned percent; + cnt++; + percent = (cnt * 100) / total; + if (percent != last_percent || + progress_update) { + fprintf(stderr, "%4u%% (%u/%u) done\r", + percent, cnt, total); + last_percent = percent; + progress_update = 0; + } + } + } + if (!ce->ce_mode) { + if (o->update) + unlink_entry(ce->name); + continue; + } + if (ce->ce_flags & mask) { + ce->ce_flags &= ~mask; + if (o->update) + checkout_entry(ce, &state, NULL); + } + } + if (total) { + signal(SIGALRM, SIG_IGN); + fputc('\n', stderr); + } +} + +int unpack_trees(struct object_list *trees, struct unpack_trees_options *o) +{ + int indpos = 0; + unsigned len = object_list_length(trees); + struct tree_entry_list **posns; + int i; + struct object_list *posn = trees; + struct tree_entry_list df_conflict_list; + struct cache_entry df_conflict_entry; + + memset(&df_conflict_list, 0, sizeof(df_conflict_list)); + df_conflict_list.next = &df_conflict_list; + state.base_dir = ""; + state.force = 1; + state.quiet = 1; + state.refresh_cache = 1; + + o->merge_size = len; + o->df_conflict_entry = &df_conflict_entry; + + if (len) { + posns = xmalloc(len * sizeof(struct tree_entry_list *)); + for (i = 0; i < len; i++) { + posns[i] = create_tree_entry_list((struct tree *) posn->item); + posn = posn->next; + } + if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "", + o, &indpos, &df_conflict_list)) + return -1; + } + + if (o->trivial_merges_only && o->nontrivial_merge) + die("Merge requires file-level merging"); + + check_updates(active_cache, active_nr, o); + return 0; +} diff --git a/unpack-trees.h b/unpack-trees.h new file mode 100644 index 0000000000..38af89a6b7 --- /dev/null +++ b/unpack-trees.h @@ -0,0 +1,30 @@ +#ifndef UNPACK_TREES_H +#define UNPACK_TREES_H + +struct unpack_trees_options; + +typedef int (*merge_fn_t)(struct cache_entry **src, + struct unpack_trees_options *options); + +struct unpack_trees_options { + int reset; + int merge; + int update; + int index_only; + int nontrivial_merge; + int trivial_merges_only; + int verbose_update; + int aggressive; + const char *prefix; + merge_fn_t fn; + + int head_idx; + int merge_size; + + struct cache_entry *df_conflict_entry; +}; + +extern int unpack_trees(struct object_list *trees, + struct unpack_trees_options *options); + +#endif -- cgit v1.2.3 From 076b0adcf9dac7bd9d18624087f679cc811aeb77 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 30 Jul 2006 20:26:15 +0200 Subject: read-tree: move merge functions to the library This will allow merge-recursive to use the read-tree functionality without exec()ing git-read-tree. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-read-tree.c | 398 +-------------------------------------------------- unpack-trees.c | 404 ++++++++++++++++++++++++++++++++++++++++++++++++++++ unpack-trees.h | 5 + 3 files changed, 410 insertions(+), 397 deletions(-) diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 6bc042f852..8869cedea1 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -3,10 +3,8 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#define DBRT_DEBUG 1 #include "cache.h" - #include "object.h" #include "tree.h" #include "tree-walk.h" @@ -16,12 +14,6 @@ static struct object_list *trees = NULL; -static void reject_merge(struct cache_entry *ce) -{ - die("Entry '%s' would be overwritten by merge. Cannot merge.", - ce->name); -} - static int list_tree(unsigned char *sha1) { struct tree *tree = parse_tree_indirect(sha1); @@ -31,394 +23,6 @@ static int list_tree(unsigned char *sha1) return 0; } -static int same(struct cache_entry *a, struct cache_entry *b) -{ - if (!!a != !!b) - return 0; - if (!a && !b) - return 1; - return a->ce_mode == b->ce_mode && - !memcmp(a->sha1, b->sha1, 20); -} - - -/* - * When a CE gets turned into an unmerged entry, we - * want it to be up-to-date - */ -static void verify_uptodate(struct cache_entry *ce, - struct unpack_trees_options *o) -{ - struct stat st; - - if (o->index_only || o->reset) - return; - - if (!lstat(ce->name, &st)) { - unsigned changed = ce_match_stat(ce, &st, 1); - if (!changed) - return; - errno = 0; - } - if (o->reset) { - ce->ce_flags |= htons(CE_UPDATE); - return; - } - if (errno == ENOENT) - return; - die("Entry '%s' not uptodate. Cannot merge.", ce->name); -} - -static void invalidate_ce_path(struct cache_entry *ce) -{ - if (ce) - cache_tree_invalidate_path(active_cache_tree, ce->name); -} - -/* - * We do not want to remove or overwrite a working tree file that - * is not tracked. - */ -static void verify_absent(const char *path, const char *action, - struct unpack_trees_options *o) -{ - struct stat st; - - if (o->index_only || o->reset || !o->update) - return; - if (!lstat(path, &st)) - die("Untracked working tree file '%s' " - "would be %s by merge.", path, action); -} - -static int merged_entry(struct cache_entry *merge, struct cache_entry *old, - struct unpack_trees_options *o) -{ - merge->ce_flags |= htons(CE_UPDATE); - if (old) { - /* - * See if we can re-use the old CE directly? - * That way we get the uptodate stat info. - * - * This also removes the UPDATE flag on - * a match. - */ - if (same(old, merge)) { - *merge = *old; - } else { - verify_uptodate(old, o); - invalidate_ce_path(old); - } - } - else { - verify_absent(merge->name, "overwritten", o); - invalidate_ce_path(merge); - } - - merge->ce_flags &= ~htons(CE_STAGEMASK); - add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - return 1; -} - -static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, - struct unpack_trees_options *o) -{ - if (old) - verify_uptodate(old, o); - else - verify_absent(ce->name, "removed", o); - ce->ce_mode = 0; - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - invalidate_ce_path(ce); - return 1; -} - -static int keep_entry(struct cache_entry *ce) -{ - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); - return 1; -} - -#if DBRT_DEBUG -static void show_stage_entry(FILE *o, - const char *label, const struct cache_entry *ce) -{ - if (!ce) - fprintf(o, "%s (missing)\n", label); - else - fprintf(o, "%s%06o %s %d\t%s\n", - label, - ntohl(ce->ce_mode), - sha1_to_hex(ce->sha1), - ce_stage(ce), - ce->name); -} -#endif - -static int threeway_merge(struct cache_entry **stages, - struct unpack_trees_options *o) -{ - struct cache_entry *index; - struct cache_entry *head; - struct cache_entry *remote = stages[o->head_idx + 1]; - int count; - int head_match = 0; - int remote_match = 0; - const char *path = NULL; - - int df_conflict_head = 0; - int df_conflict_remote = 0; - - int any_anc_missing = 0; - int no_anc_exists = 1; - int i; - - for (i = 1; i < o->head_idx; i++) { - if (!stages[i]) - any_anc_missing = 1; - else { - if (!path) - path = stages[i]->name; - no_anc_exists = 0; - } - } - - index = stages[0]; - head = stages[o->head_idx]; - - if (head == &o->df_conflict_entry) { - df_conflict_head = 1; - head = NULL; - } - - if (remote == &o->df_conflict_entry) { - df_conflict_remote = 1; - remote = NULL; - } - - if (!path && index) - path = index->name; - if (!path && head) - path = head->name; - if (!path && remote) - path = remote->name; - - /* First, if there's a #16 situation, note that to prevent #13 - * and #14. - */ - if (!same(remote, head)) { - for (i = 1; i < o->head_idx; i++) { - if (same(stages[i], head)) { - head_match = i; - } - if (same(stages[i], remote)) { - remote_match = i; - } - } - } - - /* We start with cases where the index is allowed to match - * something other than the head: #14(ALT) and #2ALT, where it - * is permitted to match the result instead. - */ - /* #14, #14ALT, #2ALT */ - if (remote && !df_conflict_head && head_match && !remote_match) { - if (index && !same(index, remote) && !same(index, head)) - reject_merge(index); - return merged_entry(remote, index, o); - } - /* - * If we have an entry in the index cache, then we want to - * make sure that it matches head. - */ - if (index && !same(index, head)) { - reject_merge(index); - } - - if (head) { - /* #5ALT, #15 */ - if (same(head, remote)) - return merged_entry(head, index, o); - /* #13, #3ALT */ - if (!df_conflict_remote && remote_match && !head_match) - return merged_entry(head, index, o); - } - - /* #1 */ - if (!head && !remote && any_anc_missing) - return 0; - - /* Under the new "aggressive" rule, we resolve mostly trivial - * cases that we historically had git-merge-one-file resolve. - */ - if (o->aggressive) { - int head_deleted = !head && !df_conflict_head; - int remote_deleted = !remote && !df_conflict_remote; - /* - * Deleted in both. - * Deleted in one and unchanged in the other. - */ - if ((head_deleted && remote_deleted) || - (head_deleted && remote && remote_match) || - (remote_deleted && head && head_match)) { - if (index) - return deleted_entry(index, index, o); - else if (path) - verify_absent(path, "removed", o); - return 0; - } - /* - * Added in both, identically. - */ - if (no_anc_exists && head && remote && same(head, remote)) - return merged_entry(head, index, o); - - } - - /* Below are "no merge" cases, which require that the index be - * up-to-date to avoid the files getting overwritten with - * conflict resolution files. - */ - if (index) { - verify_uptodate(index, o); - } - else if (path) - verify_absent(path, "overwritten", o); - - o->nontrivial_merge = 1; - - /* #2, #3, #4, #6, #7, #9, #11. */ - count = 0; - if (!head_match || !remote_match) { - for (i = 1; i < o->head_idx; i++) { - if (stages[i]) { - keep_entry(stages[i]); - count++; - break; - } - } - } -#if DBRT_DEBUG - else { - fprintf(stderr, "read-tree: warning #16 detected\n"); - show_stage_entry(stderr, "head ", stages[head_match]); - show_stage_entry(stderr, "remote ", stages[remote_match]); - } -#endif - if (head) { count += keep_entry(head); } - if (remote) { count += keep_entry(remote); } - return count; -} - -/* - * 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 - * over a merge failure when it makes sense. For details of the - * "carry forward" rule, please see . - * - */ -static int twoway_merge(struct cache_entry **src, - struct unpack_trees_options *o) -{ - struct cache_entry *current = src[0]; - struct cache_entry *oldtree = src[1], *newtree = src[2]; - - if (o->merge_size != 2) - return error("Cannot do a twoway merge of %d trees", - o->merge_size); - - if (current) { - if ((!oldtree && !newtree) || /* 4 and 5 */ - (!oldtree && newtree && - same(current, newtree)) || /* 6 and 7 */ - (oldtree && newtree && - same(oldtree, newtree)) || /* 14 and 15 */ - (oldtree && newtree && - !same(oldtree, newtree) && /* 18 and 19*/ - same(current, newtree))) { - return keep_entry(current); - } - else if (oldtree && !newtree && same(current, oldtree)) { - /* 10 or 11 */ - return deleted_entry(oldtree, current, o); - } - else if (oldtree && newtree && - same(current, oldtree) && !same(current, newtree)) { - /* 20 or 21 */ - return merged_entry(newtree, current, o); - } - else { - /* all other failures */ - if (oldtree) - reject_merge(oldtree); - if (current) - reject_merge(current); - if (newtree) - reject_merge(newtree); - return -1; - } - } - else if (newtree) - return merged_entry(newtree, current, o); - else - return deleted_entry(oldtree, current, o); -} - -/* - * Bind merge. - * - * Keep the index entries at stage0, collapse stage1 but make sure - * stage0 does not have anything there. - */ -static int bind_merge(struct cache_entry **src, - struct unpack_trees_options *o) -{ - struct cache_entry *old = src[0]; - struct cache_entry *a = src[1]; - - if (o->merge_size != 1) - return error("Cannot do a bind merge of %d trees\n", - o->merge_size); - if (a && old) - die("Entry '%s' overlaps. Cannot bind.", a->name); - if (!a) - return keep_entry(old); - else - return merged_entry(a, NULL, o); -} - -/* - * One-way merge. - * - * The rule is: - * - take the stat information from stage0, take the data from stage1 - */ -static int oneway_merge(struct cache_entry **src, - struct unpack_trees_options *o) -{ - struct cache_entry *old = src[0]; - struct cache_entry *a = src[1]; - - if (o->merge_size != 1) - return error("Cannot do a oneway merge of %d trees", - o->merge_size); - - if (!a) - return deleted_entry(old, old, o); - if (old && same(old, a)) { - if (o->reset) { - struct stat st; - if (lstat(old->name, &st) || - ce_match_stat(old, &st, 1)) - old->ce_flags |= htons(CE_UPDATE); - } - return keep_entry(old); - } - return merged_entry(a, old, o); -} - static int read_cache_unmerged(void) { int i; @@ -432,7 +36,7 @@ static int read_cache_unmerged(void) if (ce_stage(ce)) { if (last && !strcmp(ce->name, last->name)) continue; - invalidate_ce_path(ce); + cache_tree_invalidate_path(active_cache_tree, ce->name); last = ce; ce->ce_mode = 0; ce->ce_flags &= ~htons(CE_STAGEMASK); diff --git a/unpack-trees.c b/unpack-trees.c index 20251822e4..a20639be70 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -3,8 +3,11 @@ #include "cache.h" #include "tree.h" #include "tree-walk.h" +#include "cache-tree.h" #include "unpack-trees.h" +#define DBRT_DEBUG 1 + struct tree_entry_list { struct tree_entry_list *next; unsigned directory : 1; @@ -22,6 +25,9 @@ static struct tree_entry_list *create_tree_entry_list(struct tree *tree) struct tree_entry_list *ret = NULL; struct tree_entry_list **list_p = &ret; + if (!tree->object.parsed) + parse_tree(tree); + desc.buf = tree->buffer; desc.size = tree->size; @@ -368,12 +374,14 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o) memset(&df_conflict_list, 0, sizeof(df_conflict_list)); df_conflict_list.next = &df_conflict_list; + memset(&state, 0, sizeof(state)); state.base_dir = ""; state.force = 1; state.quiet = 1; state.refresh_cache = 1; o->merge_size = len; + memset(&df_conflict_entry, 0, sizeof(df_conflict_entry)); o->df_conflict_entry = &df_conflict_entry; if (len) { @@ -393,3 +401,399 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o) check_updates(active_cache, active_nr, o); return 0; } + +/* Here come the merge functions */ + +static void reject_merge(struct cache_entry *ce) +{ + die("Entry '%s' would be overwritten by merge. Cannot merge.", + ce->name); +} + +static int same(struct cache_entry *a, struct cache_entry *b) +{ + if (!!a != !!b) + return 0; + if (!a && !b) + return 1; + return a->ce_mode == b->ce_mode && + !memcmp(a->sha1, b->sha1, 20); +} + + +/* + * When a CE gets turned into an unmerged entry, we + * want it to be up-to-date + */ +static void verify_uptodate(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + struct stat st; + + if (o->index_only || o->reset) + return; + + if (!lstat(ce->name, &st)) { + unsigned changed = ce_match_stat(ce, &st, 1); + if (!changed) + return; + errno = 0; + } + if (o->reset) { + ce->ce_flags |= htons(CE_UPDATE); + return; + } + if (errno == ENOENT) + return; + die("Entry '%s' not uptodate. Cannot merge.", ce->name); +} + +static void invalidate_ce_path(struct cache_entry *ce) +{ + if (ce) + cache_tree_invalidate_path(active_cache_tree, ce->name); +} + +/* + * We do not want to remove or overwrite a working tree file that + * is not tracked. + */ +static void verify_absent(const char *path, const char *action, + struct unpack_trees_options *o) +{ + struct stat st; + + if (o->index_only || o->reset || !o->update) + return; + if (!lstat(path, &st)) + die("Untracked working tree file '%s' " + "would be %s by merge.", path, action); +} + +static int merged_entry(struct cache_entry *merge, struct cache_entry *old, + struct unpack_trees_options *o) +{ + merge->ce_flags |= htons(CE_UPDATE); + if (old) { + /* + * See if we can re-use the old CE directly? + * That way we get the uptodate stat info. + * + * This also removes the UPDATE flag on + * a match. + */ + if (same(old, merge)) { + *merge = *old; + } else { + verify_uptodate(old, o); + invalidate_ce_path(old); + } + } + else { + verify_absent(merge->name, "overwritten", o); + invalidate_ce_path(merge); + } + + merge->ce_flags &= ~htons(CE_STAGEMASK); + add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + return 1; +} + +static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, + struct unpack_trees_options *o) +{ + if (old) + verify_uptodate(old, o); + else + verify_absent(ce->name, "removed", o); + ce->ce_mode = 0; + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + invalidate_ce_path(ce); + return 1; +} + +static int keep_entry(struct cache_entry *ce) +{ + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); + return 1; +} + +#if DBRT_DEBUG +static void show_stage_entry(FILE *o, + const char *label, const struct cache_entry *ce) +{ + if (!ce) + fprintf(o, "%s (missing)\n", label); + else + fprintf(o, "%s%06o %s %d\t%s\n", + label, + ntohl(ce->ce_mode), + sha1_to_hex(ce->sha1), + ce_stage(ce), + ce->name); +} +#endif + +int threeway_merge(struct cache_entry **stages, + struct unpack_trees_options *o) +{ + struct cache_entry *index; + struct cache_entry *head; + struct cache_entry *remote = stages[o->head_idx + 1]; + int count; + int head_match = 0; + int remote_match = 0; + const char *path = NULL; + + int df_conflict_head = 0; + int df_conflict_remote = 0; + + int any_anc_missing = 0; + int no_anc_exists = 1; + int i; + + for (i = 1; i < o->head_idx; i++) { + if (!stages[i]) + any_anc_missing = 1; + else { + if (!path) + path = stages[i]->name; + no_anc_exists = 0; + } + } + + index = stages[0]; + head = stages[o->head_idx]; + + if (head == o->df_conflict_entry) { + df_conflict_head = 1; + head = NULL; + } + + if (remote == o->df_conflict_entry) { + df_conflict_remote = 1; + remote = NULL; + } + + if (!path && index) + path = index->name; + if (!path && head) + path = head->name; + if (!path && remote) + path = remote->name; + + /* First, if there's a #16 situation, note that to prevent #13 + * and #14. + */ + if (!same(remote, head)) { + for (i = 1; i < o->head_idx; i++) { + if (same(stages[i], head)) { + head_match = i; + } + if (same(stages[i], remote)) { + remote_match = i; + } + } + } + + /* We start with cases where the index is allowed to match + * something other than the head: #14(ALT) and #2ALT, where it + * is permitted to match the result instead. + */ + /* #14, #14ALT, #2ALT */ + if (remote && !df_conflict_head && head_match && !remote_match) { + if (index && !same(index, remote) && !same(index, head)) + reject_merge(index); + return merged_entry(remote, index, o); + } + /* + * If we have an entry in the index cache, then we want to + * make sure that it matches head. + */ + if (index && !same(index, head)) { + reject_merge(index); + } + + if (head) { + /* #5ALT, #15 */ + if (same(head, remote)) + return merged_entry(head, index, o); + /* #13, #3ALT */ + if (!df_conflict_remote && remote_match && !head_match) + return merged_entry(head, index, o); + } + + /* #1 */ + if (!head && !remote && any_anc_missing) + return 0; + + /* Under the new "aggressive" rule, we resolve mostly trivial + * cases that we historically had git-merge-one-file resolve. + */ + if (o->aggressive) { + int head_deleted = !head && !df_conflict_head; + int remote_deleted = !remote && !df_conflict_remote; + /* + * Deleted in both. + * Deleted in one and unchanged in the other. + */ + if ((head_deleted && remote_deleted) || + (head_deleted && remote && remote_match) || + (remote_deleted && head && head_match)) { + if (index) + return deleted_entry(index, index, o); + else if (path) + verify_absent(path, "removed", o); + return 0; + } + /* + * Added in both, identically. + */ + if (no_anc_exists && head && remote && same(head, remote)) + return merged_entry(head, index, o); + + } + + /* Below are "no merge" cases, which require that the index be + * up-to-date to avoid the files getting overwritten with + * conflict resolution files. + */ + if (index) { + verify_uptodate(index, o); + } + else if (path) + verify_absent(path, "overwritten", o); + + o->nontrivial_merge = 1; + + /* #2, #3, #4, #6, #7, #9, #11. */ + count = 0; + if (!head_match || !remote_match) { + for (i = 1; i < o->head_idx; i++) { + if (stages[i]) { + keep_entry(stages[i]); + count++; + break; + } + } + } +#if DBRT_DEBUG + else { + fprintf(stderr, "read-tree: warning #16 detected\n"); + show_stage_entry(stderr, "head ", stages[head_match]); + show_stage_entry(stderr, "remote ", stages[remote_match]); + } +#endif + if (head) { count += keep_entry(head); } + if (remote) { count += keep_entry(remote); } + return count; +} + +/* + * 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 + * over a merge failure when it makes sense. For details of the + * "carry forward" rule, please see . + * + */ +int twoway_merge(struct cache_entry **src, + struct unpack_trees_options *o) +{ + struct cache_entry *current = src[0]; + struct cache_entry *oldtree = src[1], *newtree = src[2]; + + if (o->merge_size != 2) + return error("Cannot do a twoway merge of %d trees", + o->merge_size); + + if (current) { + if ((!oldtree && !newtree) || /* 4 and 5 */ + (!oldtree && newtree && + same(current, newtree)) || /* 6 and 7 */ + (oldtree && newtree && + same(oldtree, newtree)) || /* 14 and 15 */ + (oldtree && newtree && + !same(oldtree, newtree) && /* 18 and 19*/ + same(current, newtree))) { + return keep_entry(current); + } + else if (oldtree && !newtree && same(current, oldtree)) { + /* 10 or 11 */ + return deleted_entry(oldtree, current, o); + } + else if (oldtree && newtree && + same(current, oldtree) && !same(current, newtree)) { + /* 20 or 21 */ + return merged_entry(newtree, current, o); + } + else { + /* all other failures */ + if (oldtree) + reject_merge(oldtree); + if (current) + reject_merge(current); + if (newtree) + reject_merge(newtree); + return -1; + } + } + else if (newtree) + return merged_entry(newtree, current, o); + else + return deleted_entry(oldtree, current, o); +} + +/* + * Bind merge. + * + * Keep the index entries at stage0, collapse stage1 but make sure + * stage0 does not have anything there. + */ +int bind_merge(struct cache_entry **src, + struct unpack_trees_options *o) +{ + struct cache_entry *old = src[0]; + struct cache_entry *a = src[1]; + + if (o->merge_size != 1) + return error("Cannot do a bind merge of %d trees\n", + o->merge_size); + if (a && old) + die("Entry '%s' overlaps. Cannot bind.", a->name); + if (!a) + return keep_entry(old); + else + return merged_entry(a, NULL, o); +} + +/* + * One-way merge. + * + * The rule is: + * - take the stat information from stage0, take the data from stage1 + */ +int oneway_merge(struct cache_entry **src, + struct unpack_trees_options *o) +{ + struct cache_entry *old = src[0]; + struct cache_entry *a = src[1]; + + if (o->merge_size != 1) + return error("Cannot do a oneway merge of %d trees", + o->merge_size); + + if (!a) + return deleted_entry(old, old, o); + if (old && same(old, a)) { + if (o->reset) { + struct stat st; + if (lstat(old->name, &st) || + ce_match_stat(old, &st, 1)) + old->ce_flags |= htons(CE_UPDATE); + } + return keep_entry(old); + } + return merged_entry(a, old, o); +} diff --git a/unpack-trees.h b/unpack-trees.h index 38af89a6b7..c4601621cd 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -27,4 +27,9 @@ struct unpack_trees_options { extern int unpack_trees(struct object_list *trees, struct unpack_trees_options *options); +int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o); +int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o); +int bind_merge(struct cache_entry **src, struct unpack_trees_options *o); +int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o); + #endif -- cgit v1.2.3 From 9f5dcb8168d5ea9f0169e11f017ecd9710d85797 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 31 Jul 2006 11:22:13 +0200 Subject: gitweb: Refactor generation of shortlog, tags and heads body Add git_shortlog_body, git_tags_body and git_heads_body to generate table with shortlog, tags and heads respectively in git_summary and git_shortlog, git_tags, git_heads respectively. Better support for lightweight tags in git_read_refs; currently only lightweight tag pointing to tag object is not resolved fully. Shortlog, tags and heads body tables have proper class now (we could use id instead of class). Add support for showing full comment on mouseover to tags list when comment is shortened, similar to how full title of commit was/is shown on mouseover when title was shortened. Changed layout of tags table to better show lightweight tags. Add showing which branch (head) is current branch (current head), using "current_head" class (we could use id instead). Corrected "" and hit_header_div instead of git_header_div. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 383 +++++++++++++++++++++++++----------------------------- gitweb/gitweb.css | 10 +- 2 files changed, 184 insertions(+), 209 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 2f1731870d..c741e739d0 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -1102,6 +1102,10 @@ sub git_read_refs { $ref_item{'refid'} = $ref_id; $ref_item{'epoch'} = $co{'committer_epoch'}; $ref_item{'age'} = $co{'age_string'}; + } else { + $ref_item{'reftype'} = $type; + $ref_item{'name'} = $ref_file; + $ref_item{'refid'} = $ref_id; } push @reflist, \%ref_item; @@ -1111,6 +1115,156 @@ sub git_read_refs { return \@reflist; } +sub git_shortlog_body { + # uses global variable $project + my ($revlist, $from, $to, $refs, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); + + print "
\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $commit = $revlist->[$i]; + #my $ref = defined $refs ? git_get_referencing($refs, $commit) : ''; + my $ref = git_get_referencing($refs, $commit); + my %co = git_read_commit($commit); + my %ad = date_str($co{'author_epoch'}); + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + # git_summary() used print "\n" . + print "\n" . + "\n" . + "\n" . + "\n" . + "\n"; + } + if (defined $extra) { + print "\n" . + "\n" . + "\n"; + } + print "
$co{'age_string'}$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 10)) . ""; + if (length($co{'title_short'}) < length($co{'title'})) { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), + -class => "list", -title => "$co{'title'}"}, + "" . esc_html($co{'title_short'}) . "$ref"); + } else { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), + -class => "list"}, + "" . esc_html($co{'title'}) . "$ref"); + } + print "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . + "
$extra
\n"; +} + +sub git_tags_body { + # uses global variable $project + my ($taglist, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + + print "\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $entry = $taglist->[$i]; + my %tag = %$entry; + my $comment_lines = $tag{'comment'}; + my $comment = shift @$comment_lines; + my $comment_short; + if (defined $comment) { + $comment_short = chop_str($comment, 30, 5); + } + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + ""; + } + if (defined $extra) { + print "\n" . + "\n" . + "\n"; + } + print "
$tag{'age'}" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), + -class => "list"}, "" . esc_html($tag{'name'}) . "") . + ""; + if (defined $comment) { + if (length($comment_short) < length($comment)) { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), + -class => "list", -title => $comment}, $comment_short); + } else { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), + -class => "list"}, $comment); + } + } + print ""; + if ($tag{'type'} eq "tag") { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag"); + } else { + print " "; + } + print "" . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); + if ($tag{'reftype'} eq "commit") { + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); + } elsif ($tag{'reftype'} eq "blob") { + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$tag{'refid'}")}, "raw"); + } + print "
$extra
\n"; +} + +sub git_heads_body { + # uses global variable $project + my ($taglist, $head, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + + print "\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $entry = $taglist->[$i]; + my %tag = %$entry; + my $curr = $tag{'id'} eq $head; + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n" . + ($tag{'id'} eq $head ? "\n" . + "\n" . + ""; + } + if (defined $extra) { + print "\n" . + "\n" . + "\n"; + } + print "
$tag{'age'}" : "") . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), + -class => "list"}, "" . esc_html($tag{'name'}) . "") . + "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") . + "
$extra
\n"; +} + sub git_summary { my $descr = git_read_description($project) || "none"; my $head = git_read_head($project); @@ -1139,138 +1293,36 @@ sub git_summary { my $refs = read_info_ref(); git_header_html(); git_page_nav('summary','', $head); + print "
 
\n"; print "\n" . "\n" . "\n" . "\n" . "
description" . esc_html($descr) . "
owner$owner
last change$cd{'rfc2822'}
\n"; + open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) or die_error(undef, "Open git-rev-list failed."); my @revlist = map { chomp; $_ } <$fd>; close $fd; git_header_div('shortlog'); - my $i = 16; - print "\n"; - my $alternate = 0; - foreach my $commit (@revlist) { - my %co = git_read_commit($commit); - my %ad = date_str($co{'author_epoch'}); - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - if ($i-- > 0) { - my $ref = git_get_referencing($refs, $commit); - print "\n" . - "\n" . - "\n" . - "\n" . - ""; - } else { - print "\n" . - ""; - last; - } - } - print ""; + git_shortlog_body(\@revlist, 0, 15, $refs, + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "...")); my $taglist = git_read_refs("refs/tags"); if (defined @$taglist) { git_header_div('tags'); - my $i = 16; - print "
$co{'age_string'}" . esc_html(chop_str($co{'author_name'}, 10)) . ""; - if (length($co{'title_short'}) < length($co{'title'})) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"}, - "" . esc_html($co{'title_short'}) . "$ref"); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, - "" . esc_html($co{'title'}) . "$ref"); - } - print "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "...") . "
\n"; - my $alternate = 0; - foreach my $entry (@$taglist) { - my %tag = %$entry; - my $comment_lines = $tag{'comment'}; - my $comment = shift @$comment_lines; - if (defined($comment)) { - $comment = chop_str($comment, 30, 5); - } - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - if ($i-- > 0) { - print "\n" . - "\n" . - "\n" . - "\n" . - ""; - } else { - print "\n" . - ""; - last; - } - } - print ""; + git_tags_body($taglist, 0, 15, + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "...")); } my $headlist = git_read_refs("refs/heads"); if (defined @$headlist) { git_header_div('heads'); - my $i = 16; - print "
$tag{'age'}" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), -class => "list"}, - "" . esc_html($tag{'name'}) . "") . - ""; - if (defined($comment)) { - print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, esc_html($comment)); - } - print ""; - if ($tag{'type'} eq "tag") { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | "; - } - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); - if ($tag{'reftype'} eq "commit") { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); - } - print "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "...") . "
\n"; - my $alternate = 0; - foreach my $entry (@$headlist) { - my %tag = %$entry; - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - if ($i-- > 0) { - print "\n" . - "\n" . - "\n" . - ""; - } else { - print "\n" . - ""; - last; - } - } - print ""; + git_heads_body($taglist, $head, 0, 15, + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "...")); } + git_footer_html(); } @@ -1472,48 +1524,11 @@ sub git_tags { git_header_html(); git_page_nav('','', $head,undef,$head); git_header_div('summary', $project); - print "
$tag{'age'}" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), -class => "list"}, - "" . esc_html($tag{'name'}) . "") . - "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") . - "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "...") . "
\n"; my $taglist = git_read_refs("refs/tags"); - my $alternate = 0; if (defined @$taglist) { - foreach my $entry (@$taglist) { - my %tag = %$entry; - my $comment_lines = $tag{'comment'}; - my $comment = shift @$comment_lines; - if (defined($comment)) { - $comment = chop_str($comment, 30, 5); - } - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n" . - "\n" . - "\n" . - "\n" . - ""; - } + git_tags_body($taglist); } - print ""; git_footer_html(); } @@ -1521,32 +1536,13 @@ sub git_heads { my $head = git_read_head($project); git_header_html(); git_page_nav('','', $head,undef,$head); - hit_header_div('summary', $project); - print "
$tag{'age'}" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), -class => "list"}, - "" . esc_html($tag{'name'}) . "") . - ""; - if (defined($comment)) { - print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, $comment); - } - print ""; - if ($tag{'type'} eq "tag") { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | "; - } - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); - if ($tag{'reftype'} eq "commit") { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); - } - print "
\n"; + git_header_div('summary', $project); my $taglist = git_read_refs("refs/heads"); my $alternate = 0; if (defined @$taglist) { - foreach my $entry (@$taglist) { - my %tag = %$entry; - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n" . - "\n" . - "\n" . - ""; - } + git_heads_body($taglist, $head); } - print ""; git_footer_html(); } @@ -2544,48 +2540,19 @@ sub git_shortlog { close $fd; my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist); + my $next_link = ''; + if ($#revlist >= (100 * ($page+1)-1)) { + $next_link = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), + -title => "Alt-n"}, "next"); + } + git_header_html(); git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); git_header_div('summary', $project); - print "
$tag{'age'}" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), -class => "list"}, "" . esc_html($tag{'name'}) . "") . - "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") . - "
\n"; - my $alternate = 0; - for (my $i = ($page * 100); $i <= $#revlist; $i++) { - my $commit = $revlist[$i]; - my $ref = git_get_referencing($refs, $commit); - my %co = git_read_commit($commit); - my %ad = date_str($co{'author_epoch'}); - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n" . - "\n" . - "\n" . - "\n" . - ""; - } - if ($#revlist >= (100 * ($page+1)-1)) { - print "\n" . - "\n" . - "\n"; - } - print ""; + git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link); + git_footer_html(); } diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index fffdb13d09..460e728718 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -181,12 +181,16 @@ td { vertical-align: top; } -td.link { +td.link, td.selflink { padding: 2px 5px; font-family: sans-serif; font-size: 10px; } +td.selflink { + padding-right: 0px; +} + td.sha1 { font-family: monospace; } @@ -196,6 +200,10 @@ td.error { background-color: yellow; } +td.current_head { + text-decoration: underline; +} + table.diff_tree span.file_status.new { color: #008000; } -- cgit v1.2.3 From 822c185907f27c02ce7791985ea9241e37041d5f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 31 Jul 2006 18:33:37 +0200 Subject: gitweb: do not quote path for list version of open "-|" Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index c741e739d0..73d14ffb10 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -2347,7 +2347,7 @@ sub git_history { git_print_page_path($file_name, $ftype); open my $fd, "-|", - $GIT, "rev-list", "--full-history", $hash_base, "--", "\'$file_name\'"; + $GIT, "rev-list", "--full-history", $hash_base, "--", $file_name; print "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 10)) . ""; - if (length($co{'title_short'}) < length($co{'title'})) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"}, - "" . esc_html($co{'title_short'}) . "$ref"); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, - "" . esc_html($co{'title_short'}) . "$ref"); - } - print "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - "
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -title => "Alt-n"}, "next") . - "
\n"; my $alternate = 0; while (my $line = <$fd>) { -- cgit v1.2.3 From 0349b4650969c9c59c58d99c95be3677f5bb2d15 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 31 Jul 2006 20:58:00 +0200 Subject: gitweb: Remove characters entities entirely when shortening string -- correction Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 73d14ffb10..0953b8cd56 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -776,7 +776,7 @@ sub chop_str { my $tail = $2; if (length($tail) > 4) { $tail = " ..."; - $body =~ s/&[^;]$//; # remove chopped character entities + $body =~ s/&[^;]*$//; # remove chopped character entities } return "$body$tail"; } -- cgit v1.2.3 From 717b831178a6c0e86ed5e7cd61002bd9ec0c2a95 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 31 Jul 2006 21:22:15 +0200 Subject: gitweb: Reordering code and dividing it into categories Reorder gitweb code around, divide it into sections (categories) and add some comments. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 1870 +++++++++++++++++++++++++++-------------------------- 1 file changed, 967 insertions(+), 903 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 0953b8cd56..16c368155b 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -160,21 +160,7 @@ if (defined $searchtext) { $searchtext = quotemeta $searchtext; } -sub validate_input { - my $input = shift; - - if ($input =~ m/^[0-9a-fA-F]{40}$/) { - return $input; - } - if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) { - return undef; - } - if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) { - return undef; - } - return $input; -} - +# dispatch if (!defined $action || $action eq "summary") { git_summary(); exit; @@ -235,6 +221,24 @@ if (!defined $action || $action eq "summary") { exit; } +## ====================================================================== +## validation, quoting/unquoting and escaping + +sub validate_input { + my $input = shift; + + if ($input =~ m/^[0-9a-fA-F]{40}$/) { + return $input; + } + if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) { + return undef; + } + if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) { + return undef; + } + return $input; +} + # quote unsafe chars, but keep the slash, even when it's not # correct, but quoted slashes look too horrible in bookmarks sub esc_param { @@ -264,6 +268,29 @@ sub unquote { return $str; } +## ---------------------------------------------------------------------- +## HTML aware string manipulation + +sub chop_str { + my $str = shift; + my $len = shift; + my $add_len = shift || 10; + + # allow only $len chars, but don't cut a word if it would fit in $add_len + # if it doesn't fit, cut it if it's still longer than the dots we would add + $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; + my $body = $1; + my $tail = $2; + if (length($tail) > 4) { + $tail = " ..."; + $body =~ s/&[^;]*$//; # remove chopped character entities + } + return "$body$tail"; +} + +## ---------------------------------------------------------------------- +## functions returning short strings + # CSS class for given age value (in seconds) sub age_class { my $age = shift; @@ -277,202 +304,108 @@ sub age_class { } } -sub git_header_html { - my $status = shift || "200 OK"; - my $expires = shift; +# convert age in seconds to "nn units ago" string +sub age_string { + my $age = shift; + my $age_str; - my $title = "$site_name git"; - if (defined $project) { - $title .= " - $project"; - if (defined $action) { - $title .= "/$action"; - if (defined $file_name) { - $title .= " - $file_name"; - if ($action eq "tree" && $file_name !~ m|/$|) { - $title .= "/"; - } - } - } - } - my $content_type; - # require explicit support from the UA if we are to send the page as - # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. - # we have to do this because MSIE sometimes globs '*/*', pretending to - # support xhtml+xml but choking when it gets what it asked for. - if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) { - $content_type = 'application/xhtml+xml'; + if ($age > 60*60*24*365*2) { + $age_str = (int $age/60/60/24/365); + $age_str .= " years ago"; + } elsif ($age > 60*60*24*(365/12)*2) { + $age_str = int $age/60/60/24/(365/12); + $age_str .= " months ago"; + } elsif ($age > 60*60*24*7*2) { + $age_str = int $age/60/60/24/7; + $age_str .= " weeks ago"; + } elsif ($age > 60*60*24*2) { + $age_str = int $age/60/60/24; + $age_str .= " days ago"; + } elsif ($age > 60*60*2) { + $age_str = int $age/60/60; + $age_str .= " hours ago"; + } elsif ($age > 60*2) { + $age_str = int $age/60; + $age_str .= " min ago"; + } elsif ($age > 2) { + $age_str = int $age; + $age_str .= " sec ago"; } else { - $content_type = 'text/html'; - } - print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); - print < - - - - - - - -$title - -$rss_link - - -EOF - print "
\n" . - "" . - "\"git\"" . - "\n"; - print $cgi->a({-href => esc_param($home_link)}, "projects") . " / "; - if (defined $project) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project)); - if (defined $action) { - print " / $action"; - } - print "\n"; - if (!defined $searchtext) { - $searchtext = ""; - } - my $search_hash; - if (defined $hash_base) { - $search_hash = $hash_base; - } elsif (defined $hash) { - $search_hash = $hash; - } else { - $search_hash = "HEAD"; - } - $cgi->param("a", "search"); - $cgi->param("h", $search_hash); - print $cgi->startform(-method => "get", -action => $my_uri) . - "
\n" . - $cgi->hidden(-name => "p") . "\n" . - $cgi->hidden(-name => "a") . "\n" . - $cgi->hidden(-name => "h") . "\n" . - $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . - "
" . - $cgi->end_form() . "\n"; + $age_str .= " right now"; } - print "
\n"; + return $age_str; } -sub git_footer_html { - print "
\n"; - if (defined $project) { - my $descr = git_read_description($project); - if (defined $descr) { - print "\n"; - } - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n"; +# convert file mode in octal to symbolic file mode string +sub mode_str { + my $mode = oct shift; + + if (S_ISDIR($mode & S_IFMT)) { + return 'drwxr-xr-x'; + } elsif (S_ISLNK($mode)) { + return 'lrwxrwxrwx'; + } elsif (S_ISREG($mode)) { + # git cares only about the executable bit + if ($mode & S_IXUSR) { + return '-rwxr-xr-x'; + } else { + return '-rw-r--r--'; + }; } else { - print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n"; + return '----------'; } - print "
\n" . - "\n" . - ""; } -sub die_error { - my $status = shift || "403 Forbidden"; - my $error = shift || "Malformed query, file missing or permission denied"; +# convert file mode in octal to file type string +sub file_type { + my $mode = oct shift; - git_header_html($status); - print "
\n" . - "

\n" . - "$status - $error\n" . - "
\n" . - "
\n"; - git_footer_html(); - exit; + if (S_ISDIR($mode & S_IFMT)) { + return "directory"; + } elsif (S_ISLNK($mode)) { + return "symlink"; + } elsif (S_ISREG($mode)) { + return "file"; + } else { + return "unknown"; + } } -sub git_page_nav { - my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_; - $extra = '' if !defined $extra; # pager or formats +## ---------------------------------------------------------------------- +## functions returning short HTML fragments, or transforming HTML fragments +## which don't beling to other sections - my @navs = qw(summary shortlog log commit commitdiff tree); - if ($suppress) { - @navs = grep { $_ ne $suppress } @navs; - } +# format line of commit message or tag comment +sub format_log_line_html { + my $line = shift; - my %arg = map { $_, ''} @navs; - if (defined $head) { - for (qw(commit commitdiff)) { - $arg{$_} = ";h=$head"; - } - if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) { - for (qw(shortlog log)) { - $arg{$_} = ";h=$head"; - } + $line = esc_html($line); + $line =~ s/ / /g; + if ($line =~ m/([0-9a-fA-F]{40})/) { + my $hash_text = $1; + if (git_get_type($hash_text) eq "commit") { + my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text); + $line =~ s/$hash_text/$link/; } } - $arg{tree} .= ";h=$treehead" if defined $treehead; - $arg{tree} .= ";hb=$treebase" if defined $treebase; - - print "
\n" . - (join " | ", - map { $_ eq $current - ? $_ - : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_") - } - @navs); - print "
\n$extra
\n" . - "
\n"; + return $line; } -sub git_header_div { - my ($action, $title, $hash, $hash_base) = @_; - my $rest = ''; - - $rest .= ";h=$hash" if $hash; - $rest .= ";hb=$hash_base" if $hash_base; +# format marker of refs pointing to given object +sub git_get_referencing { + my ($refs, $id) = @_; - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"), - -class => "title"}, $title ? $title : $action) . "\n" . - "
\n"; + if (defined $refs->{$id}) { + return ' ' . esc_html($refs->{$id}) . ''; + } else { + return ""; + } } -sub git_get_paging_nav { - my ($action, $hash, $head, $page, $nrevs) = @_; - my $paging_nav; - - - if ($hash ne $head || $page) { - $paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD"); - } else { - $paging_nav .= "HEAD"; - } - - if ($page > 0) { - $paging_nav .= " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)), - -accesskey => "p", -title => "Alt-p"}, "prev"); - } else { - $paging_nav .= " ⋅ prev"; - } - - if ($nrevs >= (100 * ($page+1)-1)) { - $paging_nav .= " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)), - -accesskey => "n", -title => "Alt-n"}, "next"); - } else { - $paging_nav .= " ⋅ next"; - } - - return $paging_nav; -} - -sub git_get_type { - my $hash = shift; - - open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return; - my $type = <$fd>; - close $fd or return; - chomp $type; - return $type; -} +## ---------------------------------------------------------------------- +## git utility subroutines, invoking git commands +# get HEAD ref of given project as hash sub git_read_head { my $project = shift; my $oENV = $ENV{'GIT_DIR'}; @@ -491,6 +424,57 @@ sub git_read_head { return $retval; } +# get type of given object +sub git_get_type { + my $hash = shift; + + open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return; + my $type = <$fd>; + close $fd or return; + chomp $type; + return $type; +} + +sub git_get_project_config { + my $key = shift; + + return unless ($key); + $key =~ s/^gitweb\.//; + return if ($key =~ m/\W/); + + my $val = qx($GIT repo-config --get gitweb.$key); + return ($val); +} + +sub git_get_project_config_bool { + my $val = git_get_project_config (@_); + if ($val and $val =~ m/true|yes|on/) { + return (1); + } + return; # implicit false +} + +# get hash of given path at given ref +sub git_get_hash_by_path { + my $base = shift; + my $path = shift || return undef; + + my $tree = $base; + + open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path + or die_error(undef, "Open git-ls-tree failed."); + my $line = <$fd>; + close $fd or return undef; + + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + return $3; +} + +## ...................................................................... +## git utility functions, directly accessing git repository + +# assumes that PATH is not symref sub git_read_hash { my $path = shift; @@ -513,6 +497,100 @@ sub git_read_description { return $descr; } +sub git_read_projects { + my @list; + + if (-d $projects_list) { + # search in directory + my $dir = $projects_list; + opendir my ($dh), $dir or return undef; + while (my $dir = readdir($dh)) { + if (-e "$projectroot/$dir/HEAD") { + my $pr = { + path => $dir, + }; + push @list, $pr + } + } + closedir($dh); + } elsif (-f $projects_list) { + # read from file(url-encoded): + # 'git%2Fgit.git Linus+Torvalds' + # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' + # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' + open my ($fd), $projects_list or return undef; + while (my $line = <$fd>) { + chomp $line; + my ($path, $owner) = split ' ', $line; + $path = unescape($path); + $owner = unescape($owner); + if (!defined $path) { + next; + } + if (-e "$projectroot/$path/HEAD") { + my $pr = { + path => $path, + owner => decode("utf8", $owner, Encode::FB_DEFAULT), + }; + push @list, $pr + } + } + close $fd; + } + @list = sort {$a->{'path'} cmp $b->{'path'}} @list; + return @list; +} + +sub read_info_ref { + my $type = shift || ""; + my %refs; + # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 + # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} + open my $fd, "$projectroot/$project/info/refs" or return; + while (my $line = <$fd>) { + chomp $line; + # attention: for $type == "" it saves only last path part of ref name + # e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb' + if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) { + if (defined $refs{$1}) { + $refs{$1} .= " / $2"; + } else { + $refs{$1} = $2; + } + } + } + close $fd or return; + return \%refs; +} + +## ---------------------------------------------------------------------- +## parse to hash functions + +sub date_str { + my $epoch = shift; + my $tz = shift || "-0000"; + + my %date; + my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); + my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); + $date{'hour'} = $hour; + $date{'minute'} = $min; + $date{'mday'} = $mday; + $date{'day'} = $days[$wday]; + $date{'month'} = $months[$mon]; + $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; + $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; + + $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; + my $local = $epoch + ((int $1 + ($2/60)) * 3600); + ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); + $date{'hour_local'} = $hour; + $date{'minute_local'} = $min; + $date{'tz_local'} = $tz; + return %date; +} + sub git_read_tag { my $tag_id = shift; my %tag; @@ -548,37 +626,6 @@ sub git_read_tag { return %tag } -sub age_string { - my $age = shift; - my $age_str; - - if ($age > 60*60*24*365*2) { - $age_str = (int $age/60/60/24/365); - $age_str .= " years ago"; - } elsif ($age > 60*60*24*(365/12)*2) { - $age_str = int $age/60/60/24/(365/12); - $age_str .= " months ago"; - } elsif ($age > 60*60*24*7*2) { - $age_str = int $age/60/60/24/7; - $age_str .= " weeks ago"; - } elsif ($age > 60*60*24*2) { - $age_str = int $age/60/60/24; - $age_str .= " days ago"; - } elsif ($age > 60*60*2) { - $age_str = int $age/60/60; - $age_str .= " hours ago"; - } elsif ($age > 60*2) { - $age_str = int $age/60; - $age_str .= " min ago"; - } elsif ($age > 2) { - $age_str = int $age; - $age_str .= " sec ago"; - } else { - $age_str .= " right now"; - } - return $age_str; -} - sub git_read_commit { my $commit_id = shift; my $commit_text = shift; @@ -673,187 +720,78 @@ sub git_read_commit { return %co; } -sub git_diff_print { - my $from = shift; - my $from_name = shift; - my $to = shift; - my $to_name = shift; - my $format = shift || "html"; - - my $from_tmp = "/dev/null"; - my $to_tmp = "/dev/null"; - my $pid = $$; +## ...................................................................... +## parse to array of hashes functions - # create tmp from-file - if (defined $from) { - $from_tmp = "$git_temp/gitweb_" . $$ . "_from"; - open my $fd2, "> $from_tmp"; - open my $fd, "-|", $GIT, "cat-file", "blob", $from; - my @file = <$fd>; - print $fd2 @file; - close $fd2; - close $fd; - } +sub git_read_refs { + my $ref_dir = shift; + my @reflist; - # create tmp to-file - if (defined $to) { - $to_tmp = "$git_temp/gitweb_" . $$ . "_to"; - open my $fd2, "> $to_tmp"; - open my $fd, "-|", $GIT, "cat-file", "blob", $to; - my @file = <$fd>; - print $fd2 @file; - close $fd2; - close $fd; + my @refs; + opendir my $dh, "$projectroot/$project/$ref_dir"; + while (my $dir = readdir($dh)) { + if ($dir =~ m/^\./) { + next; + } + if (-d "$projectroot/$project/$ref_dir/$dir") { + opendir my $dh2, "$projectroot/$project/$ref_dir/$dir"; + my @subdirs = grep !m/^\./, readdir $dh2; + closedir($dh2); + foreach my $subdir (@subdirs) { + push @refs, "$dir/$subdir" + } + next; + } + push @refs, $dir; } - - open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp"; - if ($format eq "plain") { - undef $/; - print <$fd>; - $/ = "\n"; - } else { - while (my $line = <$fd>) { - chomp $line; - my $char = substr($line, 0, 1); - my $diff_class = ""; - if ($char eq '+') { - $diff_class = " add"; - } elsif ($char eq "-") { - $diff_class = " rem"; - } elsif ($char eq "@") { - $diff_class = " chunk_header"; - } elsif ($char eq "\\") { - # skip errors - next; - } - while ((my $pos = index($line, "\t")) != -1) { - if (my $count = (8 - (($pos-1) % 8))) { - my $spaces = ' ' x $count; - $line =~ s/\t/$spaces/; - } + closedir($dh); + foreach my $ref_file (@refs) { + my $ref_id = git_read_hash("$project/$ref_dir/$ref_file"); + my $type = git_get_type($ref_id) || next; + my %ref_item; + my %co; + $ref_item{'type'} = $type; + $ref_item{'id'} = $ref_id; + $ref_item{'epoch'} = 0; + $ref_item{'age'} = "unknown"; + if ($type eq "tag") { + my %tag = git_read_tag($ref_id); + $ref_item{'comment'} = $tag{'comment'}; + if ($tag{'type'} eq "commit") { + %co = git_read_commit($tag{'object'}); + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; + } elsif (defined($tag{'epoch'})) { + my $age = time - $tag{'epoch'}; + $ref_item{'epoch'} = $tag{'epoch'}; + $ref_item{'age'} = age_string($age); } - print "
" . esc_html($line) . "
\n"; - } - } - close $fd; - - if (defined $from) { - unlink($from_tmp); - } - if (defined $to) { - unlink($to_tmp); - } -} - -sub mode_str { - my $mode = oct shift; - - if (S_ISDIR($mode & S_IFMT)) { - return 'drwxr-xr-x'; - } elsif (S_ISLNK($mode)) { - return 'lrwxrwxrwx'; - } elsif (S_ISREG($mode)) { - # git cares only about the executable bit - if ($mode & S_IXUSR) { - return '-rwxr-xr-x'; + $ref_item{'reftype'} = $tag{'type'}; + $ref_item{'name'} = $tag{'name'}; + $ref_item{'refid'} = $tag{'object'}; + } elsif ($type eq "commit"){ + %co = git_read_commit($ref_id); + $ref_item{'reftype'} = "commit"; + $ref_item{'name'} = $ref_file; + $ref_item{'title'} = $co{'title'}; + $ref_item{'refid'} = $ref_id; + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; } else { - return '-rw-r--r--'; - }; - } else { - return '----------'; - } -} - -sub chop_str { - my $str = shift; - my $len = shift; - my $add_len = shift || 10; - - # allow only $len chars, but don't cut a word if it would fit in $add_len - # if it doesn't fit, cut it if it's still longer than the dots we would add - $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; - my $body = $1; - my $tail = $2; - if (length($tail) > 4) { - $tail = " ..."; - $body =~ s/&[^;]*$//; # remove chopped character entities - } - return "$body$tail"; -} - -sub file_type { - my $mode = oct shift; - - if (S_ISDIR($mode & S_IFMT)) { - return "directory"; - } elsif (S_ISLNK($mode)) { - return "symlink"; - } elsif (S_ISREG($mode)) { - return "file"; - } else { - return "unknown"; - } -} - -sub format_log_line_html { - my $line = shift; - - $line = esc_html($line); - $line =~ s/ / /g; - if ($line =~ m/([0-9a-fA-F]{40})/) { - my $hash_text = $1; - if (git_get_type($hash_text) eq "commit") { - my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text); - $line =~ s/$hash_text/$link/; + $ref_item{'reftype'} = $type; + $ref_item{'name'} = $ref_file; + $ref_item{'refid'} = $ref_id; } - } - return $line; -} - -sub date_str { - my $epoch = shift; - my $tz = shift || "-0000"; - - my %date; - my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); - my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); - my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); - $date{'hour'} = $hour; - $date{'minute'} = $min; - $date{'mday'} = $mday; - $date{'day'} = $days[$wday]; - $date{'month'} = $months[$mon]; - $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; - $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; - $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; - my $local = $epoch + ((int $1 + ($2/60)) * 3600); - ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); - $date{'hour_local'} = $hour; - $date{'minute_local'} = $min; - $date{'tz_local'} = $tz; - return %date; + push @reflist, \%ref_item; + } + # sort tags by age + @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; + return \@reflist; } -# git-logo (cached in browser for one day) -sub git_logo { - binmode STDOUT, ':raw'; - print $cgi->header(-type => 'image/png', -expires => '+1d'); - # cat git-logo.png | hexdump -e '16/1 " %02x" "\n"' | sed 's/ /\\x/g' - print "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" . - "\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" . - "\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" . - "\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" . - "\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" . - "\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" . - "\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" . - "\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" . - "\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" . - "\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" . - "\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" . - "\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" . - "\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; -} +## ---------------------------------------------------------------------- +## filesystem-related functions sub get_file_owner { my $path = shift; @@ -868,253 +806,283 @@ sub get_file_owner { return decode("utf8", $owner, Encode::FB_DEFAULT); } -sub git_read_projects { - my @list; +## ...................................................................... +## mimetype related functions - if (-d $projects_list) { - # search in directory - my $dir = $projects_list; - opendir my ($dh), $dir or return undef; - while (my $dir = readdir($dh)) { - if (-e "$projectroot/$dir/HEAD") { - my $pr = { - path => $dir, - }; - push @list, $pr - } - } - closedir($dh); - } elsif (-f $projects_list) { - # read from file(url-encoded): - # 'git%2Fgit.git Linus+Torvalds' - # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' - # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' - open my ($fd), $projects_list or return undef; - while (my $line = <$fd>) { - chomp $line; - my ($path, $owner) = split ' ', $line; - $path = unescape($path); - $owner = unescape($owner); - if (!defined $path) { - next; - } - if (-e "$projectroot/$path/HEAD") { - my $pr = { - path => $path, - owner => decode("utf8", $owner, Encode::FB_DEFAULT), - }; - push @list, $pr - } +sub mimetype_guess_file { + my $filename = shift; + my $mimemap = shift; + -r $mimemap or return undef; + + my %mimemap; + open(MIME, $mimemap) or return undef; + while () { + my ($mime, $exts) = split(/\t+/); + my @exts = split(/\s+/, $exts); + foreach my $ext (@exts) { + $mimemap{$ext} = $mime; } - close $fd; } - @list = sort {$a->{'path'} cmp $b->{'path'}} @list; - return @list; -} + close(MIME); -sub git_get_project_config { - my $key = shift; + $filename =~ /\.(.*?)$/; + return $mimemap{$1}; +} - return unless ($key); - $key =~ s/^gitweb\.//; - return if ($key =~ m/\W/); +sub mimetype_guess { + my $filename = shift; + my $mime; + $filename =~ /\./ or return undef; - my $val = qx($GIT repo-config --get gitweb.$key); - return ($val); + if ($mimetypes_file) { + my $file = $mimetypes_file; + #$file =~ m#^/# or $file = "$projectroot/$path/$file"; + $mime = mimetype_guess_file($filename, $file); + } + $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); + return $mime; } -sub git_get_project_config_bool { - my $val = git_get_project_config (@_); - if ($val and $val =~ m/true|yes|on/) { - return (1); - } - return; # implicit false -} +sub git_blob_plain_mimetype { + my $fd = shift; + my $filename = shift; -sub git_project_list { - my @list = git_read_projects(); - my @projects; - if (!@list) { - die_error(undef, "No project found."); - } - foreach my $pr (@list) { - my $head = git_read_head($pr->{'path'}); - if (!defined $head) { - next; - } - $ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}"; - my %co = git_read_commit($head); - if (!%co) { - next; - } - $pr->{'commit'} = \%co; - if (!defined $pr->{'descr'}) { - my $descr = git_read_description($pr->{'path'}) || ""; - $pr->{'descr'} = chop_str($descr, 25, 5); - } - if (!defined $pr->{'owner'}) { - $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || ""; - } - push @projects, $pr; - } - git_header_html(); - if (-f $home_text) { - print "
\n"; - open (my $fd, $home_text); - print <$fd>; - close $fd; - print "
\n"; - } - print "
\n" . - "\n"; - if (!defined($order) || (defined($order) && ($order eq "project"))) { - @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; - print "\n"; - } else { - print "\n"; + if ($filename) { + my $mime = mimetype_guess($filename); + $mime and return $mime; } - if (defined($order) && ($order eq "descr")) { - @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; - print "\n"; + + # just in case + return $default_blob_plain_mimetype unless $fd; + + if (-T $fd) { + return 'text/plain' . + ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); + } elsif (! $filename) { + return 'application/octet-stream'; + } elsif ($filename =~ m/\.png$/i) { + return 'image/png'; + } elsif ($filename =~ m/\.gif$/i) { + return 'image/gif'; + } elsif ($filename =~ m/\.jpe?g$/i) { + return 'image/jpeg'; } else { - print "\n"; + return 'application/octet-stream'; } - if (defined($order) && ($order eq "owner")) { - @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; - print "\n"; - } else { - print "\n"; +} + +## ====================================================================== +## functions printing HTML: header, footer, error page + +sub git_header_html { + my $status = shift || "200 OK"; + my $expires = shift; + + my $title = "$site_name git"; + if (defined $project) { + $title .= " - $project"; + if (defined $action) { + $title .= "/$action"; + if (defined $file_name) { + $title .= " - $file_name"; + if ($action eq "tree" && $file_name !~ m|/$|) { + $title .= "/"; + } + } + } } - if (defined($order) && ($order eq "age")) { - @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects; - print "\n"; + my $content_type; + # require explicit support from the UA if we are to send the page as + # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. + # we have to do this because MSIE sometimes globs '*/*', pretending to + # support xhtml+xml but choking when it gets what it asked for. + if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) { + $content_type = 'application/xhtml+xml'; } else { - print "\n"; + $content_type = 'text/html'; } - print "\n" . - "\n"; - my $alternate = 0; - foreach my $pr (@projects) { - if ($alternate) { - print "\n"; + print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); + print < + + + + + + + +$title + +$rss_link + + +EOF + print "
\n" . + "" . + "\"git\"" . + "\n"; + print $cgi->a({-href => esc_param($home_link)}, "projects") . " / "; + if (defined $project) { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project)); + if (defined $action) { + print " / $action"; + } + print "\n"; + if (!defined $searchtext) { + $searchtext = ""; + } + my $search_hash; + if (defined $hash_base) { + $search_hash = $hash_base; + } elsif (defined $hash) { + $search_hash = $hash; } else { - print "
\n"; + $search_hash = "HEAD"; } - $alternate ^= 1; - print "\n" . - "\n" . - "\n"; - print "\n" . - "\n" . - "\n"; + $cgi->param("a", "search"); + $cgi->param("h", $search_hash); + print $cgi->startform(-method => "get", -action => $my_uri) . + "
\n" . + $cgi->hidden(-name => "p") . "\n" . + $cgi->hidden(-name => "a") . "\n" . + $cgi->hidden(-name => "h") . "\n" . + $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . + "
" . + $cgi->end_form() . "\n"; } - print "
Project" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "Description" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "Owner" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "Last Change" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "" . esc_html($pr->{'descr'}) . "" . chop_str($pr->{'owner'}, 15) . "{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . - "
\n"; + print "
\n"; +} + +sub git_footer_html { + print "
\n"; + if (defined $project) { + my $descr = git_read_description($project); + if (defined $descr) { + print "\n"; + } + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n"; + } else { + print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n"; + } + print "
\n" . + "\n" . + ""; +} + +sub die_error { + my $status = shift || "403 Forbidden"; + my $error = shift || "Malformed query, file missing or permission denied"; + + git_header_html($status); + print "
\n" . + "

\n" . + "$status - $error\n" . + "
\n" . + "
\n"; git_footer_html(); + exit; } -sub read_info_ref { - my $type = shift || ""; - my %refs; - # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 - # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} - open my $fd, "$projectroot/$project/info/refs" or return; - while (my $line = <$fd>) { - chomp $line; - # attention: for $type == "" it saves only last path part of ref name - # e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb' - if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) { - if (defined $refs{$1}) { - $refs{$1} .= " / $2"; - } else { - $refs{$1} = $2; +## ---------------------------------------------------------------------- +## functions printing or outputting HTML: navigation + +sub git_page_nav { + my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_; + $extra = '' if !defined $extra; # pager or formats + + my @navs = qw(summary shortlog log commit commitdiff tree); + if ($suppress) { + @navs = grep { $_ ne $suppress } @navs; + } + + my %arg = map { $_, ''} @navs; + if (defined $head) { + for (qw(commit commitdiff)) { + $arg{$_} = ";h=$head"; + } + if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) { + for (qw(shortlog log)) { + $arg{$_} = ";h=$head"; } } } - close $fd or return; - return \%refs; + $arg{tree} .= ";h=$treehead" if defined $treehead; + $arg{tree} .= ";hb=$treebase" if defined $treebase; + + print "
\n" . + (join " | ", + map { $_ eq $current + ? $_ + : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_") + } + @navs); + print "
\n$extra
\n" . + "
\n"; } -sub git_get_referencing { - my ($refs, $id) = @_; +sub git_get_paging_nav { + my ($action, $hash, $head, $page, $nrevs) = @_; + my $paging_nav; - if (defined $refs->{$id}) { - return ' ' . esc_html($refs->{$id}) . ''; + + if ($hash ne $head || $page) { + $paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD"); } else { - return ""; + $paging_nav .= "HEAD"; + } + + if ($page > 0) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)), + -accesskey => "p", -title => "Alt-p"}, "prev"); + } else { + $paging_nav .= " ⋅ prev"; + } + + if ($nrevs >= (100 * ($page+1)-1)) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)), + -accesskey => "n", -title => "Alt-n"}, "next"); + } else { + $paging_nav .= " ⋅ next"; } + + return $paging_nav; } -sub git_read_refs { - my $ref_dir = shift; - my @reflist; +## ...................................................................... +## functions printing or outputting HTML: div + +sub git_header_div { + my ($action, $title, $hash, $hash_base) = @_; + my $rest = ''; + + $rest .= ";h=$hash" if $hash; + $rest .= ";hb=$hash_base" if $hash_base; + + print "
\n" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"), + -class => "title"}, $title ? $title : $action) . "\n" . + "
\n"; +} - my @refs; - opendir my $dh, "$projectroot/$project/$ref_dir"; - while (my $dir = readdir($dh)) { - if ($dir =~ m/^\./) { - next; - } - if (-d "$projectroot/$project/$ref_dir/$dir") { - opendir my $dh2, "$projectroot/$project/$ref_dir/$dir"; - my @subdirs = grep !m/^\./, readdir $dh2; - closedir($dh2); - foreach my $subdir (@subdirs) { - push @refs, "$dir/$subdir" - } - next; - } - push @refs, $dir; - } - closedir($dh); - foreach my $ref_file (@refs) { - my $ref_id = git_read_hash("$project/$ref_dir/$ref_file"); - my $type = git_get_type($ref_id) || next; - my %ref_item; - my %co; - $ref_item{'type'} = $type; - $ref_item{'id'} = $ref_id; - $ref_item{'epoch'} = 0; - $ref_item{'age'} = "unknown"; - if ($type eq "tag") { - my %tag = git_read_tag($ref_id); - $ref_item{'comment'} = $tag{'comment'}; - if ($tag{'type'} eq "commit") { - %co = git_read_commit($tag{'object'}); - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } elsif (defined($tag{'epoch'})) { - my $age = time - $tag{'epoch'}; - $ref_item{'epoch'} = $tag{'epoch'}; - $ref_item{'age'} = age_string($age); - } - $ref_item{'reftype'} = $tag{'type'}; - $ref_item{'name'} = $tag{'name'}; - $ref_item{'refid'} = $tag{'object'}; - } elsif ($type eq "commit"){ - %co = git_read_commit($ref_id); - $ref_item{'reftype'} = "commit"; - $ref_item{'name'} = $ref_file; - $ref_item{'title'} = $co{'title'}; - $ref_item{'refid'} = $ref_id; - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } else { - $ref_item{'reftype'} = $type; - $ref_item{'name'} = $ref_file; - $ref_item{'refid'} = $ref_id; - } +sub git_print_page_path { + my $name = shift; + my $type = shift; - push @reflist, \%ref_item; + if (!defined $name) { + print "
/
\n"; + } elsif ($type =~ "blob") { + print "
" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "
\n"; + } else { + print "
" . esc_html($name) . "
\n"; } - # sort tags by age - @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; - return \@reflist; } +## ...................................................................... +## functions printing large fragments of HTML + sub git_shortlog_body { # uses global variable $project my ($revlist, $from, $to, $refs, $extra) = @_; @@ -1163,106 +1131,291 @@ sub git_shortlog_body { print "
\n"; } -sub git_tags_body { - # uses global variable $project - my ($taglist, $from, $to, $extra) = @_; - $from = 0 unless defined $from; - $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); +sub git_tags_body { + # uses global variable $project + my ($taglist, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + + print "\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $entry = $taglist->[$i]; + my %tag = %$entry; + my $comment_lines = $tag{'comment'}; + my $comment = shift @$comment_lines; + my $comment_short; + if (defined $comment) { + $comment_short = chop_str($comment, 30, 5); + } + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + ""; + } + if (defined $extra) { + print "\n" . + "\n" . + "\n"; + } + print "
$tag{'age'}" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), + -class => "list"}, "" . esc_html($tag{'name'}) . "") . + ""; + if (defined $comment) { + if (length($comment_short) < length($comment)) { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), + -class => "list", -title => $comment}, $comment_short); + } else { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), + -class => "list"}, $comment); + } + } + print ""; + if ($tag{'type'} eq "tag") { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag"); + } else { + print " "; + } + print "" . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); + if ($tag{'reftype'} eq "commit") { + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); + } elsif ($tag{'reftype'} eq "blob") { + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$tag{'refid'}")}, "raw"); + } + print "
$extra
\n"; +} + +sub git_heads_body { + # uses global variable $project + my ($taglist, $head, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + + print "\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $entry = $taglist->[$i]; + my %tag = %$entry; + my $curr = $tag{'id'} eq $head; + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n" . + ($tag{'id'} eq $head ? "\n" . + "\n" . + ""; + } + if (defined $extra) { + print "\n" . + "\n" . + "\n"; + } + print "
$tag{'age'}" : "") . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), + -class => "list"}, "" . esc_html($tag{'name'}) . "") . + "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") . + "
$extra
\n"; +} + +## ---------------------------------------------------------------------- +## functions printing large fragments, format as one of arguments + +sub git_diff_print { + my $from = shift; + my $from_name = shift; + my $to = shift; + my $to_name = shift; + my $format = shift || "html"; + + my $from_tmp = "/dev/null"; + my $to_tmp = "/dev/null"; + my $pid = $$; + + # create tmp from-file + if (defined $from) { + $from_tmp = "$git_temp/gitweb_" . $$ . "_from"; + open my $fd2, "> $from_tmp"; + open my $fd, "-|", $GIT, "cat-file", "blob", $from; + my @file = <$fd>; + print $fd2 @file; + close $fd2; + close $fd; + } + + # create tmp to-file + if (defined $to) { + $to_tmp = "$git_temp/gitweb_" . $$ . "_to"; + open my $fd2, "> $to_tmp"; + open my $fd, "-|", $GIT, "cat-file", "blob", $to; + my @file = <$fd>; + print $fd2 @file; + close $fd2; + close $fd; + } + + open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp"; + if ($format eq "plain") { + undef $/; + print <$fd>; + $/ = "\n"; + } else { + while (my $line = <$fd>) { + chomp $line; + my $char = substr($line, 0, 1); + my $diff_class = ""; + if ($char eq '+') { + $diff_class = " add"; + } elsif ($char eq "-") { + $diff_class = " rem"; + } elsif ($char eq "@") { + $diff_class = " chunk_header"; + } elsif ($char eq "\\") { + # skip errors + next; + } + while ((my $pos = index($line, "\t")) != -1) { + if (my $count = (8 - (($pos-1) % 8))) { + my $spaces = ' ' x $count; + $line =~ s/\t/$spaces/; + } + } + print "
" . esc_html($line) . "
\n"; + } + } + close $fd; + + if (defined $from) { + unlink($from_tmp); + } + if (defined $to) { + unlink($to_tmp); + } +} + + +## ====================================================================== +## ====================================================================== +## actions + +# git-logo (cached in browser for one day) +sub git_logo { + binmode STDOUT, ':raw'; + print $cgi->header(-type => 'image/png', -expires => '+1d'); + # cat git-logo.png | hexdump -e '16/1 " %02x" "\n"' | sed 's/ /\\x/g' + print "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" . + "\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" . + "\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" . + "\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" . + "\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" . + "\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" . + "\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" . + "\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" . + "\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" . + "\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" . + "\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" . + "\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" . + "\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; +} - print "\n"; - my $alternate = 0; - for (my $i = $from; $i <= $to; $i++) { - my $entry = $taglist->[$i]; - my %tag = %$entry; - my $comment_lines = $tag{'comment'}; - my $comment = shift @$comment_lines; - my $comment_short; - if (defined $comment) { - $comment_short = chop_str($comment, 30, 5); - } - if ($alternate) { - print "\n"; - } else { - print "\n"; +sub git_project_list { + my @list = git_read_projects(); + my @projects; + if (!@list) { + die_error(undef, "No project found."); + } + foreach my $pr (@list) { + my $head = git_read_head($pr->{'path'}); + if (!defined $head) { + next; } - $alternate ^= 1; - print "\n" . - "\n" . - "\n" . - "\n" . - "\n" . - ""; + push @projects, $pr; } - if (defined $extra) { - print "\n" . - "\n" . - "\n"; + git_header_html(); + if (-f $home_text) { + print "
\n"; + open (my $fd, $home_text); + print <$fd>; + close $fd; + print "
\n"; } - print "
$tag{'age'}" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), - -class => "list"}, "" . esc_html($tag{'name'}) . "") . - ""; - if (defined $comment) { - if (length($comment_short) < length($comment)) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), - -class => "list", -title => $comment}, $comment_short); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), - -class => "list"}, $comment); - } + $ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}"; + my %co = git_read_commit($head); + if (!%co) { + next; } - print ""; - if ($tag{'type'} eq "tag") { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag"); - } else { - print " "; + $pr->{'commit'} = \%co; + if (!defined $pr->{'descr'}) { + my $descr = git_read_description($pr->{'path'}) || ""; + $pr->{'descr'} = chop_str($descr, 25, 5); } - print "" . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); - if ($tag{'reftype'} eq "commit") { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); - } elsif ($tag{'reftype'} eq "blob") { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$tag{'refid'}")}, "raw"); + if (!defined $pr->{'owner'}) { + $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || ""; } - print "
$extra
\n"; -} - -sub git_heads_body { - # uses global variable $project - my ($taglist, $head, $from, $to, $extra) = @_; - $from = 0 unless defined $from; - $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); - - print "\n"; + print "
\n" . + "\n"; + if (!defined($order) || (defined($order) && ($order eq "project"))) { + @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; + print "\n"; + } else { + print "\n"; + } + if (defined($order) && ($order eq "descr")) { + @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; + print "\n"; + } else { + print "\n"; + } + if (defined($order) && ($order eq "owner")) { + @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; + print "\n"; + } else { + print "\n"; + } + if (defined($order) && ($order eq "age")) { + @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects; + print "\n"; + } else { + print "\n"; + } + print "\n" . + "\n"; my $alternate = 0; - for (my $i = $from; $i <= $to; $i++) { - my $entry = $taglist->[$i]; - my %tag = %$entry; - my $curr = $tag{'id'} eq $head; + foreach my $pr (@projects) { if ($alternate) { print "\n"; } else { print "\n"; } $alternate ^= 1; - print "\n" . - ($tag{'id'} eq $head ? "\n" . + print "\n" . + "\n" . + "\n"; + print "\n" . "\n" . - ""; - } - if (defined $extra) { - print "\n" . - "\n" . "\n"; } print "
Project" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "Description" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "Owner" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "Last Change" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "
$tag{'age'}" : "") . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), - -class => "list"}, "" . esc_html($tag{'name'}) . "") . - "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "" . esc_html($pr->{'descr'}) . "" . chop_str($pr->{'owner'}, 15) . "{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") . + $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . "
$extra
\n"; + git_footer_html(); } sub git_summary { @@ -1326,20 +1479,6 @@ sub git_summary { git_footer_html(); } -sub git_print_page_path { - my $name = shift; - my $type = shift; - - if (!defined $name) { - print "
/
\n"; - } elsif ($type =~ "blob") { - print "
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "
\n"; - } else { - print "
" . esc_html($name) . "
\n"; - } -} - sub git_tag { my $head = git_read_head($project); git_header_html(); @@ -1500,128 +1639,50 @@ HTML my $spaces = ' ' x $count; $data =~ s/\t/$spaces/; } - } - $data = esc_html ($data); - - print < -
$short_rev..$age_str$author$lineno$data
\n\n"; - close $fd or print "Reading blob failed.\n"; - print "
"; - git_footer_html(); -} - -sub git_tags { - my $head = git_read_head($project); - git_header_html(); - git_page_nav('','', $head,undef,$head); - git_header_div('summary', $project); - - my $taglist = git_read_refs("refs/tags"); - if (defined @$taglist) { - git_tags_body($taglist); - } - git_footer_html(); -} - -sub git_heads { - my $head = git_read_head($project); - git_header_html(); - git_page_nav('','', $head,undef,$head); - git_header_div('summary', $project); - - my $taglist = git_read_refs("refs/heads"); - my $alternate = 0; - if (defined @$taglist) { - git_heads_body($taglist, $head); - } - git_footer_html(); -} - -sub git_get_hash_by_path { - my $base = shift; - my $path = shift || return undef; - - my $tree = $base; - - open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path - or die_error(undef, "Open git-ls-tree failed."); - my $line = <$fd>; - close $fd or return undef; - - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; - return $3; -} - -sub mimetype_guess_file { - my $filename = shift; - my $mimemap = shift; - -r $mimemap or return undef; - - my %mimemap; - open(MIME, $mimemap) or return undef; - while () { - my ($mime, $exts) = split(/\t+/); - my @exts = split(/\s+/, $exts); - foreach my $ext (@exts) { - $mimemap{$ext} = $mime; - } - } - close(MIME); + } + $data = esc_html ($data); - $filename =~ /\.(.*?)$/; - return $mimemap{$1}; + print < + $short_rev.. + $age_str + $author + $lineno + $data + +HTML + } # while (my $line = <$fd>) + print "\n\n"; + close $fd or print "Reading blob failed.\n"; + print "
"; + git_footer_html(); } -sub mimetype_guess { - my $filename = shift; - my $mime; - $filename =~ /\./ or return undef; +sub git_tags { + my $head = git_read_head($project); + git_header_html(); + git_page_nav('','', $head,undef,$head); + git_header_div('summary', $project); - if ($mimetypes_file) { - my $file = $mimetypes_file; - #$file =~ m#^/# or $file = "$projectroot/$path/$file"; - $mime = mimetype_guess_file($filename, $file); + my $taglist = git_read_refs("refs/tags"); + if (defined @$taglist) { + git_tags_body($taglist); } - $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); - return $mime; + git_footer_html(); } -sub git_blob_plain_mimetype { - my $fd = shift; - my $filename = shift; - - if ($filename) { - my $mime = mimetype_guess($filename); - $mime and return $mime; - } - - # just in case - return $default_blob_plain_mimetype unless $fd; +sub git_heads { + my $head = git_read_head($project); + git_header_html(); + git_page_nav('','', $head,undef,$head); + git_header_div('summary', $project); - if (-T $fd) { - return 'text/plain' . - ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); - } elsif (! $filename) { - return 'application/octet-stream'; - } elsif ($filename =~ m/\.png$/i) { - return 'image/png'; - } elsif ($filename =~ m/\.gif$/i) { - return 'image/gif'; - } elsif ($filename =~ m/\.jpe?g$/i) { - return 'image/jpeg'; - } else { - return 'application/octet-stream'; + my $taglist = git_read_refs("refs/heads"); + my $alternate = 0; + if (defined @$taglist) { + git_heads_body($taglist, $head); } + git_footer_html(); } sub git_blob_plain { @@ -1793,98 +1854,6 @@ sub git_tree { git_footer_html(); } -sub git_rss { - # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ - open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) - or die_error(undef, "Open git-rev-list failed."); - my @revlist = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading rev-list failed."); - print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "\n". - "\n"; - print "\n"; - print "$project\n". - "" . esc_html("$my_url?p=$project;a=summary") . "\n". - "$project log\n". - "en\n"; - - for (my $i = 0; $i <= $#revlist; $i++) { - my $commit = $revlist[$i]; - my %co = git_read_commit($commit); - # we read 150, we always show 30 and the ones more recent than 48 hours - if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { - last; - } - my %cd = date_str($co{'committer_epoch'}); - open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next; - my @difftree = map { chomp; $_ } <$fd>; - close $fd or next; - print "\n" . - "" . - sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . - "\n" . - "" . esc_html($co{'author'}) . "\n" . - "$cd{'rfc2822'}\n" . - "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" . - "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" . - "" . esc_html($co{'title'}) . "\n" . - "" . - "\n"; - } - print "
\n"; - foreach my $line (@difftree) { - if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { - next; - } - my $file = validate_input(unquote($7)); - $file = decode("utf8", $file, Encode::FB_DEFAULT); - print "$file
\n"; - } - print "]]>\n" . - "
\n" . - "
\n"; - } - print "
"; -} - -sub git_opml { - my @list = git_read_projects(); - - print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "\n". - "\n". - "". - " $site_name Git OPML Export\n". - "\n". - "\n". - "\n"; - - foreach my $pr (@list) { - my %proj = %$pr; - my $head = git_read_head($proj{'path'}); - if (!defined $head) { - next; - } - $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; - my %co = git_read_commit($head); - if (!%co) { - next; - } - - my $path = esc_html(chop_str($proj{'path'}, 25, 5)); - my $rss = "$my_url?p=$proj{'path'};a=rss"; - my $html = "$my_url?p=$proj{'path'};a=summary"; - print "\n"; - } - print "\n". - "\n". - "\n"; -} - sub git_log { my $head = git_read_head($project); if (!defined $hash) { @@ -2556,3 +2525,98 @@ sub git_shortlog { git_footer_html(); } + +## ...................................................................... +## feeds (RSS, OPML) + +sub git_rss { + # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ + open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) + or die_error(undef, "Open git-rev-list failed."); + my @revlist = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading rev-list failed."); + print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print "\n". + "\n"; + print "\n"; + print "$project\n". + "" . esc_html("$my_url?p=$project;a=summary") . "\n". + "$project log\n". + "en\n"; + + for (my $i = 0; $i <= $#revlist; $i++) { + my $commit = $revlist[$i]; + my %co = git_read_commit($commit); + # we read 150, we always show 30 and the ones more recent than 48 hours + if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { + last; + } + my %cd = date_str($co{'committer_epoch'}); + open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next; + my @difftree = map { chomp; $_ } <$fd>; + close $fd or next; + print "\n" . + "" . + sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . + "\n" . + "" . esc_html($co{'author'}) . "\n" . + "$cd{'rfc2822'}\n" . + "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" . + "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" . + "" . esc_html($co{'title'}) . "\n" . + "" . + "\n"; + } + print "
\n"; + foreach my $line (@difftree) { + if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { + next; + } + my $file = validate_input(unquote($7)); + $file = decode("utf8", $file, Encode::FB_DEFAULT); + print "$file
\n"; + } + print "]]>\n" . + "
\n" . + "
\n"; + } + print "
"; +} + +sub git_opml { + my @list = git_read_projects(); + + print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print "\n". + "\n". + "". + " $site_name Git OPML Export\n". + "\n". + "\n". + "\n"; + + foreach my $pr (@list) { + my %proj = %$pr; + my $head = git_read_head($proj{'path'}); + if (!defined $head) { + next; + } + $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; + my %co = git_read_commit($head); + if (!%co) { + next; + } + + my $path = esc_html(chop_str($proj{'path'}, 25, 5)); + my $rss = "$my_url?p=$proj{'path'};a=rss"; + my $html = "$my_url?p=$proj{'path'};a=summary"; + print "\n"; + } + print "\n". + "\n". + "\n"; +} -- cgit v1.2.3 From c27d205aaefb654c12a4ab9e0b4fae1882c0fc70 Mon Sep 17 00:00:00 2001 From: Matthias Lederhofer Date: Mon, 31 Jul 2006 15:27:00 +0200 Subject: pager: environment variable GIT_PAGER to override PAGER Signed-off-by: Matthias Lederhofer Signed-off-by: Junio C Hamano --- Documentation/git.txt | 3 +++ pager.c | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Documentation/git.txt b/Documentation/git.txt index 7310a2b8b8..d24388368e 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -627,6 +627,9 @@ git Diffs other ~~~~~ +'GIT_PAGER':: + This environment variable overrides `$PAGER`. + 'GIT_TRACE':: If this variable is set git will print `trace:` messages on stderr telling about alias expansion, built-in command diff --git a/pager.c b/pager.c index 280f57f796..dcb398da8e 100644 --- a/pager.c +++ b/pager.c @@ -15,10 +15,12 @@ void setup_pager(void) { pid_t pid; int fd[2]; - const char *pager = getenv("PAGER"); + const char *pager = getenv("GIT_PAGER"); if (!isatty(1)) return; + if (!pager) + pager = getenv("PAGER"); if (!pager) pager = "less"; else if (!*pager || !strcmp(pager, "cat")) -- cgit v1.2.3 From 8e85cdc4efd15fa70dfbbfebaf3ef9a758c2772c Mon Sep 17 00:00:00 2001 From: Matthias Lederhofer Date: Mon, 31 Jul 2006 23:46:25 +0200 Subject: gitweb: use a hash to lookup the sub for an action Signed-off-by: Matthias Lederhofer Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 81 +++++++++++++++++-------------------------------------- 1 file changed, 25 insertions(+), 56 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 16c368155b..4e79390aff 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -161,65 +161,34 @@ if (defined $searchtext) { } # dispatch -if (!defined $action || $action eq "summary") { - git_summary(); - exit; -} elsif ($action eq "heads") { - git_heads(); - exit; -} elsif ($action eq "tags") { - git_tags(); - exit; -} elsif ($action eq "blob") { - git_blob(); - exit; -} elsif ($action eq "blob_plain") { - git_blob_plain(); - exit; -} elsif ($action eq "tree") { - git_tree(); - exit; -} elsif ($action eq "rss") { - git_rss(); - exit; -} elsif ($action eq "commit") { - git_commit(); - exit; -} elsif ($action eq "log") { - git_log(); - exit; -} elsif ($action eq "blobdiff") { - git_blobdiff(); - exit; -} elsif ($action eq "blobdiff_plain") { - git_blobdiff_plain(); - exit; -} elsif ($action eq "commitdiff") { - git_commitdiff(); - exit; -} elsif ($action eq "commitdiff_plain") { - git_commitdiff_plain(); - exit; -} elsif ($action eq "history") { - git_history(); - exit; -} elsif ($action eq "search") { - git_search(); - exit; -} elsif ($action eq "shortlog") { - git_shortlog(); - exit; -} elsif ($action eq "tag") { - git_tag(); - exit; -} elsif ($action eq "blame") { - git_blame2(); - exit; -} else { +my %actions = ( + "blame" => \&git_blame2, + "blobdiff" => \&git_blobdiff, + "blobdiff_plain" => \&git_blobdiff_plain, + "blob" => \&git_blob, + "blob_plain" => \&git_blob_plain, + "commitdiff" => \&git_commitdiff, + "commitdiff_plain" => \&git_commitdiff_plain, + "commit" => \&git_commit, + "heads" => \&git_heads, + "history" => \&git_history, + "log" => \&git_log, + "rss" => \&git_rss, + "search" => \&git_search, + "shortlog" => \&git_shortlog, + "summary" => \&git_summary, + "tag" => \&git_tag, + "tags" => \&git_tags, + "tree" => \&git_tree, +); + +$action = 'summary' if (!defined($action)); +if (!defined($actions{$action})) { undef $action; die_error(undef, "Unknown action."); - exit; } +$actions{$action}->(); +exit; ## ====================================================================== ## validation, quoting/unquoting and escaping -- cgit v1.2.3 From 7a13b999a5aa6de236c7b16139c765eb9e5cb3c4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 31 Jul 2006 19:18:34 -0700 Subject: gitweb: There can be more than two levels of subdirectories Earlier code to read .git/refs/{tags,heads} hierarchy had a hardcoded up-to-two-level assumption. Lift it by using File::Find. Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 4e79390aff..9569af09b5 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -14,6 +14,7 @@ use CGI::Util qw(unescape); use CGI::Carp qw(fatalsToBrowser); use Encode; use Fcntl ':mode'; +use File::Find qw(); binmode STDOUT, ':utf8'; our $cgi = new CGI; @@ -697,23 +698,14 @@ sub git_read_refs { my @reflist; my @refs; - opendir my $dh, "$projectroot/$project/$ref_dir"; - while (my $dir = readdir($dh)) { - if ($dir =~ m/^\./) { - next; - } - if (-d "$projectroot/$project/$ref_dir/$dir") { - opendir my $dh2, "$projectroot/$project/$ref_dir/$dir"; - my @subdirs = grep !m/^\./, readdir $dh2; - closedir($dh2); - foreach my $subdir (@subdirs) { - push @refs, "$dir/$subdir" - } - next; + my $pfxlen = length("$projectroot/$project/$ref_dir"); + File::Find::find(sub { + return if (/^\./); + if (-f $_) { + push @refs, substr($File::Find::name, $pfxlen + 1); } - push @refs, $dir; - } - closedir($dh); + }, "$projectroot/$project/$ref_dir"); + foreach my $ref_file (@refs) { my $ref_id = git_read_hash("$project/$ref_dir/$ref_file"); my $type = git_get_type($ref_id) || next; -- cgit v1.2.3 From b77aeb249a0ebdb8daa85ba58bbecde537f84025 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 31 Jul 2006 19:12:18 -0700 Subject: gitweb: an obvious cut and paste error. Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 9569af09b5..902b96a914 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -1433,7 +1433,7 @@ sub git_summary { my $headlist = git_read_refs("refs/heads"); if (defined @$headlist) { git_header_div('heads'); - git_heads_body($taglist, $head, 0, 15, + git_heads_body($headlist, $head, 0, 15, $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "...")); } -- cgit v1.2.3 From 46b059d718777ab146b8089afd3d2fcdd6000154 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 31 Jul 2006 19:24:37 -0700 Subject: gitweb: fix use of uninitialized value. Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 902b96a914..f65b5d5ce2 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -779,9 +779,11 @@ sub mimetype_guess_file { open(MIME, $mimemap) or return undef; while () { my ($mime, $exts) = split(/\t+/); - my @exts = split(/\s+/, $exts); - foreach my $ext (@exts) { - $mimemap{$ext} = $mime; + if (defined $exts) { + my @exts = split(/\s+/, $exts); + foreach my $ext (@exts) { + $mimemap{$ext} = $mime; + } } } close(MIME); -- cgit v1.2.3 From 498934a7e5410b815a9089c1b5205271d73e0f11 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 31 Jul 2006 19:36:50 -0700 Subject: gitweb: when showing history of a tree, show tree link not blob Otherwise clicking on that erroneous blob link would produce nonsense. Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index f65b5d5ce2..3cd4ce2a01 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -2303,7 +2303,7 @@ sub git_history { "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=$commit;f=$file_name")}, "blob"); + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$ftype;hb=$commit;f=$file_name")}, $ftype); my $blob = git_get_hash_by_path($hash_base, $file_name); my $blob_parent = git_get_hash_by_path($commit, $file_name); if (defined $blob && defined $blob_parent && $blob ne $blob_parent) { -- cgit v1.2.3 From 5d1acf4da0d22d73c6b5d70887003d8fa7642ecc Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 31 Jul 2006 21:00:16 -0700 Subject: gitweb: avoid undefined value warning in print_page_path The function unconditionally tried to match $type but some callers did not pass it. Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 3cd4ce2a01..9dc300d450 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -1035,7 +1035,7 @@ sub git_print_page_path { if (!defined $name) { print "
/
\n"; - } elsif ($type =~ "blob") { + } elsif (defined $type && $type eq 'blob') { print "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "
\n"; } else { @@ -1550,7 +1550,7 @@ sub git_blame { " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_header_div('commit', esc_html($co{'title'}), $hash_base); - git_print_page_path($file_name); + git_print_page_path($file_name, 'blob'); print "
\n"; print < @@ -1773,7 +1773,7 @@ sub git_tree { if (defined $file_name) { $base = esc_html("$file_name/"); } - git_print_page_path($file_name); + git_print_page_path($file_name, 'tree'); print "
\n"; print "\n"; my $alternate = 0; -- cgit v1.2.3 From 6326b60cfd9c953e64ddab9659587afeedccd95b Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 1 Aug 2006 02:59:12 +0200 Subject: gitweb: Refactoring git_project_list Slightly reworking git_project_list, including moving setting $order, as it is used only in this action. Mostly reindent. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.cgi | 57 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 9dc300d450..9448b72798 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -81,14 +81,6 @@ if (defined $action) { } } -our $order = $cgi->param('o'); -if (defined $order) { - if ($order =~ m/[^0-9a-zA-Z_]/) { - undef $order; - die_error(undef, "Invalid order parameter."); - } -} - our $project = ($cgi->param('p') || $ENV{'PATH_INFO'}); if (defined $project) { $project =~ s|^/||; $project =~ s|/$||; @@ -1297,10 +1289,15 @@ sub git_logo { } sub git_project_list { + my $order = $cgi->param('o'); + if (defined $order && $order !~ m/project|descr|owner|age/) { + die_error(undef, "Invalid order parameter '$order'."); + } + my @list = git_read_projects(); my @projects; if (!@list) { - die_error(undef, "No project found."); + die_error(undef, "No projects found."); } foreach my $pr (@list) { my $head = git_read_head($pr->{'path'}); @@ -1322,6 +1319,7 @@ sub git_project_list { } push @projects, $pr; } + git_header_html(); if (-f $home_text) { print "
\n"; @@ -1332,29 +1330,42 @@ sub git_project_list { } print "
\n" . "\n"; - if (!defined($order) || (defined($order) && ($order eq "project"))) { + $order ||= "project"; + if ($order eq "project") { @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; print "\n"; } else { - print "\n"; + print "\n"; } - if (defined($order) && ($order eq "descr")) { + if ($order eq "descr") { @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; print "\n"; } else { - print "\n"; + print "\n"; } - if (defined($order) && ($order eq "owner")) { + if ($order eq "owner") { @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; print "\n"; } else { - print "\n"; + print "\n"; } - if (defined($order) && ($order eq "age")) { + if ($order eq "age") { @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects; print "\n"; } else { - print "\n"; + print "\n"; } print "\n" . "\n"; @@ -1366,14 +1377,16 @@ sub git_project_list { print "\n"; } $alternate ^= 1; - print "\n" . + print "\n" . "\n" . "\n"; - print "\n" . + print "\n" . "\n" . "\n"; } -- cgit v1.2.3 From 5d043a3d856bd40d8b34b8836a561e438d23573b Mon Sep 17 00:00:00 2001 From: Martin Waitz Date: Tue, 1 Aug 2006 21:34:08 +0200 Subject: gitweb: fill in gitweb configuration by Makefile Generate gitweb/gitweb.cgi to reduce the need to patch gitweb.cgi by the end user. The GIT installation directory is already known by the Makefile, and can be inserted directly into gitweb. All other gitweb configuration parameters can now be specified by providing GITWEB_* variables while building GIT. These are described in gitweb/README. Signed-off-by: Martin Waitz Signed-off-by: Junio C Hamano --- Makefile | 25 +- gitweb/README | 17 + gitweb/gitweb.cgi | 2598 ---------------------------------------------------- gitweb/gitweb.perl | 2597 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 2637 insertions(+), 2600 deletions(-) delete mode 100755 gitweb/gitweb.cgi create mode 100755 gitweb/gitweb.perl diff --git a/Makefile b/Makefile index 15864e23bd..fb2b28868a 100644 --- a/Makefile +++ b/Makefile @@ -116,6 +116,13 @@ template_dir = $(prefix)/share/git-core/templates/ GIT_PYTHON_DIR = $(prefix)/share/git-core/python # DESTDIR= +# default configuration for gitweb +GITWEB_SITENAME = +GITWEB_PROJECTROOT = /pub/git +GITWEB_LIST = +GITWEB_HOMETEXT = indextext.html +GITWEB_CSS = gitweb.css + export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR CC = gcc @@ -514,7 +521,7 @@ LIB_OBJS += $(COMPAT_OBJS) export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir ### Build rules -all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk +all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi all: $(MAKE) -C templates @@ -572,6 +579,20 @@ git-status: git-commit cp $< $@+ mv $@+ $@ +gitweb/gitweb.cgi: gitweb/gitweb.perl + rm -f $@ $@+ + sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ + -e 's|@@GIT_VERSION@@|$(GIT_VERSION)|g' \ + -e 's|@@GIT_BINDIR@@|$(bindir)|g' \ + -e 's|@@GITWEB_SITENAME@@|$(GITWEB_SITENAME)|g' \ + -e 's|@@GITWEB_PROJECTROOT@@|$(GITWEB_PROJECTROOT)|g' \ + -e 's|@@GITWEB_LIST@@|$(GITWEB_LIST)|g' \ + -e 's|@@GITWEB_HOMETEXT@@|$(GITWEB_HOMETEXT)|g' \ + -e 's|@@GITWEB_CSS@@|$(GITWEB_CSS)|g' \ + $< >$@+ + chmod +x $@+ + mv $@+ $@ + git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css rm -f $@ $@+ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ @@ -582,7 +603,7 @@ 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' \ - $@.sh | sed "s|/usr/bin/git|$(bindir)/git|" > $@+ + $@.sh > $@+ chmod +x $@+ mv $@+ $@ diff --git a/gitweb/README b/gitweb/README index 8d672762ea..ed939e2fb5 100644 --- a/gitweb/README +++ b/gitweb/README @@ -5,5 +5,22 @@ The one working on: From the git version 1.4.0 gitweb is bundled with git. + +How to configure gitweb for your local system: + +You can specify the following configuration variables when building GIT: + * GITWEB_SITENAME + Shown in the title of all generated pages, defaults to the servers name. + * GITWEB_PROJECTROOT + The root directory for all projects shown by gitweb. + * GITWEB_LIST + points to a directory to scan for projects (defaults to project root) + or to a file for explicit listing of projects. + * GITWEB_HOMETEXT + points to an .html file which is included on the gitweb project + overview page. + * GITWEB_CSS + Points to the location where you put gitweb.css on your web server. + Any comment/question/concern to: Kay Sievers diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi deleted file mode 100755 index 9448b72798..0000000000 --- a/gitweb/gitweb.cgi +++ /dev/null @@ -1,2598 +0,0 @@ -#!/usr/bin/perl - -# gitweb - simple web interface to track changes in git repositories -# -# (C) 2005-2006, Kay Sievers -# (C) 2005, Christian Gierke -# -# This program is licensed under the GPLv2 - -use strict; -use warnings; -use CGI qw(:standard :escapeHTML -nosticky); -use CGI::Util qw(unescape); -use CGI::Carp qw(fatalsToBrowser); -use Encode; -use Fcntl ':mode'; -use File::Find qw(); -binmode STDOUT, ':utf8'; - -our $cgi = new CGI; -our $version = "267"; -our $my_url = $cgi->url(); -our $my_uri = $cgi->url(-absolute => 1); -our $rss_link = ""; - -# core git executable to use -# this can just be "git" if your webserver has a sensible PATH -our $GIT = "/usr/bin/git"; - -# absolute fs-path which will be prepended to the project path -#our $projectroot = "/pub/scm"; -our $projectroot = "/home/kay/public_html/pub/scm"; - -# version of the core git binary -our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; - -# location for temporary files needed for diffs -our $git_temp = "/tmp/gitweb"; -if (! -d $git_temp) { - mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp"); -} - -# target of the home link on top of all pages -our $home_link = $my_uri; - -# name of your site or organization to appear in page titles -# replace this with something more descriptive for clearer bookmarks -our $site_name = $ENV{'SERVER_NAME'} || "Untitled"; - -# html text to include at home page -our $home_text = "indextext.html"; - -# URI of default stylesheet -our $stylesheet = "gitweb.css"; - -# source of projects list -#our $projects_list = $projectroot; -our $projects_list = "index/index.aux"; - -# default blob_plain mimetype and default charset for text/plain blob -our $default_blob_plain_mimetype = 'text/plain'; -our $default_text_plain_charset = undef; - -# file to use for guessing MIME types before trying /etc/mime.types -# (relative to the current git repository) -our $mimetypes_file = undef; - -# input validation and dispatch -our $action = $cgi->param('a'); -if (defined $action) { - if ($action =~ m/[^0-9a-zA-Z\.\-_]/) { - undef $action; - die_error(undef, "Invalid action parameter."); - } - if ($action eq "git-logo.png") { - git_logo(); - exit; - } elsif ($action eq "opml") { - git_opml(); - exit; - } -} - -our $project = ($cgi->param('p') || $ENV{'PATH_INFO'}); -if (defined $project) { - $project =~ s|^/||; $project =~ s|/$||; - $project = validate_input($project); - if (!defined($project)) { - die_error(undef, "Invalid project parameter."); - } - if (!(-d "$projectroot/$project")) { - undef $project; - die_error(undef, "No such directory."); - } - if (!(-e "$projectroot/$project/HEAD")) { - undef $project; - die_error(undef, "No such project."); - } - $rss_link = ""; - $ENV{'GIT_DIR'} = "$projectroot/$project"; -} else { - git_project_list(); - exit; -} - -our $file_name = $cgi->param('f'); -if (defined $file_name) { - $file_name = validate_input($file_name); - if (!defined($file_name)) { - die_error(undef, "Invalid file parameter."); - } -} - -our $hash = $cgi->param('h'); -if (defined $hash) { - $hash = validate_input($hash); - if (!defined($hash)) { - die_error(undef, "Invalid hash parameter."); - } -} - -our $hash_parent = $cgi->param('hp'); -if (defined $hash_parent) { - $hash_parent = validate_input($hash_parent); - if (!defined($hash_parent)) { - die_error(undef, "Invalid hash parent parameter."); - } -} - -our $hash_base = $cgi->param('hb'); -if (defined $hash_base) { - $hash_base = validate_input($hash_base); - if (!defined($hash_base)) { - die_error(undef, "Invalid hash base parameter."); - } -} - -our $page = $cgi->param('pg'); -if (defined $page) { - if ($page =~ m/[^0-9]$/) { - undef $page; - die_error(undef, "Invalid page parameter."); - } -} - -our $searchtext = $cgi->param('s'); -if (defined $searchtext) { - if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) { - undef $searchtext; - die_error(undef, "Invalid search parameter."); - } - $searchtext = quotemeta $searchtext; -} - -# dispatch -my %actions = ( - "blame" => \&git_blame2, - "blobdiff" => \&git_blobdiff, - "blobdiff_plain" => \&git_blobdiff_plain, - "blob" => \&git_blob, - "blob_plain" => \&git_blob_plain, - "commitdiff" => \&git_commitdiff, - "commitdiff_plain" => \&git_commitdiff_plain, - "commit" => \&git_commit, - "heads" => \&git_heads, - "history" => \&git_history, - "log" => \&git_log, - "rss" => \&git_rss, - "search" => \&git_search, - "shortlog" => \&git_shortlog, - "summary" => \&git_summary, - "tag" => \&git_tag, - "tags" => \&git_tags, - "tree" => \&git_tree, -); - -$action = 'summary' if (!defined($action)); -if (!defined($actions{$action})) { - undef $action; - die_error(undef, "Unknown action."); -} -$actions{$action}->(); -exit; - -## ====================================================================== -## validation, quoting/unquoting and escaping - -sub validate_input { - my $input = shift; - - if ($input =~ m/^[0-9a-fA-F]{40}$/) { - return $input; - } - if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) { - return undef; - } - if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) { - return undef; - } - return $input; -} - -# quote unsafe chars, but keep the slash, even when it's not -# 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/ /\+/g; - return $str; -} - -# replace invalid utf8 character with SUBSTITUTION sequence -sub esc_html { - my $str = shift; - $str = decode("utf8", $str, Encode::FB_DEFAULT); - $str = escapeHTML($str); - $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) - return $str; -} - -# git may return quoted and escaped filenames -sub unquote { - my $str = shift; - if ($str =~ m/^"(.*)"$/) { - $str = $1; - $str =~ s/\\([0-7]{1,3})/chr(oct($1))/eg; - } - return $str; -} - -## ---------------------------------------------------------------------- -## HTML aware string manipulation - -sub chop_str { - my $str = shift; - my $len = shift; - my $add_len = shift || 10; - - # allow only $len chars, but don't cut a word if it would fit in $add_len - # if it doesn't fit, cut it if it's still longer than the dots we would add - $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; - my $body = $1; - my $tail = $2; - if (length($tail) > 4) { - $tail = " ..."; - $body =~ s/&[^;]*$//; # remove chopped character entities - } - return "$body$tail"; -} - -## ---------------------------------------------------------------------- -## functions returning short strings - -# CSS class for given age value (in seconds) -sub age_class { - my $age = shift; - - if ($age < 60*60*2) { - return "age0"; - } elsif ($age < 60*60*24*2) { - return "age1"; - } else { - return "age2"; - } -} - -# convert age in seconds to "nn units ago" string -sub age_string { - my $age = shift; - my $age_str; - - if ($age > 60*60*24*365*2) { - $age_str = (int $age/60/60/24/365); - $age_str .= " years ago"; - } elsif ($age > 60*60*24*(365/12)*2) { - $age_str = int $age/60/60/24/(365/12); - $age_str .= " months ago"; - } elsif ($age > 60*60*24*7*2) { - $age_str = int $age/60/60/24/7; - $age_str .= " weeks ago"; - } elsif ($age > 60*60*24*2) { - $age_str = int $age/60/60/24; - $age_str .= " days ago"; - } elsif ($age > 60*60*2) { - $age_str = int $age/60/60; - $age_str .= " hours ago"; - } elsif ($age > 60*2) { - $age_str = int $age/60; - $age_str .= " min ago"; - } elsif ($age > 2) { - $age_str = int $age; - $age_str .= " sec ago"; - } else { - $age_str .= " right now"; - } - return $age_str; -} - -# convert file mode in octal to symbolic file mode string -sub mode_str { - my $mode = oct shift; - - if (S_ISDIR($mode & S_IFMT)) { - return 'drwxr-xr-x'; - } elsif (S_ISLNK($mode)) { - return 'lrwxrwxrwx'; - } elsif (S_ISREG($mode)) { - # git cares only about the executable bit - if ($mode & S_IXUSR) { - return '-rwxr-xr-x'; - } else { - return '-rw-r--r--'; - }; - } else { - return '----------'; - } -} - -# convert file mode in octal to file type string -sub file_type { - my $mode = oct shift; - - if (S_ISDIR($mode & S_IFMT)) { - return "directory"; - } elsif (S_ISLNK($mode)) { - return "symlink"; - } elsif (S_ISREG($mode)) { - return "file"; - } else { - return "unknown"; - } -} - -## ---------------------------------------------------------------------- -## functions returning short HTML fragments, or transforming HTML fragments -## which don't beling to other sections - -# format line of commit message or tag comment -sub format_log_line_html { - my $line = shift; - - $line = esc_html($line); - $line =~ s/ / /g; - if ($line =~ m/([0-9a-fA-F]{40})/) { - my $hash_text = $1; - if (git_get_type($hash_text) eq "commit") { - my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text); - $line =~ s/$hash_text/$link/; - } - } - return $line; -} - -# format marker of refs pointing to given object -sub git_get_referencing { - my ($refs, $id) = @_; - - if (defined $refs->{$id}) { - return ' ' . esc_html($refs->{$id}) . ''; - } else { - return ""; - } -} - -## ---------------------------------------------------------------------- -## git utility subroutines, invoking git commands - -# get HEAD ref of given project as hash -sub git_read_head { - my $project = shift; - my $oENV = $ENV{'GIT_DIR'}; - my $retval = undef; - $ENV{'GIT_DIR'} = "$projectroot/$project"; - if (open my $fd, "-|", $GIT, "rev-parse", "--verify", "HEAD") { - my $head = <$fd>; - close $fd; - if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) { - $retval = $1; - } - } - if (defined $oENV) { - $ENV{'GIT_DIR'} = $oENV; - } - return $retval; -} - -# get type of given object -sub git_get_type { - my $hash = shift; - - open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return; - my $type = <$fd>; - close $fd or return; - chomp $type; - return $type; -} - -sub git_get_project_config { - my $key = shift; - - return unless ($key); - $key =~ s/^gitweb\.//; - return if ($key =~ m/\W/); - - my $val = qx($GIT repo-config --get gitweb.$key); - return ($val); -} - -sub git_get_project_config_bool { - my $val = git_get_project_config (@_); - if ($val and $val =~ m/true|yes|on/) { - return (1); - } - return; # implicit false -} - -# get hash of given path at given ref -sub git_get_hash_by_path { - my $base = shift; - my $path = shift || return undef; - - my $tree = $base; - - open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path - or die_error(undef, "Open git-ls-tree failed."); - my $line = <$fd>; - close $fd or return undef; - - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; - return $3; -} - -## ...................................................................... -## git utility functions, directly accessing git repository - -# assumes that PATH is not symref -sub git_read_hash { - my $path = shift; - - open my $fd, "$projectroot/$path" or return undef; - my $head = <$fd>; - close $fd; - chomp $head; - if ($head =~ m/^[0-9a-fA-F]{40}$/) { - return $head; - } -} - -sub git_read_description { - my $path = shift; - - open my $fd, "$projectroot/$path/description" or return undef; - my $descr = <$fd>; - close $fd; - chomp $descr; - return $descr; -} - -sub git_read_projects { - my @list; - - if (-d $projects_list) { - # search in directory - my $dir = $projects_list; - opendir my ($dh), $dir or return undef; - while (my $dir = readdir($dh)) { - if (-e "$projectroot/$dir/HEAD") { - my $pr = { - path => $dir, - }; - push @list, $pr - } - } - closedir($dh); - } elsif (-f $projects_list) { - # read from file(url-encoded): - # 'git%2Fgit.git Linus+Torvalds' - # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' - # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' - open my ($fd), $projects_list or return undef; - while (my $line = <$fd>) { - chomp $line; - my ($path, $owner) = split ' ', $line; - $path = unescape($path); - $owner = unescape($owner); - if (!defined $path) { - next; - } - if (-e "$projectroot/$path/HEAD") { - my $pr = { - path => $path, - owner => decode("utf8", $owner, Encode::FB_DEFAULT), - }; - push @list, $pr - } - } - close $fd; - } - @list = sort {$a->{'path'} cmp $b->{'path'}} @list; - return @list; -} - -sub read_info_ref { - my $type = shift || ""; - my %refs; - # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 - # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} - open my $fd, "$projectroot/$project/info/refs" or return; - while (my $line = <$fd>) { - chomp $line; - # attention: for $type == "" it saves only last path part of ref name - # e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb' - if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) { - if (defined $refs{$1}) { - $refs{$1} .= " / $2"; - } else { - $refs{$1} = $2; - } - } - } - close $fd or return; - return \%refs; -} - -## ---------------------------------------------------------------------- -## parse to hash functions - -sub date_str { - my $epoch = shift; - my $tz = shift || "-0000"; - - my %date; - my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); - my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); - my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); - $date{'hour'} = $hour; - $date{'minute'} = $min; - $date{'mday'} = $mday; - $date{'day'} = $days[$wday]; - $date{'month'} = $months[$mon]; - $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; - $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; - - $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; - my $local = $epoch + ((int $1 + ($2/60)) * 3600); - ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); - $date{'hour_local'} = $hour; - $date{'minute_local'} = $min; - $date{'tz_local'} = $tz; - return %date; -} - -sub git_read_tag { - my $tag_id = shift; - my %tag; - my @comment; - - open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return; - $tag{'id'} = $tag_id; - while (my $line = <$fd>) { - chomp $line; - if ($line =~ m/^object ([0-9a-fA-F]{40})$/) { - $tag{'object'} = $1; - } elsif ($line =~ m/^type (.+)$/) { - $tag{'type'} = $1; - } elsif ($line =~ m/^tag (.+)$/) { - $tag{'name'} = $1; - } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) { - $tag{'author'} = $1; - $tag{'epoch'} = $2; - $tag{'tz'} = $3; - } elsif ($line =~ m/--BEGIN/) { - push @comment, $line; - last; - } elsif ($line eq "") { - last; - } - } - push @comment, <$fd>; - $tag{'comment'} = \@comment; - close $fd or return; - if (!defined $tag{'name'}) { - return - }; - return %tag -} - -sub git_read_commit { - my $commit_id = shift; - my $commit_text = shift; - - my @commit_lines; - my %co; - - if (defined $commit_text) { - @commit_lines = @$commit_text; - } else { - $/ = "\0"; - open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return; - @commit_lines = split '\n', <$fd>; - close $fd or return; - $/ = "\n"; - pop @commit_lines; - } - my $header = shift @commit_lines; - if (!($header =~ m/^[0-9a-fA-F]{40}/)) { - return; - } - ($co{'id'}, my @parents) = split ' ', $header; - $co{'parents'} = \@parents; - $co{'parent'} = $parents[0]; - while (my $line = shift @commit_lines) { - last if $line eq "\n"; - if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) { - $co{'tree'} = $1; - } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) { - $co{'author'} = $1; - $co{'author_epoch'} = $2; - $co{'author_tz'} = $3; - if ($co{'author'} =~ m/^([^<]+) 50) { - $title =~ s/^Automatic //; - $title =~ s/^merge (of|with) /Merge ... /i; - if (length($title) > 50) { - $title =~ s/(http|rsync):\/\///; - } - if (length($title) > 50) { - $title =~ s/(master|www|rsync)\.//; - } - if (length($title) > 50) { - $title =~ s/kernel.org:?//; - } - if (length($title) > 50) { - $title =~ s/\/pub\/scm//; - } - } - $co{'title_short'} = chop_str($title, 50, 5); - last; - } - } - # remove added spaces - foreach my $line (@commit_lines) { - $line =~ s/^ //; - } - $co{'comment'} = \@commit_lines; - - my $age = time - $co{'committer_epoch'}; - $co{'age'} = $age; - $co{'age_string'} = age_string($age); - my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'}); - if ($age > 60*60*24*7*2) { - $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; - $co{'age_string_age'} = $co{'age_string'}; - } else { - $co{'age_string_date'} = $co{'age_string'}; - $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; - } - return %co; -} - -## ...................................................................... -## parse to array of hashes functions - -sub git_read_refs { - my $ref_dir = shift; - my @reflist; - - my @refs; - my $pfxlen = length("$projectroot/$project/$ref_dir"); - File::Find::find(sub { - return if (/^\./); - if (-f $_) { - push @refs, substr($File::Find::name, $pfxlen + 1); - } - }, "$projectroot/$project/$ref_dir"); - - foreach my $ref_file (@refs) { - my $ref_id = git_read_hash("$project/$ref_dir/$ref_file"); - my $type = git_get_type($ref_id) || next; - my %ref_item; - my %co; - $ref_item{'type'} = $type; - $ref_item{'id'} = $ref_id; - $ref_item{'epoch'} = 0; - $ref_item{'age'} = "unknown"; - if ($type eq "tag") { - my %tag = git_read_tag($ref_id); - $ref_item{'comment'} = $tag{'comment'}; - if ($tag{'type'} eq "commit") { - %co = git_read_commit($tag{'object'}); - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } elsif (defined($tag{'epoch'})) { - my $age = time - $tag{'epoch'}; - $ref_item{'epoch'} = $tag{'epoch'}; - $ref_item{'age'} = age_string($age); - } - $ref_item{'reftype'} = $tag{'type'}; - $ref_item{'name'} = $tag{'name'}; - $ref_item{'refid'} = $tag{'object'}; - } elsif ($type eq "commit"){ - %co = git_read_commit($ref_id); - $ref_item{'reftype'} = "commit"; - $ref_item{'name'} = $ref_file; - $ref_item{'title'} = $co{'title'}; - $ref_item{'refid'} = $ref_id; - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } else { - $ref_item{'reftype'} = $type; - $ref_item{'name'} = $ref_file; - $ref_item{'refid'} = $ref_id; - } - - push @reflist, \%ref_item; - } - # sort tags by age - @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; - return \@reflist; -} - -## ---------------------------------------------------------------------- -## filesystem-related functions - -sub get_file_owner { - my $path = shift; - - my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path); - my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid); - if (!defined $gcos) { - return undef; - } - my $owner = $gcos; - $owner =~ s/[,;].*$//; - return decode("utf8", $owner, Encode::FB_DEFAULT); -} - -## ...................................................................... -## mimetype related functions - -sub mimetype_guess_file { - my $filename = shift; - my $mimemap = shift; - -r $mimemap or return undef; - - my %mimemap; - open(MIME, $mimemap) or return undef; - while () { - my ($mime, $exts) = split(/\t+/); - if (defined $exts) { - my @exts = split(/\s+/, $exts); - foreach my $ext (@exts) { - $mimemap{$ext} = $mime; - } - } - } - close(MIME); - - $filename =~ /\.(.*?)$/; - return $mimemap{$1}; -} - -sub mimetype_guess { - my $filename = shift; - my $mime; - $filename =~ /\./ or return undef; - - if ($mimetypes_file) { - my $file = $mimetypes_file; - #$file =~ m#^/# or $file = "$projectroot/$path/$file"; - $mime = mimetype_guess_file($filename, $file); - } - $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); - return $mime; -} - -sub git_blob_plain_mimetype { - my $fd = shift; - my $filename = shift; - - if ($filename) { - my $mime = mimetype_guess($filename); - $mime and return $mime; - } - - # just in case - return $default_blob_plain_mimetype unless $fd; - - if (-T $fd) { - return 'text/plain' . - ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); - } elsif (! $filename) { - return 'application/octet-stream'; - } elsif ($filename =~ m/\.png$/i) { - return 'image/png'; - } elsif ($filename =~ m/\.gif$/i) { - return 'image/gif'; - } elsif ($filename =~ m/\.jpe?g$/i) { - return 'image/jpeg'; - } else { - return 'application/octet-stream'; - } -} - -## ====================================================================== -## functions printing HTML: header, footer, error page - -sub git_header_html { - my $status = shift || "200 OK"; - my $expires = shift; - - my $title = "$site_name git"; - if (defined $project) { - $title .= " - $project"; - if (defined $action) { - $title .= "/$action"; - if (defined $file_name) { - $title .= " - $file_name"; - if ($action eq "tree" && $file_name !~ m|/$|) { - $title .= "/"; - } - } - } - } - my $content_type; - # require explicit support from the UA if we are to send the page as - # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. - # we have to do this because MSIE sometimes globs '*/*', pretending to - # support xhtml+xml but choking when it gets what it asked for. - if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) { - $content_type = 'application/xhtml+xml'; - } else { - $content_type = 'text/html'; - } - print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); - print < - - - - - - - -$title - -$rss_link - - -EOF - print "
\n" . - "" . - "\"git\"" . - "\n"; - print $cgi->a({-href => esc_param($home_link)}, "projects") . " / "; - if (defined $project) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project)); - if (defined $action) { - print " / $action"; - } - print "\n"; - if (!defined $searchtext) { - $searchtext = ""; - } - my $search_hash; - if (defined $hash_base) { - $search_hash = $hash_base; - } elsif (defined $hash) { - $search_hash = $hash; - } else { - $search_hash = "HEAD"; - } - $cgi->param("a", "search"); - $cgi->param("h", $search_hash); - print $cgi->startform(-method => "get", -action => $my_uri) . - "
\n" . - $cgi->hidden(-name => "p") . "\n" . - $cgi->hidden(-name => "a") . "\n" . - $cgi->hidden(-name => "h") . "\n" . - $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . - "
" . - $cgi->end_form() . "\n"; - } - print "
\n"; -} - -sub git_footer_html { - print "
\n"; - if (defined $project) { - my $descr = git_read_description($project); - if (defined $descr) { - print "\n"; - } - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n"; - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n"; - } - print "
\n" . - "\n" . - ""; -} - -sub die_error { - my $status = shift || "403 Forbidden"; - my $error = shift || "Malformed query, file missing or permission denied"; - - git_header_html($status); - print "
\n" . - "

\n" . - "$status - $error\n" . - "
\n" . - "
\n"; - git_footer_html(); - exit; -} - -## ---------------------------------------------------------------------- -## functions printing or outputting HTML: navigation - -sub git_page_nav { - my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_; - $extra = '' if !defined $extra; # pager or formats - - my @navs = qw(summary shortlog log commit commitdiff tree); - if ($suppress) { - @navs = grep { $_ ne $suppress } @navs; - } - - my %arg = map { $_, ''} @navs; - if (defined $head) { - for (qw(commit commitdiff)) { - $arg{$_} = ";h=$head"; - } - if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) { - for (qw(shortlog log)) { - $arg{$_} = ";h=$head"; - } - } - } - $arg{tree} .= ";h=$treehead" if defined $treehead; - $arg{tree} .= ";hb=$treebase" if defined $treebase; - - print "
\n" . - (join " | ", - map { $_ eq $current - ? $_ - : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_") - } - @navs); - print "
\n$extra
\n" . - "
\n"; -} - -sub git_get_paging_nav { - my ($action, $hash, $head, $page, $nrevs) = @_; - my $paging_nav; - - - if ($hash ne $head || $page) { - $paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD"); - } else { - $paging_nav .= "HEAD"; - } - - if ($page > 0) { - $paging_nav .= " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)), - -accesskey => "p", -title => "Alt-p"}, "prev"); - } else { - $paging_nav .= " ⋅ prev"; - } - - if ($nrevs >= (100 * ($page+1)-1)) { - $paging_nav .= " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)), - -accesskey => "n", -title => "Alt-n"}, "next"); - } else { - $paging_nav .= " ⋅ next"; - } - - return $paging_nav; -} - -## ...................................................................... -## functions printing or outputting HTML: div - -sub git_header_div { - my ($action, $title, $hash, $hash_base) = @_; - my $rest = ''; - - $rest .= ";h=$hash" if $hash; - $rest .= ";hb=$hash_base" if $hash_base; - - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"), - -class => "title"}, $title ? $title : $action) . "\n" . - "
\n"; -} - -sub git_print_page_path { - my $name = shift; - my $type = shift; - - if (!defined $name) { - print "
/
\n"; - } elsif (defined $type && $type eq 'blob') { - print "
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "
\n"; - } else { - print "
" . esc_html($name) . "
\n"; - } -} - -## ...................................................................... -## functions printing large fragments of HTML - -sub git_shortlog_body { - # uses global variable $project - my ($revlist, $from, $to, $refs, $extra) = @_; - $from = 0 unless defined $from; - $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); - - print "
Project" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "" . + $cgi->a({-href => "$my_uri?" . esc_param("o=project"), + -class => "header"}, "Project") . + "Description" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "" . + $cgi->a({-href => "$my_uri?" . esc_param("o=descr"), + -class => "header"}, "Description") . + "Owner" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "" . + $cgi->a({-href => "$my_uri?" . esc_param("o=owner"), + -class => "header"}, "Owner") . + "Last Change" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "" . + $cgi->a({-href => "$my_uri?" . esc_param("o=age"), + -class => "header"}, "Last Change") . + "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), + -class => "list"}, esc_html($pr->{'path'})) . "" . esc_html($pr->{'descr'}) . "" . chop_str($pr->{'owner'}, 15) . "{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "{'commit'}{'age'}) . "\">" . + $pr->{'commit'}{'age_string'} . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . + $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . "
\n"; - my $alternate = 0; - for (my $i = $from; $i <= $to; $i++) { - my $commit = $revlist->[$i]; - #my $ref = defined $refs ? git_get_referencing($refs, $commit) : ''; - my $ref = git_get_referencing($refs, $commit); - my %co = git_read_commit($commit); - my %ad = date_str($co{'author_epoch'}); - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - # git_summary() used print "\n" . - print "\n" . - "\n" . - "\n" . - "\n" . - "\n"; - } - if (defined $extra) { - print "\n" . - "\n" . - "\n"; - } - print "
$co{'age_string'}$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 10)) . ""; - if (length($co{'title_short'}) < length($co{'title'})) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), - -class => "list", -title => "$co{'title'}"}, - "" . esc_html($co{'title_short'}) . "$ref"); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), - -class => "list"}, - "" . esc_html($co{'title'}) . "$ref"); - } - print "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - "
$extra
\n"; -} - -sub git_tags_body { - # uses global variable $project - my ($taglist, $from, $to, $extra) = @_; - $from = 0 unless defined $from; - $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); - - print "\n"; - my $alternate = 0; - for (my $i = $from; $i <= $to; $i++) { - my $entry = $taglist->[$i]; - my %tag = %$entry; - my $comment_lines = $tag{'comment'}; - my $comment = shift @$comment_lines; - my $comment_short; - if (defined $comment) { - $comment_short = chop_str($comment, 30, 5); - } - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n" . - "\n" . - "\n" . - "\n" . - "\n" . - ""; - } - if (defined $extra) { - print "\n" . - "\n" . - "\n"; - } - print "
$tag{'age'}" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), - -class => "list"}, "" . esc_html($tag{'name'}) . "") . - ""; - if (defined $comment) { - if (length($comment_short) < length($comment)) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), - -class => "list", -title => $comment}, $comment_short); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), - -class => "list"}, $comment); - } - } - print ""; - if ($tag{'type'} eq "tag") { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag"); - } else { - print " "; - } - print "" . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); - if ($tag{'reftype'} eq "commit") { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); - } elsif ($tag{'reftype'} eq "blob") { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$tag{'refid'}")}, "raw"); - } - print "
$extra
\n"; -} - -sub git_heads_body { - # uses global variable $project - my ($taglist, $head, $from, $to, $extra) = @_; - $from = 0 unless defined $from; - $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); - - print "\n"; - my $alternate = 0; - for (my $i = $from; $i <= $to; $i++) { - my $entry = $taglist->[$i]; - my %tag = %$entry; - my $curr = $tag{'id'} eq $head; - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n" . - ($tag{'id'} eq $head ? "\n" . - "\n" . - ""; - } - if (defined $extra) { - print "\n" . - "\n" . - "\n"; - } - print "
$tag{'age'}" : "") . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), - -class => "list"}, "" . esc_html($tag{'name'}) . "") . - "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") . - "
$extra
\n"; -} - -## ---------------------------------------------------------------------- -## functions printing large fragments, format as one of arguments - -sub git_diff_print { - my $from = shift; - my $from_name = shift; - my $to = shift; - my $to_name = shift; - my $format = shift || "html"; - - my $from_tmp = "/dev/null"; - my $to_tmp = "/dev/null"; - my $pid = $$; - - # create tmp from-file - if (defined $from) { - $from_tmp = "$git_temp/gitweb_" . $$ . "_from"; - open my $fd2, "> $from_tmp"; - open my $fd, "-|", $GIT, "cat-file", "blob", $from; - my @file = <$fd>; - print $fd2 @file; - close $fd2; - close $fd; - } - - # create tmp to-file - if (defined $to) { - $to_tmp = "$git_temp/gitweb_" . $$ . "_to"; - open my $fd2, "> $to_tmp"; - open my $fd, "-|", $GIT, "cat-file", "blob", $to; - my @file = <$fd>; - print $fd2 @file; - close $fd2; - close $fd; - } - - open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp"; - if ($format eq "plain") { - undef $/; - print <$fd>; - $/ = "\n"; - } else { - while (my $line = <$fd>) { - chomp $line; - my $char = substr($line, 0, 1); - my $diff_class = ""; - if ($char eq '+') { - $diff_class = " add"; - } elsif ($char eq "-") { - $diff_class = " rem"; - } elsif ($char eq "@") { - $diff_class = " chunk_header"; - } elsif ($char eq "\\") { - # skip errors - next; - } - while ((my $pos = index($line, "\t")) != -1) { - if (my $count = (8 - (($pos-1) % 8))) { - my $spaces = ' ' x $count; - $line =~ s/\t/$spaces/; - } - } - print "
" . esc_html($line) . "
\n"; - } - } - close $fd; - - if (defined $from) { - unlink($from_tmp); - } - if (defined $to) { - unlink($to_tmp); - } -} - - -## ====================================================================== -## ====================================================================== -## actions - -# git-logo (cached in browser for one day) -sub git_logo { - binmode STDOUT, ':raw'; - print $cgi->header(-type => 'image/png', -expires => '+1d'); - # cat git-logo.png | hexdump -e '16/1 " %02x" "\n"' | sed 's/ /\\x/g' - print "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" . - "\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" . - "\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" . - "\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" . - "\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" . - "\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" . - "\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" . - "\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" . - "\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" . - "\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" . - "\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" . - "\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" . - "\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; -} - -sub git_project_list { - my $order = $cgi->param('o'); - if (defined $order && $order !~ m/project|descr|owner|age/) { - die_error(undef, "Invalid order parameter '$order'."); - } - - my @list = git_read_projects(); - my @projects; - if (!@list) { - die_error(undef, "No projects found."); - } - foreach my $pr (@list) { - my $head = git_read_head($pr->{'path'}); - if (!defined $head) { - next; - } - $ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}"; - my %co = git_read_commit($head); - if (!%co) { - next; - } - $pr->{'commit'} = \%co; - if (!defined $pr->{'descr'}) { - my $descr = git_read_description($pr->{'path'}) || ""; - $pr->{'descr'} = chop_str($descr, 25, 5); - } - if (!defined $pr->{'owner'}) { - $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || ""; - } - push @projects, $pr; - } - - git_header_html(); - if (-f $home_text) { - print "
\n"; - open (my $fd, $home_text); - print <$fd>; - close $fd; - print "
\n"; - } - print "\n" . - "\n"; - $order ||= "project"; - if ($order eq "project") { - @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; - print "\n"; - } else { - print "\n"; - } - if ($order eq "descr") { - @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; - print "\n"; - } else { - print "\n"; - } - if ($order eq "owner") { - @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; - print "\n"; - } else { - print "\n"; - } - if ($order eq "age") { - @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects; - print "\n"; - } else { - print "\n"; - } - print "\n" . - "\n"; - my $alternate = 0; - foreach my $pr (@projects) { - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n" . - "\n" . - "\n"; - print "\n" . - "\n" . - "\n"; - } - print "
Project" . - $cgi->a({-href => "$my_uri?" . esc_param("o=project"), - -class => "header"}, "Project") . - "Description" . - $cgi->a({-href => "$my_uri?" . esc_param("o=descr"), - -class => "header"}, "Description") . - "Owner" . - $cgi->a({-href => "$my_uri?" . esc_param("o=owner"), - -class => "header"}, "Owner") . - "Last Change" . - $cgi->a({-href => "$my_uri?" . esc_param("o=age"), - -class => "header"}, "Last Change") . - "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), - -class => "list"}, esc_html($pr->{'path'})) . "" . esc_html($pr->{'descr'}) . "" . chop_str($pr->{'owner'}, 15) . "{'commit'}{'age'}) . "\">" . - $pr->{'commit'}{'age_string'} . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . - "
\n"; - git_footer_html(); -} - -sub git_summary { - my $descr = git_read_description($project) || "none"; - my $head = git_read_head($project); - my %co = git_read_commit($head); - my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); - - my $owner; - if (-f $projects_list) { - open (my $fd , $projects_list); - while (my $line = <$fd>) { - chomp $line; - my ($pr, $ow) = split ' ', $line; - $pr = unescape($pr); - $ow = unescape($ow); - if ($pr eq $project) { - $owner = decode("utf8", $ow, Encode::FB_DEFAULT); - last; - } - } - close $fd; - } - if (!defined $owner) { - $owner = get_file_owner("$projectroot/$project"); - } - - my $refs = read_info_ref(); - git_header_html(); - git_page_nav('summary','', $head); - - print "
 
\n"; - print "\n" . - "\n" . - "\n" . - "\n" . - "
description" . esc_html($descr) . "
owner$owner
last change$cd{'rfc2822'}
\n"; - - open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) - or die_error(undef, "Open git-rev-list failed."); - my @revlist = map { chomp; $_ } <$fd>; - close $fd; - git_header_div('shortlog'); - git_shortlog_body(\@revlist, 0, 15, $refs, - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "...")); - - my $taglist = git_read_refs("refs/tags"); - if (defined @$taglist) { - git_header_div('tags'); - git_tags_body($taglist, 0, 15, - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "...")); - } - - my $headlist = git_read_refs("refs/heads"); - if (defined @$headlist) { - git_header_div('heads'); - git_heads_body($headlist, $head, 0, 15, - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "...")); - } - - git_footer_html(); -} - -sub git_tag { - my $head = git_read_head($project); - git_header_html(); - git_page_nav('','', $head,undef,$head); - my %tag = git_read_tag($hash); - git_header_div('commit', esc_html($tag{'name'}), $hash); - print "
\n" . - "\n" . - "\n" . - "\n" . - "\n" . - "\n" . - "\n"; - if (defined($tag{'author'})) { - my %ad = date_str($tag{'epoch'}, $tag{'tz'}); - print "\n"; - print "\n"; - } - print "
object" . $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'object'}) . "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'type'}) . "
author" . esc_html($tag{'author'}) . "
" . $ad{'rfc2822'} . sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) . "
\n\n" . - "
\n"; - print "
"; - my $comment = $tag{'comment'}; - foreach my $line (@$comment) { - print esc_html($line) . "
\n"; - } - print "
\n"; - git_footer_html(); -} - -sub git_blame2 { - my $fd; - my $ftype; - die_error(undef, "Permission denied.") if (!git_get_project_config_bool ('blame')); - die_error('404 Not Found', "File name not defined") if (!$file_name); - $hash_base ||= git_read_head($project); - die_error(undef, "Reading commit failed") unless ($hash_base); - my %co = git_read_commit($hash_base) - or die_error(undef, "Reading commit failed"); - if (!defined $hash) { - $hash = git_get_hash_by_path($hash_base, $file_name, "blob") - or die_error(undef, "Error looking up file"); - } - $ftype = git_get_type($hash); - if ($ftype !~ "blob") { - die_error("400 Bad Request", "object is not a blob"); - } - open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base) - or die_error(undef, "Open git-blame failed."); - git_header_html(); - my $formats_nav = - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); - git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_header_div('commit', esc_html($co{'title'}), $hash_base); - git_print_page_path($file_name, $ftype); - my @rev_color = (qw(light dark)); - my $num_colors = scalar(@rev_color); - my $current_color = 0; - my $last_rev; - print "
\n"; - print "\n"; - print "\n"; - while (<$fd>) { - /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; - my $full_rev = $1; - my $rev = substr($full_rev, 0, 8); - my $lineno = $2; - my $data = $3; - - if (!defined $last_rev) { - $last_rev = $full_rev; - } elsif ($last_rev ne $full_rev) { - $last_rev = $full_rev; - $current_color = ++$current_color % $num_colors; - } - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - } - print "
CommitLineData
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$full_rev;f=$file_name")}, esc_html($rev)) . "" . esc_html($lineno) . "" . esc_html($data) . "
\n"; - print "
"; - close $fd or print "Reading blob failed\n"; - git_footer_html(); -} - -sub git_blame { - my $fd; - die_error('403 Permission denied', "Permission denied.") if (!git_get_project_config_bool ('blame')); - die_error('404 Not Found', "What file will it be, master?") if (!$file_name); - $hash_base ||= git_read_head($project); - die_error(undef, "Reading commit failed.") unless ($hash_base); - my %co = git_read_commit($hash_base) - or die_error(undef, "Reading commit failed."); - if (!defined $hash) { - $hash = git_get_hash_by_path($hash_base, $file_name, "blob") - or die_error(undef, "Error lookup file."); - } - open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base) - or die_error(undef, "Open git-annotate failed."); - git_header_html(); - my $formats_nav = - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); - git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_header_div('commit', esc_html($co{'title'}), $hash_base); - git_print_page_path($file_name, 'blob'); - print "
\n"; - print < - - Commit - Age - Author - Line - Data - -HTML - my @line_class = (qw(light dark)); - my $line_class_len = scalar (@line_class); - my $line_class_num = $#line_class; - while (my $line = <$fd>) { - my $long_rev; - my $short_rev; - my $author; - my $time; - my $lineno; - my $data; - my $age; - my $age_str; - my $age_class; - - chomp $line; - $line_class_num = ($line_class_num + 1) % $line_class_len; - - if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) \+\d\d\d\d\t(\d+)\)(.*)$/) { - $long_rev = $1; - $author = $2; - $time = $3; - $lineno = $4; - $data = $5; - } else { - print qq( Unable to parse: $line\n); - next; - } - $short_rev = substr ($long_rev, 0, 8); - $age = time () - $time; - $age_str = age_string ($age); - $age_str =~ s/ / /g; - $age_class = age_class($age); - $author = esc_html ($author); - $author =~ s/ / /g; - # escape tabs - while ((my $pos = index($data, "\t")) != -1) { - if (my $count = (8 - ($pos % 8))) { - my $spaces = ' ' x $count; - $data =~ s/\t/$spaces/; - } - } - $data = esc_html ($data); - - print < - $short_rev.. - $age_str - $author - $lineno - $data - -HTML - } # while (my $line = <$fd>) - print "\n\n"; - close $fd or print "Reading blob failed.\n"; - print "
"; - git_footer_html(); -} - -sub git_tags { - my $head = git_read_head($project); - git_header_html(); - git_page_nav('','', $head,undef,$head); - git_header_div('summary', $project); - - my $taglist = git_read_refs("refs/tags"); - if (defined @$taglist) { - git_tags_body($taglist); - } - git_footer_html(); -} - -sub git_heads { - my $head = git_read_head($project); - git_header_html(); - git_page_nav('','', $head,undef,$head); - git_header_div('summary', $project); - - my $taglist = git_read_refs("refs/heads"); - my $alternate = 0; - if (defined @$taglist) { - git_heads_body($taglist, $head); - } - git_footer_html(); -} - -sub git_blob_plain { - if (!defined $hash) { - if (defined $file_name) { - my $base = $hash_base || git_read_head($project); - $hash = git_get_hash_by_path($base, $file_name, "blob") - or die_error(undef, "Error lookup file."); - } else { - die_error(undef, "No file name defined."); - } - } - my $type = shift; - open my $fd, "-|", $GIT, "cat-file", "blob", $hash - or die_error("Couldn't cat $file_name, $hash"); - - $type ||= git_blob_plain_mimetype($fd, $file_name); - - # save as filename, even when no $file_name is given - my $save_as = "$hash"; - if (defined $file_name) { - $save_as = $file_name; - } elsif ($type =~ m/^text\//) { - $save_as .= '.txt'; - } - - print $cgi->header(-type => "$type", '-content-disposition' => "inline; filename=\"$save_as\""); - undef $/; - binmode STDOUT, ':raw'; - print <$fd>; - binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi - $/ = "\n"; - close $fd; -} - -sub git_blob { - if (!defined $hash) { - if (defined $file_name) { - my $base = $hash_base || git_read_head($project); - $hash = git_get_hash_by_path($base, $file_name, "blob") - or die_error(undef, "Error lookup file."); - } else { - die_error(undef, "No file name defined."); - } - } - my $have_blame = git_get_project_config_bool ('blame'); - open my $fd, "-|", $GIT, "cat-file", "blob", $hash - or die_error(undef, "Couldn't cat $file_name, $hash."); - my $mimetype = git_blob_plain_mimetype($fd, $file_name); - if ($mimetype !~ m/^text\//) { - close $fd; - return git_blob_plain($mimetype); - } - git_header_html(); - my $formats_nav = ''; - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { - if (defined $file_name) { - if ($have_blame) { - $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | "; - } - $formats_nav .= - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head"); - } else { - $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain"); - } - git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_header_div('commit', esc_html($co{'title'}), $hash_base); - } else { - print "
\n" . - "

\n" . - "
$hash
\n"; - } - git_print_page_path($file_name, "blob"); - print "
\n"; - my $nr; - while (my $line = <$fd>) { - chomp $line; - $nr++; - while ((my $pos = index($line, "\t")) != -1) { - if (my $count = (8 - ($pos % 8))) { - my $spaces = ' ' x $count; - $line =~ s/\t/$spaces/; - } - } - printf "
%4i %s
\n", $nr, $nr, $nr, esc_html($line); - } - close $fd or print "Reading blob failed.\n"; - print "
"; - git_footer_html(); -} - -sub git_tree { - if (!defined $hash) { - $hash = git_read_head($project); - if (defined $file_name) { - my $base = $hash_base || $hash; - $hash = git_get_hash_by_path($base, $file_name, "tree"); - } - if (!defined $hash_base) { - $hash_base = $hash; - } - } - $/ = "\0"; - open my $fd, "-|", $GIT, "ls-tree", '-z', $hash - or die_error(undef, "Open git-ls-tree failed."); - my @entries = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading tree failed."); - $/ = "\n"; - - my $refs = read_info_ref(); - my $ref = git_get_referencing($refs, $hash_base); - git_header_html(); - my $base_key = ""; - my $base = ""; - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { - $base_key = ";hb=$hash_base"; - git_page_nav('tree','', $hash_base); - git_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); - } else { - print "
\n"; - print "

\n"; - print "
$hash
\n"; - } - if (defined $file_name) { - $base = esc_html("$file_name/"); - } - git_print_page_path($file_name, 'tree'); - print "
\n"; - print "\n"; - my $alternate = 0; - foreach my $line (@entries) { - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; - my $t_mode = $1; - my $t_type = $2; - my $t_hash = $3; - my $t_name = validate_input($4); - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n"; - if ($t_type eq "blob") { - print "\n" . - "\n"; - } elsif ($t_type eq "tree") { - print "\n" . - "\n"; - } - print "\n"; - } - print "
" . mode_str($t_mode) . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name"), -class => "list"}, esc_html($t_name)) . - "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob") . -# " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$t_hash;f=$base$t_name")}, "raw") . - "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, esc_html($t_name)) . - "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, "tree") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash_base;f=$base$t_name")}, "history") . - "
\n" . - "
"; - git_footer_html(); -} - -sub git_log { - my $head = git_read_head($project); - if (!defined $hash) { - $hash = $head; - } - if (!defined $page) { - $page = 0; - } - my $refs = read_info_ref(); - - my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", $GIT, "rev-list", $limit, $hash - or die_error(undef, "Open git-rev-list failed."); - my @revlist = map { chomp; $_ } <$fd>; - close $fd; - - my $paging_nav = git_get_paging_nav('log', $hash, $head, $page, $#revlist); - - git_header_html(); - git_page_nav('log','', $hash,undef,undef, $paging_nav); - - if (!@revlist) { - my %co = git_read_commit($hash); - - git_header_div('summary', $project); - print "
Last change $co{'age_string'}.

\n"; - } - for (my $i = ($page * 100); $i <= $#revlist; $i++) { - my $commit = $revlist[$i]; - my $ref = git_get_referencing($refs, $commit); - my %co = git_read_commit($commit); - next if !%co; - my %ad = date_str($co{'author_epoch'}); - git_header_div('commit', - "$co{'age_string'}" . - esc_html($co{'title'}) . $ref, - $commit); - print "
\n" . - "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - "
\n" . - "
\n" . - "" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]
\n" . - "
\n" . - "
\n"; - my $comment = $co{'comment'}; - my $empty = 0; - foreach my $line (@$comment) { - if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { - next; - } - if ($line eq "") { - if ($empty) { - next; - } - $empty = 1; - } else { - $empty = 0; - } - print format_log_line_html($line) . "
\n"; - } - if (!$empty) { - print "
\n"; - } - print "
\n"; - } - git_footer_html(); -} - -sub git_commit { - my %co = git_read_commit($hash); - if (!%co) { - die_error(undef, "Unknown commit object."); - } - my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); - my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); - - my $parent = $co{'parent'}; - if (!defined $parent) { - $parent = "--root"; - } - open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash - or die_error(undef, "Open git-diff-tree failed."); - my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading git-diff-tree failed."); - - # non-textual hash id's can be cached - my $expires; - if ($hash =~ m/^[0-9a-fA-F]{40}$/) { - $expires = "+1d"; - } - my $refs = read_info_ref(); - my $ref = git_get_referencing($refs, $co{'id'}); - my $formats_nav = ''; - if (defined $file_name && defined $co{'parent'}) { - my $parent = $co{'parent'}; - $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame"); - } - git_header_html(undef, $expires); - git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', - $hash, $co{'tree'}, $hash, - $formats_nav); - - if (defined $co{'parent'}) { - git_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); - } else { - git_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash); - } - print "
\n" . - "\n"; - print "\n". - "" . - "" . - "\n"; - print "\n"; - print "\n"; - print "\n"; - print "" . - "" . - "" . - "" . - "\n"; - my $parents = $co{'parents'}; - foreach my $par (@$parents) { - print "" . - "" . - "" . - "" . - "\n"; - } - print "
author" . esc_html($co{'author'}) . "
$ad{'rfc2822'}"; - if ($ad{'hour_local'} < 6) { - printf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); - } else { - printf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); - } - print "
committer" . esc_html($co{'committer'}) . "
$cd{'rfc2822'}" . sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) . "
commit$co{'id'}
tree" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), class => "list"}, $co{'tree'}) . - "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . - "
parent" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par"), class => "list"}, $par) . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash;hp=$par")}, "commitdiff") . - "
". - "
\n"; - print "
\n"; - my $comment = $co{'comment'}; - my $empty = 0; - my $signed = 0; - foreach my $line (@$comment) { - # print only one empty line - if ($line eq "") { - if ($empty || $signed) { - next; - } - $empty = 1; - } else { - $empty = 0; - } - if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { - $signed = 1; - print "" . esc_html($line) . "
\n"; - } else { - $signed = 0; - print format_log_line_html($line) . "
\n"; - } - } - print "
\n"; - print "
\n"; - if ($#difftree > 10) { - print(($#difftree + 1) . " files changed:\n"); - } - print "
\n"; - print "\n"; - my $alternate = 0; - foreach my $line (@difftree) { - # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' - # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { - next; - } - my $from_mode = $1; - my $to_mode = $2; - my $from_id = $3; - my $to_id = $4; - my $status = $5; - my $similarity = $6; - my $file = validate_input(unquote($7)); - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - if ($status eq "A") { - my $mode_chng = ""; - if (S_ISREG(oct $to_mode)) { - $mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777); - } - print "\n" . - "\n" . - "\n"; - } elsif ($status eq "D") { - print "\n" . - "\n" . - "\n" - } elsif ($status eq "M" || $status eq "T") { - my $mode_chnge = ""; - if ($from_mode != $to_mode) { - $mode_chnge = " [changed"; - if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) { - $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode); - } - if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) { - if (S_ISREG($from_mode) && S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777); - } elsif (S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777); - } - } - $mode_chnge .= "]\n"; - } - print "\n" . - "\n" . - "\n"; - } elsif ($status eq "R") { - my ($from_file, $to_file) = split "\t", $file; - my $mode_chng = ""; - if ($from_mode != $to_mode) { - $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); - } - print "\n" . - "\n" . - "\n"; - } - print "\n"; - } - print "
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "[new " . file_type($to_mode) . "$mode_chng]" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "[deleted " . file_type($from_mode). "]" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . - ""; - if ($to_id ne $from_id) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); - } - print "$mode_chnge"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob"); - if ($to_id ne $from_id) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file")}, "diff"); - } - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . "\n"; - print "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file"), -class => "list"}, esc_html($to_file)) . "[moved from " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$from_file"), -class => "list"}, esc_html($from_file)) . - " with " . (int $similarity) . "% similarity$mode_chng]" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob"); - if ($to_id ne $from_id) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file")}, "diff"); - } - print "
\n"; - git_footer_html(); -} - -sub git_blobdiff { - mkdir($git_temp, 0700); - git_header_html(); - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { - my $formats_nav = - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); - git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_header_div('commit', esc_html($co{'title'}), $hash_base); - } else { - print "
\n" . - "

\n" . - "
$hash vs $hash_parent
\n"; - } - git_print_page_path($file_name, "blob"); - print "
\n" . - "
blob:" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash_parent;hb=$hash_base;f=$file_name")}, $hash_parent) . - " -> blob:" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, $hash) . - "
\n"; - git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash); - print "
"; - git_footer_html(); -} - -sub git_blobdiff_plain { - mkdir($git_temp, 0700); - print $cgi->header(-type => "text/plain", -charset => 'utf-8'); - git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash, "plain"); -} - -sub git_commitdiff { - mkdir($git_temp, 0700); - my %co = git_read_commit($hash); - if (!%co) { - die_error(undef, "Unknown commit object."); - } - if (!defined $hash_parent) { - $hash_parent = $co{'parent'}; - } - open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash - or die_error(undef, "Open git-diff-tree failed."); - my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading diff-tree failed."); - - # non-textual hash id's can be cached - my $expires; - if ($hash =~ m/^[0-9a-fA-F]{40}$/) { - $expires = "+1d"; - } - my $refs = read_info_ref(); - my $ref = git_get_referencing($refs, $co{'id'}); - my $formats_nav = - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); - git_header_html(undef, $expires); - git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); - git_header_div('commit', esc_html($co{'title'}) . $ref, $hash); - print "
\n"; - my $comment = $co{'comment'}; - my $empty = 0; - my $signed = 0; - my @log = @$comment; - # remove first and empty lines after that - shift @log; - while (defined $log[0] && $log[0] eq "") { - shift @log; - } - foreach my $line (@log) { - if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { - next; - } - if ($line eq "") { - if ($empty) { - next; - } - $empty = 1; - } else { - $empty = 0; - } - print format_log_line_html($line) . "
\n"; - } - print "
\n"; - foreach my $line (@difftree) { - # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' - # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/; - my $from_mode = $1; - my $to_mode = $2; - my $from_id = $3; - my $to_id = $4; - my $status = $5; - my $file = validate_input(unquote($6)); - if ($status eq "A") { - print "
" . file_type($to_mode) . ":" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id) . "(new)" . - "
\n"; - git_diff_print(undef, "/dev/null", $to_id, "b/$file"); - } elsif ($status eq "D") { - print "
" . file_type($from_mode) . ":" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . "(deleted)" . - "
\n"; - git_diff_print($from_id, "a/$file", undef, "/dev/null"); - } elsif ($status eq "M") { - if ($from_id ne $to_id) { - print "
" . - file_type($from_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . - " -> " . - file_type($to_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id); - print "
\n"; - git_diff_print($from_id, "a/$file", $to_id, "b/$file"); - } - } - } - print "
\n" . - "
"; - git_footer_html(); -} - -sub git_commitdiff_plain { - mkdir($git_temp, 0700); - open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash - or die_error(undef, "Open git-diff-tree failed."); - my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading diff-tree failed."); - - # try to figure out the next tag after this commit - my $tagname; - my $refs = read_info_ref("tags"); - open $fd, "-|", $GIT, "rev-list", "HEAD"; - my @commits = map { chomp; $_ } <$fd>; - close $fd; - foreach my $commit (@commits) { - if (defined $refs->{$commit}) { - $tagname = $refs->{$commit} - } - if ($commit eq $hash) { - last; - } - } - - print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\""); - my %co = git_read_commit($hash); - my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); - my $comment = $co{'comment'}; - print "From: $co{'author'}\n" . - "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n". - "Subject: $co{'title'}\n"; - if (defined $tagname) { - print "X-Git-Tag: $tagname\n"; - } - print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" . - "\n"; - - foreach my $line (@$comment) {; - print "$line\n"; - } - print "---\n\n"; - - foreach my $line (@difftree) { - $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/; - my $from_id = $3; - my $to_id = $4; - my $status = $5; - my $file = $6; - if ($status eq "A") { - git_diff_print(undef, "/dev/null", $to_id, "b/$file", "plain"); - } elsif ($status eq "D") { - git_diff_print($from_id, "a/$file", undef, "/dev/null", "plain"); - } elsif ($status eq "M") { - git_diff_print($from_id, "a/$file", $to_id, "b/$file", "plain"); - } - } -} - -sub git_history { - if (!defined $hash_base) { - $hash_base = git_read_head($project); - } - my $ftype; - my %co = git_read_commit($hash_base); - if (!%co) { - die_error(undef, "Unknown commit object."); - } - my $refs = read_info_ref(); - git_header_html(); - git_page_nav('','', $hash_base,$co{'tree'},$hash_base); - git_header_div('commit', esc_html($co{'title'}), $hash_base); - if (!defined $hash && defined $file_name) { - $hash = git_get_hash_by_path($hash_base, $file_name); - } - if (defined $hash) { - $ftype = git_get_type($hash); - } - git_print_page_path($file_name, $ftype); - - open my $fd, "-|", - $GIT, "rev-list", "--full-history", $hash_base, "--", $file_name; - print "\n"; - my $alternate = 0; - while (my $line = <$fd>) { - if ($line =~ m/^([0-9a-fA-F]{40})/){ - my $commit = $1; - my %co = git_read_commit($commit); - if (!%co) { - next; - } - my $ref = git_get_referencing($refs, $commit); - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n" . - "\n" . - "\n" . - "\n" . - "\n"; - } - } - print "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, "" . - esc_html(chop_str($co{'title'}, 50)) . "$ref") . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$ftype;hb=$commit;f=$file_name")}, $ftype); - my $blob = git_get_hash_by_path($hash_base, $file_name); - my $blob_parent = git_get_hash_by_path($commit, $file_name); - if (defined $blob && defined $blob_parent && $blob ne $blob_parent) { - print " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$blob;hp=$blob_parent;hb=$commit;f=$file_name")}, - "diff to current"); - } - print "
\n"; - close $fd; - git_footer_html(); -} - -sub git_search { - if (!defined $searchtext) { - die_error("", "Text field empty."); - } - if (!defined $hash) { - $hash = git_read_head($project); - } - my %co = git_read_commit($hash); - if (!%co) { - die_error(undef, "Unknown commit object."); - } - # pickaxe may take all resources of your box and run for several minutes - # with every query - so decide by yourself how public you make this feature :) - my $commit_search = 1; - my $author_search = 0; - my $committer_search = 0; - my $pickaxe_search = 0; - if ($searchtext =~ s/^author\\://i) { - $author_search = 1; - } elsif ($searchtext =~ s/^committer\\://i) { - $committer_search = 1; - } elsif ($searchtext =~ s/^pickaxe\\://i) { - $commit_search = 0; - $pickaxe_search = 1; - } - git_header_html(); - git_page_nav('','', $hash,$co{'tree'},$hash); - git_header_div('commit', esc_html($co{'title'}), $hash); - - print "\n"; - my $alternate = 0; - if ($commit_search) { - $/ = "\0"; - open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash or next; - while (my $commit_text = <$fd>) { - if (!grep m/$searchtext/i, $commit_text) { - next; - } - if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) { - next; - } - if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) { - next; - } - my @commit_lines = split "\n", $commit_text; - my %co = git_read_commit(undef, \@commit_lines); - if (!%co) { - next; - } - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n" . - "\n" . - "\n" . - "\n" . - "\n"; - } - close $fd; - } - - if ($pickaxe_search) { - $/ = "\n"; - open my $fd, "-|", "$GIT rev-list $hash | $GIT diff-tree -r --stdin -S\'$searchtext\'"; - undef %co; - my @files; - while (my $line = <$fd>) { - if (%co && $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { - my %set; - $set{'file'} = $6; - $set{'from_id'} = $3; - $set{'to_id'} = $4; - $set{'id'} = $set{'to_id'}; - if ($set{'id'} =~ m/0{40}/) { - $set{'id'} = $set{'from_id'}; - } - if ($set{'id'} =~ m/0{40}/) { - next; - } - push @files, \%set; - } elsif ($line =~ m/^([0-9a-fA-F]{40})$/){ - if (%co) { - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n" . - "\n" . - "\n" . - "\n" . - "\n"; - } - %co = git_read_commit($1); - } - } - close $fd; - } - print "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "" . esc_html(chop_str($co{'title'}, 50)) . "
"); - my $comment = $co{'comment'}; - foreach my $line (@$comment) { - if ($line =~ m/^(.*)($searchtext)(.*)$/i) { - my $lead = esc_html($1) || ""; - $lead = chop_str($lead, 30, 10); - my $match = esc_html($2) || ""; - my $trail = esc_html($3) || ""; - $trail = chop_str($trail, 30, 10); - my $text = "$lead$match$trail"; - print chop_str($text, 80, 5) . "
\n"; - } - } - print "
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree"); - print "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "" . - esc_html(chop_str($co{'title'}, 50)) . "
"); - while (my $setref = shift @files) { - my %set = %$setref; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$set{'id'};hb=$co{'id'};f=$set{'file'}"), class => "list"}, - "" . esc_html($set{'file'}) . "") . - "
\n"; - } - print "
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree"); - print "
\n"; - git_footer_html(); -} - -sub git_shortlog { - my $head = git_read_head($project); - if (!defined $hash) { - $hash = $head; - } - if (!defined $page) { - $page = 0; - } - my $refs = read_info_ref(); - - my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", $GIT, "rev-list", $limit, $hash - or die_error(undef, "Open git-rev-list failed."); - my @revlist = map { chomp; $_ } <$fd>; - close $fd; - - my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist); - my $next_link = ''; - if ($#revlist >= (100 * ($page+1)-1)) { - $next_link = - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), - -title => "Alt-n"}, "next"); - } - - - git_header_html(); - git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); - git_header_div('summary', $project); - - git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link); - - git_footer_html(); -} - -## ...................................................................... -## feeds (RSS, OPML) - -sub git_rss { - # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ - open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) - or die_error(undef, "Open git-rev-list failed."); - my @revlist = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading rev-list failed."); - print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "\n". - "\n"; - print "\n"; - print "$project\n". - "" . esc_html("$my_url?p=$project;a=summary") . "\n". - "$project log\n". - "en\n"; - - for (my $i = 0; $i <= $#revlist; $i++) { - my $commit = $revlist[$i]; - my %co = git_read_commit($commit); - # we read 150, we always show 30 and the ones more recent than 48 hours - if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { - last; - } - my %cd = date_str($co{'committer_epoch'}); - open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next; - my @difftree = map { chomp; $_ } <$fd>; - close $fd or next; - print "\n" . - "" . - sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . - "\n" . - "" . esc_html($co{'author'}) . "\n" . - "$cd{'rfc2822'}\n" . - "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" . - "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" . - "" . esc_html($co{'title'}) . "\n" . - "" . - "\n"; - } - print "
\n"; - foreach my $line (@difftree) { - if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { - next; - } - my $file = validate_input(unquote($7)); - $file = decode("utf8", $file, Encode::FB_DEFAULT); - print "$file
\n"; - } - print "]]>\n" . - "
\n" . - "
\n"; - } - print "
"; -} - -sub git_opml { - my @list = git_read_projects(); - - print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "\n". - "\n". - "". - " $site_name Git OPML Export\n". - "\n". - "\n". - "\n"; - - foreach my $pr (@list) { - my %proj = %$pr; - my $head = git_read_head($proj{'path'}); - if (!defined $head) { - next; - } - $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; - my %co = git_read_commit($head); - if (!%co) { - next; - } - - my $path = esc_html(chop_str($proj{'path'}, 25, 5)); - my $rss = "$my_url?p=$proj{'path'};a=rss"; - my $html = "$my_url?p=$proj{'path'};a=summary"; - print "\n"; - } - print "\n". - "\n". - "\n"; -} diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl new file mode 100755 index 0000000000..6e4261d5f2 --- /dev/null +++ b/gitweb/gitweb.perl @@ -0,0 +1,2597 @@ +#!/usr/bin/perl + +# gitweb - simple web interface to track changes in git repositories +# +# (C) 2005-2006, Kay Sievers +# (C) 2005, Christian Gierke +# +# This program is licensed under the GPLv2 + +use strict; +use warnings; +use CGI qw(:standard :escapeHTML -nosticky); +use CGI::Util qw(unescape); +use CGI::Carp qw(fatalsToBrowser); +use Encode; +use Fcntl ':mode'; +use File::Find qw(); +binmode STDOUT, ':utf8'; + +our $cgi = new CGI; +our $version = "@@GIT_VERSION@@"; +our $my_url = $cgi->url(); +our $my_uri = $cgi->url(-absolute => 1); +our $rss_link = ""; + +# core git executable to use +# this can just be "git" if your webserver has a sensible PATH +our $GIT = "@@GIT_BINDIR@@/git"; + +# absolute fs-path which will be prepended to the project path +#our $projectroot = "/pub/scm"; +our $projectroot = "@@GITWEB_PROJECTROOT@@"; + +# version of the core git binary +our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; + +# location for temporary files needed for diffs +our $git_temp = "/tmp/gitweb"; +if (! -d $git_temp) { + mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp"); +} + +# target of the home link on top of all pages +our $home_link = $my_uri; + +# name of your site or organization to appear in page titles +# replace this with something more descriptive for clearer bookmarks +our $site_name = "@@GITWEB_SITENAME@@" || $ENV{'SERVER_NAME'} || "Untitled"; + +# html text to include at home page +our $home_text = "@@GITWEB_HOMETEXT@@"; + +# URI of default stylesheet +our $stylesheet = "@@GITWEB_CSS@@"; + +# source of projects list +our $projects_list = "@@GITWEB_LIST@@" || "$projectroot"; + +# default blob_plain mimetype and default charset for text/plain blob +our $default_blob_plain_mimetype = 'text/plain'; +our $default_text_plain_charset = undef; + +# file to use for guessing MIME types before trying /etc/mime.types +# (relative to the current git repository) +our $mimetypes_file = undef; + +# input validation and dispatch +our $action = $cgi->param('a'); +if (defined $action) { + if ($action =~ m/[^0-9a-zA-Z\.\-_]/) { + undef $action; + die_error(undef, "Invalid action parameter."); + } + if ($action eq "git-logo.png") { + git_logo(); + exit; + } elsif ($action eq "opml") { + git_opml(); + exit; + } +} + +our $project = ($cgi->param('p') || $ENV{'PATH_INFO'}); +if (defined $project) { + $project =~ s|^/||; $project =~ s|/$||; + $project = validate_input($project); + if (!defined($project)) { + die_error(undef, "Invalid project parameter."); + } + if (!(-d "$projectroot/$project")) { + undef $project; + die_error(undef, "No such directory."); + } + if (!(-e "$projectroot/$project/HEAD")) { + undef $project; + die_error(undef, "No such project."); + } + $rss_link = ""; + $ENV{'GIT_DIR'} = "$projectroot/$project"; +} else { + git_project_list(); + exit; +} + +our $file_name = $cgi->param('f'); +if (defined $file_name) { + $file_name = validate_input($file_name); + if (!defined($file_name)) { + die_error(undef, "Invalid file parameter."); + } +} + +our $hash = $cgi->param('h'); +if (defined $hash) { + $hash = validate_input($hash); + if (!defined($hash)) { + die_error(undef, "Invalid hash parameter."); + } +} + +our $hash_parent = $cgi->param('hp'); +if (defined $hash_parent) { + $hash_parent = validate_input($hash_parent); + if (!defined($hash_parent)) { + die_error(undef, "Invalid hash parent parameter."); + } +} + +our $hash_base = $cgi->param('hb'); +if (defined $hash_base) { + $hash_base = validate_input($hash_base); + if (!defined($hash_base)) { + die_error(undef, "Invalid hash base parameter."); + } +} + +our $page = $cgi->param('pg'); +if (defined $page) { + if ($page =~ m/[^0-9]$/) { + undef $page; + die_error(undef, "Invalid page parameter."); + } +} + +our $searchtext = $cgi->param('s'); +if (defined $searchtext) { + if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) { + undef $searchtext; + die_error(undef, "Invalid search parameter."); + } + $searchtext = quotemeta $searchtext; +} + +# dispatch +my %actions = ( + "blame" => \&git_blame2, + "blobdiff" => \&git_blobdiff, + "blobdiff_plain" => \&git_blobdiff_plain, + "blob" => \&git_blob, + "blob_plain" => \&git_blob_plain, + "commitdiff" => \&git_commitdiff, + "commitdiff_plain" => \&git_commitdiff_plain, + "commit" => \&git_commit, + "heads" => \&git_heads, + "history" => \&git_history, + "log" => \&git_log, + "rss" => \&git_rss, + "search" => \&git_search, + "shortlog" => \&git_shortlog, + "summary" => \&git_summary, + "tag" => \&git_tag, + "tags" => \&git_tags, + "tree" => \&git_tree, +); + +$action = 'summary' if (!defined($action)); +if (!defined($actions{$action})) { + undef $action; + die_error(undef, "Unknown action."); +} +$actions{$action}->(); +exit; + +## ====================================================================== +## validation, quoting/unquoting and escaping + +sub validate_input { + my $input = shift; + + if ($input =~ m/^[0-9a-fA-F]{40}$/) { + return $input; + } + if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) { + return undef; + } + if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) { + return undef; + } + return $input; +} + +# quote unsafe chars, but keep the slash, even when it's not +# 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/ /\+/g; + return $str; +} + +# replace invalid utf8 character with SUBSTITUTION sequence +sub esc_html { + my $str = shift; + $str = decode("utf8", $str, Encode::FB_DEFAULT); + $str = escapeHTML($str); + $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) + return $str; +} + +# git may return quoted and escaped filenames +sub unquote { + my $str = shift; + if ($str =~ m/^"(.*)"$/) { + $str = $1; + $str =~ s/\\([0-7]{1,3})/chr(oct($1))/eg; + } + return $str; +} + +## ---------------------------------------------------------------------- +## HTML aware string manipulation + +sub chop_str { + my $str = shift; + my $len = shift; + my $add_len = shift || 10; + + # allow only $len chars, but don't cut a word if it would fit in $add_len + # if it doesn't fit, cut it if it's still longer than the dots we would add + $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; + my $body = $1; + my $tail = $2; + if (length($tail) > 4) { + $tail = " ..."; + $body =~ s/&[^;]*$//; # remove chopped character entities + } + return "$body$tail"; +} + +## ---------------------------------------------------------------------- +## functions returning short strings + +# CSS class for given age value (in seconds) +sub age_class { + my $age = shift; + + if ($age < 60*60*2) { + return "age0"; + } elsif ($age < 60*60*24*2) { + return "age1"; + } else { + return "age2"; + } +} + +# convert age in seconds to "nn units ago" string +sub age_string { + my $age = shift; + my $age_str; + + if ($age > 60*60*24*365*2) { + $age_str = (int $age/60/60/24/365); + $age_str .= " years ago"; + } elsif ($age > 60*60*24*(365/12)*2) { + $age_str = int $age/60/60/24/(365/12); + $age_str .= " months ago"; + } elsif ($age > 60*60*24*7*2) { + $age_str = int $age/60/60/24/7; + $age_str .= " weeks ago"; + } elsif ($age > 60*60*24*2) { + $age_str = int $age/60/60/24; + $age_str .= " days ago"; + } elsif ($age > 60*60*2) { + $age_str = int $age/60/60; + $age_str .= " hours ago"; + } elsif ($age > 60*2) { + $age_str = int $age/60; + $age_str .= " min ago"; + } elsif ($age > 2) { + $age_str = int $age; + $age_str .= " sec ago"; + } else { + $age_str .= " right now"; + } + return $age_str; +} + +# convert file mode in octal to symbolic file mode string +sub mode_str { + my $mode = oct shift; + + if (S_ISDIR($mode & S_IFMT)) { + return 'drwxr-xr-x'; + } elsif (S_ISLNK($mode)) { + return 'lrwxrwxrwx'; + } elsif (S_ISREG($mode)) { + # git cares only about the executable bit + if ($mode & S_IXUSR) { + return '-rwxr-xr-x'; + } else { + return '-rw-r--r--'; + }; + } else { + return '----------'; + } +} + +# convert file mode in octal to file type string +sub file_type { + my $mode = oct shift; + + if (S_ISDIR($mode & S_IFMT)) { + return "directory"; + } elsif (S_ISLNK($mode)) { + return "symlink"; + } elsif (S_ISREG($mode)) { + return "file"; + } else { + return "unknown"; + } +} + +## ---------------------------------------------------------------------- +## functions returning short HTML fragments, or transforming HTML fragments +## which don't beling to other sections + +# format line of commit message or tag comment +sub format_log_line_html { + my $line = shift; + + $line = esc_html($line); + $line =~ s/ / /g; + if ($line =~ m/([0-9a-fA-F]{40})/) { + my $hash_text = $1; + if (git_get_type($hash_text) eq "commit") { + my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text); + $line =~ s/$hash_text/$link/; + } + } + return $line; +} + +# format marker of refs pointing to given object +sub git_get_referencing { + my ($refs, $id) = @_; + + if (defined $refs->{$id}) { + return ' ' . esc_html($refs->{$id}) . ''; + } else { + return ""; + } +} + +## ---------------------------------------------------------------------- +## git utility subroutines, invoking git commands + +# get HEAD ref of given project as hash +sub git_read_head { + my $project = shift; + my $oENV = $ENV{'GIT_DIR'}; + my $retval = undef; + $ENV{'GIT_DIR'} = "$projectroot/$project"; + if (open my $fd, "-|", $GIT, "rev-parse", "--verify", "HEAD") { + my $head = <$fd>; + close $fd; + if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) { + $retval = $1; + } + } + if (defined $oENV) { + $ENV{'GIT_DIR'} = $oENV; + } + return $retval; +} + +# get type of given object +sub git_get_type { + my $hash = shift; + + open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return; + my $type = <$fd>; + close $fd or return; + chomp $type; + return $type; +} + +sub git_get_project_config { + my $key = shift; + + return unless ($key); + $key =~ s/^gitweb\.//; + return if ($key =~ m/\W/); + + my $val = qx($GIT repo-config --get gitweb.$key); + return ($val); +} + +sub git_get_project_config_bool { + my $val = git_get_project_config (@_); + if ($val and $val =~ m/true|yes|on/) { + return (1); + } + return; # implicit false +} + +# get hash of given path at given ref +sub git_get_hash_by_path { + my $base = shift; + my $path = shift || return undef; + + my $tree = $base; + + open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path + or die_error(undef, "Open git-ls-tree failed."); + my $line = <$fd>; + close $fd or return undef; + + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + return $3; +} + +## ...................................................................... +## git utility functions, directly accessing git repository + +# assumes that PATH is not symref +sub git_read_hash { + my $path = shift; + + open my $fd, "$projectroot/$path" or return undef; + my $head = <$fd>; + close $fd; + chomp $head; + if ($head =~ m/^[0-9a-fA-F]{40}$/) { + return $head; + } +} + +sub git_read_description { + my $path = shift; + + open my $fd, "$projectroot/$path/description" or return undef; + my $descr = <$fd>; + close $fd; + chomp $descr; + return $descr; +} + +sub git_read_projects { + my @list; + + if (-d $projects_list) { + # search in directory + my $dir = $projects_list; + opendir my ($dh), $dir or return undef; + while (my $dir = readdir($dh)) { + if (-e "$projectroot/$dir/HEAD") { + my $pr = { + path => $dir, + }; + push @list, $pr + } + } + closedir($dh); + } elsif (-f $projects_list) { + # read from file(url-encoded): + # 'git%2Fgit.git Linus+Torvalds' + # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' + # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' + open my ($fd), $projects_list or return undef; + while (my $line = <$fd>) { + chomp $line; + my ($path, $owner) = split ' ', $line; + $path = unescape($path); + $owner = unescape($owner); + if (!defined $path) { + next; + } + if (-e "$projectroot/$path/HEAD") { + my $pr = { + path => $path, + owner => decode("utf8", $owner, Encode::FB_DEFAULT), + }; + push @list, $pr + } + } + close $fd; + } + @list = sort {$a->{'path'} cmp $b->{'path'}} @list; + return @list; +} + +sub read_info_ref { + my $type = shift || ""; + my %refs; + # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 + # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} + open my $fd, "$projectroot/$project/info/refs" or return; + while (my $line = <$fd>) { + chomp $line; + # attention: for $type == "" it saves only last path part of ref name + # e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb' + if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) { + if (defined $refs{$1}) { + $refs{$1} .= " / $2"; + } else { + $refs{$1} = $2; + } + } + } + close $fd or return; + return \%refs; +} + +## ---------------------------------------------------------------------- +## parse to hash functions + +sub date_str { + my $epoch = shift; + my $tz = shift || "-0000"; + + my %date; + my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); + my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); + $date{'hour'} = $hour; + $date{'minute'} = $min; + $date{'mday'} = $mday; + $date{'day'} = $days[$wday]; + $date{'month'} = $months[$mon]; + $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; + $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; + + $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; + my $local = $epoch + ((int $1 + ($2/60)) * 3600); + ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); + $date{'hour_local'} = $hour; + $date{'minute_local'} = $min; + $date{'tz_local'} = $tz; + return %date; +} + +sub git_read_tag { + my $tag_id = shift; + my %tag; + my @comment; + + open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return; + $tag{'id'} = $tag_id; + while (my $line = <$fd>) { + chomp $line; + if ($line =~ m/^object ([0-9a-fA-F]{40})$/) { + $tag{'object'} = $1; + } elsif ($line =~ m/^type (.+)$/) { + $tag{'type'} = $1; + } elsif ($line =~ m/^tag (.+)$/) { + $tag{'name'} = $1; + } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) { + $tag{'author'} = $1; + $tag{'epoch'} = $2; + $tag{'tz'} = $3; + } elsif ($line =~ m/--BEGIN/) { + push @comment, $line; + last; + } elsif ($line eq "") { + last; + } + } + push @comment, <$fd>; + $tag{'comment'} = \@comment; + close $fd or return; + if (!defined $tag{'name'}) { + return + }; + return %tag +} + +sub git_read_commit { + my $commit_id = shift; + my $commit_text = shift; + + my @commit_lines; + my %co; + + if (defined $commit_text) { + @commit_lines = @$commit_text; + } else { + $/ = "\0"; + open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return; + @commit_lines = split '\n', <$fd>; + close $fd or return; + $/ = "\n"; + pop @commit_lines; + } + my $header = shift @commit_lines; + if (!($header =~ m/^[0-9a-fA-F]{40}/)) { + return; + } + ($co{'id'}, my @parents) = split ' ', $header; + $co{'parents'} = \@parents; + $co{'parent'} = $parents[0]; + while (my $line = shift @commit_lines) { + last if $line eq "\n"; + if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) { + $co{'tree'} = $1; + } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) { + $co{'author'} = $1; + $co{'author_epoch'} = $2; + $co{'author_tz'} = $3; + if ($co{'author'} =~ m/^([^<]+) 50) { + $title =~ s/^Automatic //; + $title =~ s/^merge (of|with) /Merge ... /i; + if (length($title) > 50) { + $title =~ s/(http|rsync):\/\///; + } + if (length($title) > 50) { + $title =~ s/(master|www|rsync)\.//; + } + if (length($title) > 50) { + $title =~ s/kernel.org:?//; + } + if (length($title) > 50) { + $title =~ s/\/pub\/scm//; + } + } + $co{'title_short'} = chop_str($title, 50, 5); + last; + } + } + # remove added spaces + foreach my $line (@commit_lines) { + $line =~ s/^ //; + } + $co{'comment'} = \@commit_lines; + + my $age = time - $co{'committer_epoch'}; + $co{'age'} = $age; + $co{'age_string'} = age_string($age); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'}); + if ($age > 60*60*24*7*2) { + $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; + $co{'age_string_age'} = $co{'age_string'}; + } else { + $co{'age_string_date'} = $co{'age_string'}; + $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; + } + return %co; +} + +## ...................................................................... +## parse to array of hashes functions + +sub git_read_refs { + my $ref_dir = shift; + my @reflist; + + my @refs; + my $pfxlen = length("$projectroot/$project/$ref_dir"); + File::Find::find(sub { + return if (/^\./); + if (-f $_) { + push @refs, substr($File::Find::name, $pfxlen + 1); + } + }, "$projectroot/$project/$ref_dir"); + + foreach my $ref_file (@refs) { + my $ref_id = git_read_hash("$project/$ref_dir/$ref_file"); + my $type = git_get_type($ref_id) || next; + my %ref_item; + my %co; + $ref_item{'type'} = $type; + $ref_item{'id'} = $ref_id; + $ref_item{'epoch'} = 0; + $ref_item{'age'} = "unknown"; + if ($type eq "tag") { + my %tag = git_read_tag($ref_id); + $ref_item{'comment'} = $tag{'comment'}; + if ($tag{'type'} eq "commit") { + %co = git_read_commit($tag{'object'}); + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; + } elsif (defined($tag{'epoch'})) { + my $age = time - $tag{'epoch'}; + $ref_item{'epoch'} = $tag{'epoch'}; + $ref_item{'age'} = age_string($age); + } + $ref_item{'reftype'} = $tag{'type'}; + $ref_item{'name'} = $tag{'name'}; + $ref_item{'refid'} = $tag{'object'}; + } elsif ($type eq "commit"){ + %co = git_read_commit($ref_id); + $ref_item{'reftype'} = "commit"; + $ref_item{'name'} = $ref_file; + $ref_item{'title'} = $co{'title'}; + $ref_item{'refid'} = $ref_id; + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; + } else { + $ref_item{'reftype'} = $type; + $ref_item{'name'} = $ref_file; + $ref_item{'refid'} = $ref_id; + } + + push @reflist, \%ref_item; + } + # sort tags by age + @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; + return \@reflist; +} + +## ---------------------------------------------------------------------- +## filesystem-related functions + +sub get_file_owner { + my $path = shift; + + my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path); + my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid); + if (!defined $gcos) { + return undef; + } + my $owner = $gcos; + $owner =~ s/[,;].*$//; + return decode("utf8", $owner, Encode::FB_DEFAULT); +} + +## ...................................................................... +## mimetype related functions + +sub mimetype_guess_file { + my $filename = shift; + my $mimemap = shift; + -r $mimemap or return undef; + + my %mimemap; + open(MIME, $mimemap) or return undef; + while () { + my ($mime, $exts) = split(/\t+/); + if (defined $exts) { + my @exts = split(/\s+/, $exts); + foreach my $ext (@exts) { + $mimemap{$ext} = $mime; + } + } + } + close(MIME); + + $filename =~ /\.(.*?)$/; + return $mimemap{$1}; +} + +sub mimetype_guess { + my $filename = shift; + my $mime; + $filename =~ /\./ or return undef; + + if ($mimetypes_file) { + my $file = $mimetypes_file; + #$file =~ m#^/# or $file = "$projectroot/$path/$file"; + $mime = mimetype_guess_file($filename, $file); + } + $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); + return $mime; +} + +sub git_blob_plain_mimetype { + my $fd = shift; + my $filename = shift; + + if ($filename) { + my $mime = mimetype_guess($filename); + $mime and return $mime; + } + + # just in case + return $default_blob_plain_mimetype unless $fd; + + if (-T $fd) { + return 'text/plain' . + ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); + } elsif (! $filename) { + return 'application/octet-stream'; + } elsif ($filename =~ m/\.png$/i) { + return 'image/png'; + } elsif ($filename =~ m/\.gif$/i) { + return 'image/gif'; + } elsif ($filename =~ m/\.jpe?g$/i) { + return 'image/jpeg'; + } else { + return 'application/octet-stream'; + } +} + +## ====================================================================== +## functions printing HTML: header, footer, error page + +sub git_header_html { + my $status = shift || "200 OK"; + my $expires = shift; + + my $title = "$site_name git"; + if (defined $project) { + $title .= " - $project"; + if (defined $action) { + $title .= "/$action"; + if (defined $file_name) { + $title .= " - $file_name"; + if ($action eq "tree" && $file_name !~ m|/$|) { + $title .= "/"; + } + } + } + } + my $content_type; + # require explicit support from the UA if we are to send the page as + # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. + # we have to do this because MSIE sometimes globs '*/*', pretending to + # support xhtml+xml but choking when it gets what it asked for. + if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) { + $content_type = 'application/xhtml+xml'; + } else { + $content_type = 'text/html'; + } + print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); + print < + + + + + + + +$title + +$rss_link + + +EOF + print "
\n" . + "" . + "\"git\"" . + "\n"; + print $cgi->a({-href => esc_param($home_link)}, "projects") . " / "; + if (defined $project) { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project)); + if (defined $action) { + print " / $action"; + } + print "\n"; + if (!defined $searchtext) { + $searchtext = ""; + } + my $search_hash; + if (defined $hash_base) { + $search_hash = $hash_base; + } elsif (defined $hash) { + $search_hash = $hash; + } else { + $search_hash = "HEAD"; + } + $cgi->param("a", "search"); + $cgi->param("h", $search_hash); + print $cgi->startform(-method => "get", -action => $my_uri) . + "
\n" . + $cgi->hidden(-name => "p") . "\n" . + $cgi->hidden(-name => "a") . "\n" . + $cgi->hidden(-name => "h") . "\n" . + $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . + "
" . + $cgi->end_form() . "\n"; + } + print "
\n"; +} + +sub git_footer_html { + print "
\n"; + if (defined $project) { + my $descr = git_read_description($project); + if (defined $descr) { + print "\n"; + } + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n"; + } else { + print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n"; + } + print "
\n" . + "\n" . + ""; +} + +sub die_error { + my $status = shift || "403 Forbidden"; + my $error = shift || "Malformed query, file missing or permission denied"; + + git_header_html($status); + print "
\n" . + "

\n" . + "$status - $error\n" . + "
\n" . + "
\n"; + git_footer_html(); + exit; +} + +## ---------------------------------------------------------------------- +## functions printing or outputting HTML: navigation + +sub git_page_nav { + my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_; + $extra = '' if !defined $extra; # pager or formats + + my @navs = qw(summary shortlog log commit commitdiff tree); + if ($suppress) { + @navs = grep { $_ ne $suppress } @navs; + } + + my %arg = map { $_, ''} @navs; + if (defined $head) { + for (qw(commit commitdiff)) { + $arg{$_} = ";h=$head"; + } + if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) { + for (qw(shortlog log)) { + $arg{$_} = ";h=$head"; + } + } + } + $arg{tree} .= ";h=$treehead" if defined $treehead; + $arg{tree} .= ";hb=$treebase" if defined $treebase; + + print "
\n" . + (join " | ", + map { $_ eq $current + ? $_ + : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_") + } + @navs); + print "
\n$extra
\n" . + "
\n"; +} + +sub git_get_paging_nav { + my ($action, $hash, $head, $page, $nrevs) = @_; + my $paging_nav; + + + if ($hash ne $head || $page) { + $paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD"); + } else { + $paging_nav .= "HEAD"; + } + + if ($page > 0) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)), + -accesskey => "p", -title => "Alt-p"}, "prev"); + } else { + $paging_nav .= " ⋅ prev"; + } + + if ($nrevs >= (100 * ($page+1)-1)) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)), + -accesskey => "n", -title => "Alt-n"}, "next"); + } else { + $paging_nav .= " ⋅ next"; + } + + return $paging_nav; +} + +## ...................................................................... +## functions printing or outputting HTML: div + +sub git_header_div { + my ($action, $title, $hash, $hash_base) = @_; + my $rest = ''; + + $rest .= ";h=$hash" if $hash; + $rest .= ";hb=$hash_base" if $hash_base; + + print "
\n" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"), + -class => "title"}, $title ? $title : $action) . "\n" . + "
\n"; +} + +sub git_print_page_path { + my $name = shift; + my $type = shift; + + if (!defined $name) { + print "
/
\n"; + } elsif (defined $type && $type eq 'blob') { + print "
" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "
\n"; + } else { + print "
" . esc_html($name) . "
\n"; + } +} + +## ...................................................................... +## functions printing large fragments of HTML + +sub git_shortlog_body { + # uses global variable $project + my ($revlist, $from, $to, $refs, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); + + print "\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $commit = $revlist->[$i]; + #my $ref = defined $refs ? git_get_referencing($refs, $commit) : ''; + my $ref = git_get_referencing($refs, $commit); + my %co = git_read_commit($commit); + my %ad = date_str($co{'author_epoch'}); + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + # git_summary() used print "\n" . + print "\n" . + "\n" . + "\n" . + "\n" . + "\n"; + } + if (defined $extra) { + print "\n" . + "\n" . + "\n"; + } + print "
$co{'age_string'}$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 10)) . ""; + if (length($co{'title_short'}) < length($co{'title'})) { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), + -class => "list", -title => "$co{'title'}"}, + "" . esc_html($co{'title_short'}) . "$ref"); + } else { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), + -class => "list"}, + "" . esc_html($co{'title'}) . "$ref"); + } + print "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . + "
$extra
\n"; +} + +sub git_tags_body { + # uses global variable $project + my ($taglist, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + + print "\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $entry = $taglist->[$i]; + my %tag = %$entry; + my $comment_lines = $tag{'comment'}; + my $comment = shift @$comment_lines; + my $comment_short; + if (defined $comment) { + $comment_short = chop_str($comment, 30, 5); + } + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + ""; + } + if (defined $extra) { + print "\n" . + "\n" . + "\n"; + } + print "
$tag{'age'}" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), + -class => "list"}, "" . esc_html($tag{'name'}) . "") . + ""; + if (defined $comment) { + if (length($comment_short) < length($comment)) { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), + -class => "list", -title => $comment}, $comment_short); + } else { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), + -class => "list"}, $comment); + } + } + print ""; + if ($tag{'type'} eq "tag") { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag"); + } else { + print " "; + } + print "" . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); + if ($tag{'reftype'} eq "commit") { + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); + } elsif ($tag{'reftype'} eq "blob") { + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$tag{'refid'}")}, "raw"); + } + print "
$extra
\n"; +} + +sub git_heads_body { + # uses global variable $project + my ($taglist, $head, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + + print "\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $entry = $taglist->[$i]; + my %tag = %$entry; + my $curr = $tag{'id'} eq $head; + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n" . + ($tag{'id'} eq $head ? "\n" . + "\n" . + ""; + } + if (defined $extra) { + print "\n" . + "\n" . + "\n"; + } + print "
$tag{'age'}" : "") . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), + -class => "list"}, "" . esc_html($tag{'name'}) . "") . + "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") . + "
$extra
\n"; +} + +## ---------------------------------------------------------------------- +## functions printing large fragments, format as one of arguments + +sub git_diff_print { + my $from = shift; + my $from_name = shift; + my $to = shift; + my $to_name = shift; + my $format = shift || "html"; + + my $from_tmp = "/dev/null"; + my $to_tmp = "/dev/null"; + my $pid = $$; + + # create tmp from-file + if (defined $from) { + $from_tmp = "$git_temp/gitweb_" . $$ . "_from"; + open my $fd2, "> $from_tmp"; + open my $fd, "-|", $GIT, "cat-file", "blob", $from; + my @file = <$fd>; + print $fd2 @file; + close $fd2; + close $fd; + } + + # create tmp to-file + if (defined $to) { + $to_tmp = "$git_temp/gitweb_" . $$ . "_to"; + open my $fd2, "> $to_tmp"; + open my $fd, "-|", $GIT, "cat-file", "blob", $to; + my @file = <$fd>; + print $fd2 @file; + close $fd2; + close $fd; + } + + open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp"; + if ($format eq "plain") { + undef $/; + print <$fd>; + $/ = "\n"; + } else { + while (my $line = <$fd>) { + chomp $line; + my $char = substr($line, 0, 1); + my $diff_class = ""; + if ($char eq '+') { + $diff_class = " add"; + } elsif ($char eq "-") { + $diff_class = " rem"; + } elsif ($char eq "@") { + $diff_class = " chunk_header"; + } elsif ($char eq "\\") { + # skip errors + next; + } + while ((my $pos = index($line, "\t")) != -1) { + if (my $count = (8 - (($pos-1) % 8))) { + my $spaces = ' ' x $count; + $line =~ s/\t/$spaces/; + } + } + print "
" . esc_html($line) . "
\n"; + } + } + close $fd; + + if (defined $from) { + unlink($from_tmp); + } + if (defined $to) { + unlink($to_tmp); + } +} + + +## ====================================================================== +## ====================================================================== +## actions + +# git-logo (cached in browser for one day) +sub git_logo { + binmode STDOUT, ':raw'; + print $cgi->header(-type => 'image/png', -expires => '+1d'); + # cat git-logo.png | hexdump -e '16/1 " %02x" "\n"' | sed 's/ /\\x/g' + print "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" . + "\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" . + "\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" . + "\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" . + "\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" . + "\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" . + "\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" . + "\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" . + "\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" . + "\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" . + "\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" . + "\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" . + "\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; +} + +sub git_project_list { + my $order = $cgi->param('o'); + if (defined $order && $order !~ m/project|descr|owner|age/) { + die_error(undef, "Invalid order parameter '$order'."); + } + + my @list = git_read_projects(); + my @projects; + if (!@list) { + die_error(undef, "No projects found."); + } + foreach my $pr (@list) { + my $head = git_read_head($pr->{'path'}); + if (!defined $head) { + next; + } + $ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}"; + my %co = git_read_commit($head); + if (!%co) { + next; + } + $pr->{'commit'} = \%co; + if (!defined $pr->{'descr'}) { + my $descr = git_read_description($pr->{'path'}) || ""; + $pr->{'descr'} = chop_str($descr, 25, 5); + } + if (!defined $pr->{'owner'}) { + $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || ""; + } + push @projects, $pr; + } + + git_header_html(); + if (-f $home_text) { + print "
\n"; + open (my $fd, $home_text); + print <$fd>; + close $fd; + print "
\n"; + } + print "\n" . + "\n"; + $order ||= "project"; + if ($order eq "project") { + @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; + print "\n"; + } else { + print "\n"; + } + if ($order eq "descr") { + @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; + print "\n"; + } else { + print "\n"; + } + if ($order eq "owner") { + @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; + print "\n"; + } else { + print "\n"; + } + if ($order eq "age") { + @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects; + print "\n"; + } else { + print "\n"; + } + print "\n" . + "\n"; + my $alternate = 0; + foreach my $pr (@projects) { + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n" . + "\n" . + "\n"; + print "\n" . + "\n" . + "\n"; + } + print "
Project" . + $cgi->a({-href => "$my_uri?" . esc_param("o=project"), + -class => "header"}, "Project") . + "Description" . + $cgi->a({-href => "$my_uri?" . esc_param("o=descr"), + -class => "header"}, "Description") . + "Owner" . + $cgi->a({-href => "$my_uri?" . esc_param("o=owner"), + -class => "header"}, "Owner") . + "Last Change" . + $cgi->a({-href => "$my_uri?" . esc_param("o=age"), + -class => "header"}, "Last Change") . + "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), + -class => "list"}, esc_html($pr->{'path'})) . "" . esc_html($pr->{'descr'}) . "" . chop_str($pr->{'owner'}, 15) . "{'commit'}{'age'}) . "\">" . + $pr->{'commit'}{'age_string'} . "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . + "
\n"; + git_footer_html(); +} + +sub git_summary { + my $descr = git_read_description($project) || "none"; + my $head = git_read_head($project); + my %co = git_read_commit($head); + my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); + + my $owner; + if (-f $projects_list) { + open (my $fd , $projects_list); + while (my $line = <$fd>) { + chomp $line; + my ($pr, $ow) = split ' ', $line; + $pr = unescape($pr); + $ow = unescape($ow); + if ($pr eq $project) { + $owner = decode("utf8", $ow, Encode::FB_DEFAULT); + last; + } + } + close $fd; + } + if (!defined $owner) { + $owner = get_file_owner("$projectroot/$project"); + } + + my $refs = read_info_ref(); + git_header_html(); + git_page_nav('summary','', $head); + + print "
 
\n"; + print "\n" . + "\n" . + "\n" . + "\n" . + "
description" . esc_html($descr) . "
owner$owner
last change$cd{'rfc2822'}
\n"; + + open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) + or die_error(undef, "Open git-rev-list failed."); + my @revlist = map { chomp; $_ } <$fd>; + close $fd; + git_header_div('shortlog'); + git_shortlog_body(\@revlist, 0, 15, $refs, + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "...")); + + my $taglist = git_read_refs("refs/tags"); + if (defined @$taglist) { + git_header_div('tags'); + git_tags_body($taglist, 0, 15, + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "...")); + } + + my $headlist = git_read_refs("refs/heads"); + if (defined @$headlist) { + git_header_div('heads'); + git_heads_body($headlist, $head, 0, 15, + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "...")); + } + + git_footer_html(); +} + +sub git_tag { + my $head = git_read_head($project); + git_header_html(); + git_page_nav('','', $head,undef,$head); + my %tag = git_read_tag($hash); + git_header_div('commit', esc_html($tag{'name'}), $hash); + print "
\n" . + "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + "\n"; + if (defined($tag{'author'})) { + my %ad = date_str($tag{'epoch'}, $tag{'tz'}); + print "\n"; + print "\n"; + } + print "
object" . $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'object'}) . "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'type'}) . "
author" . esc_html($tag{'author'}) . "
" . $ad{'rfc2822'} . sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) . "
\n\n" . + "
\n"; + print "
"; + my $comment = $tag{'comment'}; + foreach my $line (@$comment) { + print esc_html($line) . "
\n"; + } + print "
\n"; + git_footer_html(); +} + +sub git_blame2 { + my $fd; + my $ftype; + die_error(undef, "Permission denied.") if (!git_get_project_config_bool ('blame')); + die_error('404 Not Found', "File name not defined") if (!$file_name); + $hash_base ||= git_read_head($project); + die_error(undef, "Reading commit failed") unless ($hash_base); + my %co = git_read_commit($hash_base) + or die_error(undef, "Reading commit failed"); + if (!defined $hash) { + $hash = git_get_hash_by_path($hash_base, $file_name, "blob") + or die_error(undef, "Error looking up file"); + } + $ftype = git_get_type($hash); + if ($ftype !~ "blob") { + die_error("400 Bad Request", "object is not a blob"); + } + open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base) + or die_error(undef, "Open git-blame failed."); + git_header_html(); + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); + git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_path($file_name, $ftype); + my @rev_color = (qw(light dark)); + my $num_colors = scalar(@rev_color); + my $current_color = 0; + my $last_rev; + print "
\n"; + print "\n"; + print "\n"; + while (<$fd>) { + /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; + my $full_rev = $1; + my $rev = substr($full_rev, 0, 8); + my $lineno = $2; + my $data = $3; + + if (!defined $last_rev) { + $last_rev = $full_rev; + } elsif ($last_rev ne $full_rev) { + $last_rev = $full_rev; + $current_color = ++$current_color % $num_colors; + } + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + } + print "
CommitLineData
" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$full_rev;f=$file_name")}, esc_html($rev)) . "" . esc_html($lineno) . "" . esc_html($data) . "
\n"; + print "
"; + close $fd or print "Reading blob failed\n"; + git_footer_html(); +} + +sub git_blame { + my $fd; + die_error('403 Permission denied', "Permission denied.") if (!git_get_project_config_bool ('blame')); + die_error('404 Not Found', "What file will it be, master?") if (!$file_name); + $hash_base ||= git_read_head($project); + die_error(undef, "Reading commit failed.") unless ($hash_base); + my %co = git_read_commit($hash_base) + or die_error(undef, "Reading commit failed."); + if (!defined $hash) { + $hash = git_get_hash_by_path($hash_base, $file_name, "blob") + or die_error(undef, "Error lookup file."); + } + open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base) + or die_error(undef, "Open git-annotate failed."); + git_header_html(); + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); + git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_path($file_name, 'blob'); + print "
\n"; + print < + + Commit + Age + Author + Line + Data + +HTML + my @line_class = (qw(light dark)); + my $line_class_len = scalar (@line_class); + my $line_class_num = $#line_class; + while (my $line = <$fd>) { + my $long_rev; + my $short_rev; + my $author; + my $time; + my $lineno; + my $data; + my $age; + my $age_str; + my $age_class; + + chomp $line; + $line_class_num = ($line_class_num + 1) % $line_class_len; + + if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) \+\d\d\d\d\t(\d+)\)(.*)$/) { + $long_rev = $1; + $author = $2; + $time = $3; + $lineno = $4; + $data = $5; + } else { + print qq( Unable to parse: $line\n); + next; + } + $short_rev = substr ($long_rev, 0, 8); + $age = time () - $time; + $age_str = age_string ($age); + $age_str =~ s/ / /g; + $age_class = age_class($age); + $author = esc_html ($author); + $author =~ s/ / /g; + # escape tabs + while ((my $pos = index($data, "\t")) != -1) { + if (my $count = (8 - ($pos % 8))) { + my $spaces = ' ' x $count; + $data =~ s/\t/$spaces/; + } + } + $data = esc_html ($data); + + print < + $short_rev.. + $age_str + $author + $lineno + $data + +HTML + } # while (my $line = <$fd>) + print "\n\n"; + close $fd or print "Reading blob failed.\n"; + print "
"; + git_footer_html(); +} + +sub git_tags { + my $head = git_read_head($project); + git_header_html(); + git_page_nav('','', $head,undef,$head); + git_header_div('summary', $project); + + my $taglist = git_read_refs("refs/tags"); + if (defined @$taglist) { + git_tags_body($taglist); + } + git_footer_html(); +} + +sub git_heads { + my $head = git_read_head($project); + git_header_html(); + git_page_nav('','', $head,undef,$head); + git_header_div('summary', $project); + + my $taglist = git_read_refs("refs/heads"); + my $alternate = 0; + if (defined @$taglist) { + git_heads_body($taglist, $head); + } + git_footer_html(); +} + +sub git_blob_plain { + if (!defined $hash) { + if (defined $file_name) { + my $base = $hash_base || git_read_head($project); + $hash = git_get_hash_by_path($base, $file_name, "blob") + or die_error(undef, "Error lookup file."); + } else { + die_error(undef, "No file name defined."); + } + } + my $type = shift; + open my $fd, "-|", $GIT, "cat-file", "blob", $hash + or die_error("Couldn't cat $file_name, $hash"); + + $type ||= git_blob_plain_mimetype($fd, $file_name); + + # save as filename, even when no $file_name is given + my $save_as = "$hash"; + if (defined $file_name) { + $save_as = $file_name; + } elsif ($type =~ m/^text\//) { + $save_as .= '.txt'; + } + + print $cgi->header(-type => "$type", '-content-disposition' => "inline; filename=\"$save_as\""); + undef $/; + binmode STDOUT, ':raw'; + print <$fd>; + binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi + $/ = "\n"; + close $fd; +} + +sub git_blob { + if (!defined $hash) { + if (defined $file_name) { + my $base = $hash_base || git_read_head($project); + $hash = git_get_hash_by_path($base, $file_name, "blob") + or die_error(undef, "Error lookup file."); + } else { + die_error(undef, "No file name defined."); + } + } + my $have_blame = git_get_project_config_bool ('blame'); + open my $fd, "-|", $GIT, "cat-file", "blob", $hash + or die_error(undef, "Couldn't cat $file_name, $hash."); + my $mimetype = git_blob_plain_mimetype($fd, $file_name); + if ($mimetype !~ m/^text\//) { + close $fd; + return git_blob_plain($mimetype); + } + git_header_html(); + my $formats_nav = ''; + if (defined $hash_base && (my %co = git_read_commit($hash_base))) { + if (defined $file_name) { + if ($have_blame) { + $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | "; + } + $formats_nav .= + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head"); + } else { + $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain"); + } + git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_header_div('commit', esc_html($co{'title'}), $hash_base); + } else { + print "
\n" . + "

\n" . + "
$hash
\n"; + } + git_print_page_path($file_name, "blob"); + print "
\n"; + my $nr; + while (my $line = <$fd>) { + chomp $line; + $nr++; + while ((my $pos = index($line, "\t")) != -1) { + if (my $count = (8 - ($pos % 8))) { + my $spaces = ' ' x $count; + $line =~ s/\t/$spaces/; + } + } + printf "
%4i %s
\n", $nr, $nr, $nr, esc_html($line); + } + close $fd or print "Reading blob failed.\n"; + print "
"; + git_footer_html(); +} + +sub git_tree { + if (!defined $hash) { + $hash = git_read_head($project); + if (defined $file_name) { + my $base = $hash_base || $hash; + $hash = git_get_hash_by_path($base, $file_name, "tree"); + } + if (!defined $hash_base) { + $hash_base = $hash; + } + } + $/ = "\0"; + open my $fd, "-|", $GIT, "ls-tree", '-z', $hash + or die_error(undef, "Open git-ls-tree failed."); + my @entries = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading tree failed."); + $/ = "\n"; + + my $refs = read_info_ref(); + my $ref = git_get_referencing($refs, $hash_base); + git_header_html(); + my $base_key = ""; + my $base = ""; + if (defined $hash_base && (my %co = git_read_commit($hash_base))) { + $base_key = ";hb=$hash_base"; + git_page_nav('tree','', $hash_base); + git_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); + } else { + print "
\n"; + print "

\n"; + print "
$hash
\n"; + } + if (defined $file_name) { + $base = esc_html("$file_name/"); + } + git_print_page_path($file_name, 'tree'); + print "
\n"; + print "\n"; + my $alternate = 0; + foreach my $line (@entries) { + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + my $t_mode = $1; + my $t_type = $2; + my $t_hash = $3; + my $t_name = validate_input($4); + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n"; + if ($t_type eq "blob") { + print "\n" . + "\n"; + } elsif ($t_type eq "tree") { + print "\n" . + "\n"; + } + print "\n"; + } + print "
" . mode_str($t_mode) . "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name"), -class => "list"}, esc_html($t_name)) . + "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob") . +# " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$t_hash;f=$base$t_name")}, "raw") . + "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, esc_html($t_name)) . + "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, "tree") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash_base;f=$base$t_name")}, "history") . + "
\n" . + "
"; + git_footer_html(); +} + +sub git_log { + my $head = git_read_head($project); + if (!defined $hash) { + $hash = $head; + } + if (!defined $page) { + $page = 0; + } + my $refs = read_info_ref(); + + my $limit = sprintf("--max-count=%i", (100 * ($page+1))); + open my $fd, "-|", $GIT, "rev-list", $limit, $hash + or die_error(undef, "Open git-rev-list failed."); + my @revlist = map { chomp; $_ } <$fd>; + close $fd; + + my $paging_nav = git_get_paging_nav('log', $hash, $head, $page, $#revlist); + + git_header_html(); + git_page_nav('log','', $hash,undef,undef, $paging_nav); + + if (!@revlist) { + my %co = git_read_commit($hash); + + git_header_div('summary', $project); + print "
Last change $co{'age_string'}.

\n"; + } + for (my $i = ($page * 100); $i <= $#revlist; $i++) { + my $commit = $revlist[$i]; + my $ref = git_get_referencing($refs, $commit); + my %co = git_read_commit($commit); + next if !%co; + my %ad = date_str($co{'author_epoch'}); + git_header_div('commit', + "$co{'age_string'}" . + esc_html($co{'title'}) . $ref, + $commit); + print "
\n" . + "
\n" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . + "
\n" . + "
\n" . + "" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]
\n" . + "
\n" . + "
\n"; + my $comment = $co{'comment'}; + my $empty = 0; + foreach my $line (@$comment) { + if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { + next; + } + if ($line eq "") { + if ($empty) { + next; + } + $empty = 1; + } else { + $empty = 0; + } + print format_log_line_html($line) . "
\n"; + } + if (!$empty) { + print "
\n"; + } + print "
\n"; + } + git_footer_html(); +} + +sub git_commit { + my %co = git_read_commit($hash); + if (!%co) { + die_error(undef, "Unknown commit object."); + } + my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); + my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); + + my $parent = $co{'parent'}; + if (!defined $parent) { + $parent = "--root"; + } + open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash + or die_error(undef, "Open git-diff-tree failed."); + my @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading git-diff-tree failed."); + + # non-textual hash id's can be cached + my $expires; + if ($hash =~ m/^[0-9a-fA-F]{40}$/) { + $expires = "+1d"; + } + my $refs = read_info_ref(); + my $ref = git_get_referencing($refs, $co{'id'}); + my $formats_nav = ''; + if (defined $file_name && defined $co{'parent'}) { + my $parent = $co{'parent'}; + $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame"); + } + git_header_html(undef, $expires); + git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', + $hash, $co{'tree'}, $hash, + $formats_nav); + + if (defined $co{'parent'}) { + git_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); + } else { + git_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash); + } + print "
\n" . + "\n"; + print "\n". + "" . + "" . + "\n"; + print "\n"; + print "\n"; + print "\n"; + print "" . + "" . + "" . + "" . + "\n"; + my $parents = $co{'parents'}; + foreach my $par (@$parents) { + print "" . + "" . + "" . + "" . + "\n"; + } + print "
author" . esc_html($co{'author'}) . "
$ad{'rfc2822'}"; + if ($ad{'hour_local'} < 6) { + printf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); + } else { + printf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); + } + print "
committer" . esc_html($co{'committer'}) . "
$cd{'rfc2822'}" . sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) . "
commit$co{'id'}
tree" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), class => "list"}, $co{'tree'}) . + "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . + "
parent" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par"), class => "list"}, $par) . "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par")}, "commit") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash;hp=$par")}, "commitdiff") . + "
". + "
\n"; + print "
\n"; + my $comment = $co{'comment'}; + my $empty = 0; + my $signed = 0; + foreach my $line (@$comment) { + # print only one empty line + if ($line eq "") { + if ($empty || $signed) { + next; + } + $empty = 1; + } else { + $empty = 0; + } + if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { + $signed = 1; + print "" . esc_html($line) . "
\n"; + } else { + $signed = 0; + print format_log_line_html($line) . "
\n"; + } + } + print "
\n"; + print "
\n"; + if ($#difftree > 10) { + print(($#difftree + 1) . " files changed:\n"); + } + print "
\n"; + print "\n"; + my $alternate = 0; + foreach my $line (@difftree) { + # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' + # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' + if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { + next; + } + my $from_mode = $1; + my $to_mode = $2; + my $from_id = $3; + my $to_id = $4; + my $status = $5; + my $similarity = $6; + my $file = validate_input(unquote($7)); + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + if ($status eq "A") { + my $mode_chng = ""; + if (S_ISREG(oct $to_mode)) { + $mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777); + } + print "\n" . + "\n" . + "\n"; + } elsif ($status eq "D") { + print "\n" . + "\n" . + "\n" + } elsif ($status eq "M" || $status eq "T") { + my $mode_chnge = ""; + if ($from_mode != $to_mode) { + $mode_chnge = " [changed"; + if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) { + $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode); + } + if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) { + if (S_ISREG($from_mode) && S_ISREG($to_mode)) { + $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777); + } elsif (S_ISREG($to_mode)) { + $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777); + } + } + $mode_chnge .= "]\n"; + } + print "\n" . + "\n" . + "\n"; + } elsif ($status eq "R") { + my ($from_file, $to_file) = split "\t", $file; + my $mode_chng = ""; + if ($from_mode != $to_mode) { + $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); + } + print "\n" . + "\n" . + "\n"; + } + print "\n"; + } + print "
" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "[new " . file_type($to_mode) . "$mode_chng]" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "[deleted " . file_type($from_mode). "]" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, "blob") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . + ""; + if ($to_id ne $from_id) { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); + } else { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); + } + print "$mode_chnge"; + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob"); + if ($to_id ne $from_id) { + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file")}, "diff"); + } + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . "\n"; + print "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file"), -class => "list"}, esc_html($to_file)) . "[moved from " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$from_file"), -class => "list"}, esc_html($from_file)) . + " with " . (int $similarity) . "% similarity$mode_chng]" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob"); + if ($to_id ne $from_id) { + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file")}, "diff"); + } + print "
\n"; + git_footer_html(); +} + +sub git_blobdiff { + mkdir($git_temp, 0700); + git_header_html(); + if (defined $hash_base && (my %co = git_read_commit($hash_base))) { + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); + git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_header_div('commit', esc_html($co{'title'}), $hash_base); + } else { + print "
\n" . + "

\n" . + "
$hash vs $hash_parent
\n"; + } + git_print_page_path($file_name, "blob"); + print "
\n" . + "
blob:" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash_parent;hb=$hash_base;f=$file_name")}, $hash_parent) . + " -> blob:" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, $hash) . + "
\n"; + git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash); + print "
"; + git_footer_html(); +} + +sub git_blobdiff_plain { + mkdir($git_temp, 0700); + print $cgi->header(-type => "text/plain", -charset => 'utf-8'); + git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash, "plain"); +} + +sub git_commitdiff { + mkdir($git_temp, 0700); + my %co = git_read_commit($hash); + if (!%co) { + die_error(undef, "Unknown commit object."); + } + if (!defined $hash_parent) { + $hash_parent = $co{'parent'}; + } + open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash + or die_error(undef, "Open git-diff-tree failed."); + my @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading diff-tree failed."); + + # non-textual hash id's can be cached + my $expires; + if ($hash =~ m/^[0-9a-fA-F]{40}$/) { + $expires = "+1d"; + } + my $refs = read_info_ref(); + my $ref = git_get_referencing($refs, $co{'id'}); + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); + git_header_html(undef, $expires); + git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); + git_header_div('commit', esc_html($co{'title'}) . $ref, $hash); + print "
\n"; + my $comment = $co{'comment'}; + my $empty = 0; + my $signed = 0; + my @log = @$comment; + # remove first and empty lines after that + shift @log; + while (defined $log[0] && $log[0] eq "") { + shift @log; + } + foreach my $line (@log) { + if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { + next; + } + if ($line eq "") { + if ($empty) { + next; + } + $empty = 1; + } else { + $empty = 0; + } + print format_log_line_html($line) . "
\n"; + } + print "
\n"; + foreach my $line (@difftree) { + # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' + # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' + $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/; + my $from_mode = $1; + my $to_mode = $2; + my $from_id = $3; + my $to_id = $4; + my $status = $5; + my $file = validate_input(unquote($6)); + if ($status eq "A") { + print "
" . file_type($to_mode) . ":" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id) . "(new)" . + "
\n"; + git_diff_print(undef, "/dev/null", $to_id, "b/$file"); + } elsif ($status eq "D") { + print "
" . file_type($from_mode) . ":" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . "(deleted)" . + "
\n"; + git_diff_print($from_id, "a/$file", undef, "/dev/null"); + } elsif ($status eq "M") { + if ($from_id ne $to_id) { + print "
" . + file_type($from_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . + " -> " . + file_type($to_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id); + print "
\n"; + git_diff_print($from_id, "a/$file", $to_id, "b/$file"); + } + } + } + print "
\n" . + "
"; + git_footer_html(); +} + +sub git_commitdiff_plain { + mkdir($git_temp, 0700); + open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash + or die_error(undef, "Open git-diff-tree failed."); + my @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading diff-tree failed."); + + # try to figure out the next tag after this commit + my $tagname; + my $refs = read_info_ref("tags"); + open $fd, "-|", $GIT, "rev-list", "HEAD"; + my @commits = map { chomp; $_ } <$fd>; + close $fd; + foreach my $commit (@commits) { + if (defined $refs->{$commit}) { + $tagname = $refs->{$commit} + } + if ($commit eq $hash) { + last; + } + } + + print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\""); + my %co = git_read_commit($hash); + my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); + my $comment = $co{'comment'}; + print "From: $co{'author'}\n" . + "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n". + "Subject: $co{'title'}\n"; + if (defined $tagname) { + print "X-Git-Tag: $tagname\n"; + } + print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" . + "\n"; + + foreach my $line (@$comment) {; + print "$line\n"; + } + print "---\n\n"; + + foreach my $line (@difftree) { + $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/; + my $from_id = $3; + my $to_id = $4; + my $status = $5; + my $file = $6; + if ($status eq "A") { + git_diff_print(undef, "/dev/null", $to_id, "b/$file", "plain"); + } elsif ($status eq "D") { + git_diff_print($from_id, "a/$file", undef, "/dev/null", "plain"); + } elsif ($status eq "M") { + git_diff_print($from_id, "a/$file", $to_id, "b/$file", "plain"); + } + } +} + +sub git_history { + if (!defined $hash_base) { + $hash_base = git_read_head($project); + } + my $ftype; + my %co = git_read_commit($hash_base); + if (!%co) { + die_error(undef, "Unknown commit object."); + } + my $refs = read_info_ref(); + git_header_html(); + git_page_nav('','', $hash_base,$co{'tree'},$hash_base); + git_header_div('commit', esc_html($co{'title'}), $hash_base); + if (!defined $hash && defined $file_name) { + $hash = git_get_hash_by_path($hash_base, $file_name); + } + if (defined $hash) { + $ftype = git_get_type($hash); + } + git_print_page_path($file_name, $ftype); + + open my $fd, "-|", + $GIT, "rev-list", "--full-history", $hash_base, "--", $file_name; + print "\n"; + my $alternate = 0; + while (my $line = <$fd>) { + if ($line =~ m/^([0-9a-fA-F]{40})/){ + my $commit = $1; + my %co = git_read_commit($commit); + if (!%co) { + next; + } + my $ref = git_get_referencing($refs, $commit); + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n" . + "\n" . + "\n" . + "\n" . + "\n"; + } + } + print "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, "" . + esc_html(chop_str($co{'title'}, 50)) . "$ref") . "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$ftype;hb=$commit;f=$file_name")}, $ftype); + my $blob = git_get_hash_by_path($hash_base, $file_name); + my $blob_parent = git_get_hash_by_path($commit, $file_name); + if (defined $blob && defined $blob_parent && $blob ne $blob_parent) { + print " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$blob;hp=$blob_parent;hb=$commit;f=$file_name")}, + "diff to current"); + } + print "
\n"; + close $fd; + git_footer_html(); +} + +sub git_search { + if (!defined $searchtext) { + die_error("", "Text field empty."); + } + if (!defined $hash) { + $hash = git_read_head($project); + } + my %co = git_read_commit($hash); + if (!%co) { + die_error(undef, "Unknown commit object."); + } + # pickaxe may take all resources of your box and run for several minutes + # with every query - so decide by yourself how public you make this feature :) + my $commit_search = 1; + my $author_search = 0; + my $committer_search = 0; + my $pickaxe_search = 0; + if ($searchtext =~ s/^author\\://i) { + $author_search = 1; + } elsif ($searchtext =~ s/^committer\\://i) { + $committer_search = 1; + } elsif ($searchtext =~ s/^pickaxe\\://i) { + $commit_search = 0; + $pickaxe_search = 1; + } + git_header_html(); + git_page_nav('','', $hash,$co{'tree'},$hash); + git_header_div('commit', esc_html($co{'title'}), $hash); + + print "\n"; + my $alternate = 0; + if ($commit_search) { + $/ = "\0"; + open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash or next; + while (my $commit_text = <$fd>) { + if (!grep m/$searchtext/i, $commit_text) { + next; + } + if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) { + next; + } + if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) { + next; + } + my @commit_lines = split "\n", $commit_text; + my %co = git_read_commit(undef, \@commit_lines); + if (!%co) { + next; + } + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n" . + "\n" . + "\n" . + "\n" . + "\n"; + } + close $fd; + } + + if ($pickaxe_search) { + $/ = "\n"; + open my $fd, "-|", "$GIT rev-list $hash | $GIT diff-tree -r --stdin -S\'$searchtext\'"; + undef %co; + my @files; + while (my $line = <$fd>) { + if (%co && $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { + my %set; + $set{'file'} = $6; + $set{'from_id'} = $3; + $set{'to_id'} = $4; + $set{'id'} = $set{'to_id'}; + if ($set{'id'} =~ m/0{40}/) { + $set{'id'} = $set{'from_id'}; + } + if ($set{'id'} =~ m/0{40}/) { + next; + } + push @files, \%set; + } elsif ($line =~ m/^([0-9a-fA-F]{40})$/){ + if (%co) { + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n" . + "\n" . + "\n" . + "\n" . + "\n"; + } + %co = git_read_commit($1); + } + } + close $fd; + } + print "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "" . esc_html(chop_str($co{'title'}, 50)) . "
"); + my $comment = $co{'comment'}; + foreach my $line (@$comment) { + if ($line =~ m/^(.*)($searchtext)(.*)$/i) { + my $lead = esc_html($1) || ""; + $lead = chop_str($lead, 30, 10); + my $match = esc_html($2) || ""; + my $trail = esc_html($3) || ""; + $trail = chop_str($trail, 30, 10); + my $text = "$lead$match$trail"; + print chop_str($text, 80, 5) . "
\n"; + } + } + print "
" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree"); + print "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "" . + esc_html(chop_str($co{'title'}, 50)) . "
"); + while (my $setref = shift @files) { + my %set = %$setref; + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$set{'id'};hb=$co{'id'};f=$set{'file'}"), class => "list"}, + "" . esc_html($set{'file'}) . "") . + "
\n"; + } + print "
" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree"); + print "
\n"; + git_footer_html(); +} + +sub git_shortlog { + my $head = git_read_head($project); + if (!defined $hash) { + $hash = $head; + } + if (!defined $page) { + $page = 0; + } + my $refs = read_info_ref(); + + my $limit = sprintf("--max-count=%i", (100 * ($page+1))); + open my $fd, "-|", $GIT, "rev-list", $limit, $hash + or die_error(undef, "Open git-rev-list failed."); + my @revlist = map { chomp; $_ } <$fd>; + close $fd; + + my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist); + my $next_link = ''; + if ($#revlist >= (100 * ($page+1)-1)) { + $next_link = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), + -title => "Alt-n"}, "next"); + } + + + git_header_html(); + git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); + git_header_div('summary', $project); + + git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link); + + git_footer_html(); +} + +## ...................................................................... +## feeds (RSS, OPML) + +sub git_rss { + # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ + open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) + or die_error(undef, "Open git-rev-list failed."); + my @revlist = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading rev-list failed."); + print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print "\n". + "\n"; + print "\n"; + print "$project\n". + "" . esc_html("$my_url?p=$project;a=summary") . "\n". + "$project log\n". + "en\n"; + + for (my $i = 0; $i <= $#revlist; $i++) { + my $commit = $revlist[$i]; + my %co = git_read_commit($commit); + # we read 150, we always show 30 and the ones more recent than 48 hours + if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { + last; + } + my %cd = date_str($co{'committer_epoch'}); + open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next; + my @difftree = map { chomp; $_ } <$fd>; + close $fd or next; + print "\n" . + "" . + sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . + "\n" . + "" . esc_html($co{'author'}) . "\n" . + "$cd{'rfc2822'}\n" . + "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" . + "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" . + "" . esc_html($co{'title'}) . "\n" . + "" . + "\n"; + } + print "
\n"; + foreach my $line (@difftree) { + if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { + next; + } + my $file = validate_input(unquote($7)); + $file = decode("utf8", $file, Encode::FB_DEFAULT); + print "$file
\n"; + } + print "]]>\n" . + "
\n" . + "
\n"; + } + print "
"; +} + +sub git_opml { + my @list = git_read_projects(); + + print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print "\n". + "\n". + "". + " $site_name Git OPML Export\n". + "\n". + "\n". + "\n"; + + foreach my $pr (@list) { + my %proj = %$pr; + my $head = git_read_head($proj{'path'}); + if (!defined $head) { + next; + } + $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; + my %co = git_read_commit($head); + if (!%co) { + next; + } + + my $path = esc_html(chop_str($proj{'path'}, 25, 5)); + my $rss = "$my_url?p=$proj{'path'};a=rss"; + my $html = "$my_url?p=$proj{'path'};a=summary"; + print "\n"; + } + print "\n". + "\n". + "\n"; +} -- cgit v1.2.3 From 281f2f6b45bf62e5bc81a6b457f1f7d26c426ea3 Mon Sep 17 00:00:00 2001 From: Martin Waitz Date: Mon, 31 Jul 2006 00:38:39 +0200 Subject: gitweb: use out-of-line GIT logo. Use the normal web server instead of the CGI to provide the git logo, just like the gitweb.css. Signed-off-by: Martin Waitz Signed-off-by: Junio C Hamano --- Makefile | 2 ++ gitweb/README | 2 ++ gitweb/git-logo.png | Bin 0 -> 208 bytes gitweb/gitweb.perl | 29 ++++------------------------- 4 files changed, 8 insertions(+), 25 deletions(-) create mode 100644 gitweb/git-logo.png diff --git a/Makefile b/Makefile index fb2b28868a..ac5db1ef4c 100644 --- a/Makefile +++ b/Makefile @@ -122,6 +122,7 @@ GITWEB_PROJECTROOT = /pub/git GITWEB_LIST = GITWEB_HOMETEXT = indextext.html GITWEB_CSS = gitweb.css +GITWEB_LOGO = git-logo.png export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR @@ -589,6 +590,7 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl -e 's|@@GITWEB_LIST@@|$(GITWEB_LIST)|g' \ -e 's|@@GITWEB_HOMETEXT@@|$(GITWEB_HOMETEXT)|g' \ -e 's|@@GITWEB_CSS@@|$(GITWEB_CSS)|g' \ + -e 's|@@GITWEB_LOGO@@|$(GITWEB_LOGO)|g' \ $< >$@+ chmod +x $@+ mv $@+ $@ diff --git a/gitweb/README b/gitweb/README index ed939e2fb5..1b2180c731 100644 --- a/gitweb/README +++ b/gitweb/README @@ -21,6 +21,8 @@ You can specify the following configuration variables when building GIT: overview page. * GITWEB_CSS Points to the location where you put gitweb.css on your web server. + * GITWEB_LOGO + Points to the location where you put git-logo.png on your web server. Any comment/question/concern to: Kay Sievers diff --git a/gitweb/git-logo.png b/gitweb/git-logo.png new file mode 100644 index 0000000000..16ae8d5382 Binary files /dev/null and b/gitweb/git-logo.png differ diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6e4261d5f2..1db1414a0a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -52,6 +52,8 @@ our $home_text = "@@GITWEB_HOMETEXT@@"; # URI of default stylesheet our $stylesheet = "@@GITWEB_CSS@@"; +# URI of GIT logo +our $logo = "@@GITWEB_LOGO@@"; # source of projects list our $projects_list = "@@GITWEB_LIST@@" || "$projectroot"; @@ -71,10 +73,7 @@ if (defined $action) { undef $action; die_error(undef, "Invalid action parameter."); } - if ($action eq "git-logo.png") { - git_logo(); - exit; - } elsif ($action eq "opml") { + if ($action eq "opml") { git_opml(); exit; } @@ -873,7 +872,7 @@ $rss_link EOF print "
\n" . "" . - "\"git\"" . + "\"git\"" . "\n"; print $cgi->a({-href => esc_param($home_link)}, "projects") . " / "; if (defined $project) { @@ -1267,26 +1266,6 @@ sub git_diff_print { ## ====================================================================== ## actions -# git-logo (cached in browser for one day) -sub git_logo { - binmode STDOUT, ':raw'; - print $cgi->header(-type => 'image/png', -expires => '+1d'); - # cat git-logo.png | hexdump -e '16/1 " %02x" "\n"' | sed 's/ /\\x/g' - print "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" . - "\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" . - "\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" . - "\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" . - "\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" . - "\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" . - "\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" . - "\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" . - "\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" . - "\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" . - "\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" . - "\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" . - "\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; -} - sub git_project_list { my $order = $cgi->param('o'); if (defined $order && $order !~ m/project|descr|owner|age/) { -- cgit v1.2.3 From ee0d3234b1e4e69819635d4fc9e9c37933c0ea82 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 1 Aug 2006 15:56:28 -0700 Subject: gitweb/README: do not bug Kay with gitweb questions anymore Signed-off-by: Junio C Hamano --- gitweb/README | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gitweb/README b/gitweb/README index 1b2180c731..b91d42a909 100644 --- a/gitweb/README +++ b/gitweb/README @@ -24,5 +24,9 @@ You can specify the following configuration variables when building GIT: * GITWEB_LOGO Points to the location where you put git-logo.png on your web server. -Any comment/question/concern to: +Originally written by: Kay Sievers + +Any comment/question/concern to: + Git mailing list + -- cgit v1.2.3 From 22987ae0bc354bb2277c068ce15e9887f7cf0626 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 1 Aug 2006 16:23:47 -0700 Subject: Makefile: gitweb/gitweb.cgi is now generated. Signed-off-by: Junio C Hamano --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index ac5db1ef4c..6624c7501e 100644 --- a/Makefile +++ b/Makefile @@ -804,6 +804,7 @@ clean: rm -rf $(GIT_TARNAME) .doc-tmp-dir rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz rm -f $(htmldocs).tar.gz $(manpages).tar.gz + rm -f gitweb/gitweb.cgi $(MAKE) -C Documentation/ clean $(MAKE) -C templates clean $(MAKE) -C t/ clean -- cgit v1.2.3 From da7bad50ed0816cf2ee7f558ed154a7c67fb546d Mon Sep 17 00:00:00 2001 From: Ramsay Allan Jones Date: Sun, 30 Jul 2006 16:52:09 +0100 Subject: Fix header breakage with _XOPEN_SOURCE. convert-objects.c sets _XOPEN_SOURCE and _XOPEN_SOURCE_EXTENDED before including , in order to get the declaration of strptime(). This leads to breakage in cache.h, due to S_ISLNK and S_IFLNK no longer being defined by . These definitions are protected by the __USE_BSD symbol, which is not set when _XOPEN_SOURCE is set. Moving the #defines and #include below all other #includes does not fix the problem, however, since now _USE_XOPEN, which protects the declaration of strptime(), is now not defined (don't ask!). The fix is to #define _GNU_SOURCE, which enables the definition of practically everything. Signed-off-by: Ramsay Allan Jones Signed-off-by: Junio C Hamano --- convert-objects.c | 1 + 1 file changed, 1 insertion(+) diff --git a/convert-objects.c b/convert-objects.c index ebea8e472b..168771ed85 100644 --- a/convert-objects.c +++ b/convert-objects.c @@ -1,5 +1,6 @@ #define _XOPEN_SOURCE 500 /* glibc2 and AIX 5.3L need this */ #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */ +#define _GNU_SOURCE #include #include "cache.h" #include "blob.h" -- cgit v1.2.3 From c8d138a8c004ebce6ef840cfcc7c47227c2d16ba Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 2 Aug 2006 15:23:34 -0400 Subject: gitweb: optionally read config from GITWEB_CONFIG Configuration will first be taken from variables inside the gitweb.cgi script, which in turn come from the Makefile. Afterwards, the contents of GITWEB_CONFIG are read, overriding the builtin defaults. This should eliminate the need for editing the gitweb script at all. Users should edit the Makefile and/or add a config file. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Makefile | 2 ++ gitweb/gitweb.perl | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 6624c7501e..a2b4acaaae 100644 --- a/Makefile +++ b/Makefile @@ -117,6 +117,7 @@ GIT_PYTHON_DIR = $(prefix)/share/git-core/python # DESTDIR= # default configuration for gitweb +GITWEB_CONFIG = gitweb_config.perl GITWEB_SITENAME = GITWEB_PROJECTROOT = /pub/git GITWEB_LIST = @@ -585,6 +586,7 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ -e 's|@@GIT_VERSION@@|$(GIT_VERSION)|g' \ -e 's|@@GIT_BINDIR@@|$(bindir)|g' \ + -e 's|@@GITWEB_CONFIG@@|$(GITWEB_CONFIG)|g' \ -e 's|@@GITWEB_SITENAME@@|$(GITWEB_SITENAME)|g' \ -e 's|@@GITWEB_PROJECTROOT@@|$(GITWEB_PROJECTROOT)|g' \ -e 's|@@GITWEB_LIST@@|$(GITWEB_LIST)|g' \ diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1db1414a0a..d5b2de8b3a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -31,14 +31,8 @@ our $GIT = "@@GIT_BINDIR@@/git"; #our $projectroot = "/pub/scm"; our $projectroot = "@@GITWEB_PROJECTROOT@@"; -# version of the core git binary -our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; - # location for temporary files needed for diffs our $git_temp = "/tmp/gitweb"; -if (! -d $git_temp) { - mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp"); -} # target of the home link on top of all pages our $home_link = $my_uri; @@ -56,7 +50,7 @@ our $stylesheet = "@@GITWEB_CSS@@"; our $logo = "@@GITWEB_LOGO@@"; # source of projects list -our $projects_list = "@@GITWEB_LIST@@" || "$projectroot"; +our $projects_list = "@@GITWEB_LIST@@"; # default blob_plain mimetype and default charset for text/plain blob our $default_blob_plain_mimetype = 'text/plain'; @@ -66,6 +60,17 @@ our $default_text_plain_charset = undef; # (relative to the current git repository) our $mimetypes_file = undef; +our $GITWEB_CONFIG = "@@GITWEB_CONFIG@@"; +require $GITWEB_CONFIG if -e $GITWEB_CONFIG; + +# version of the core git binary +our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; + +$projects_list ||= $projectroot; +if (! -d $git_temp) { + mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp"); +} + # input validation and dispatch our $action = $cgi->param('a'); if (defined $action) { -- cgit v1.2.3 From bb55f77fcd0748e0faf152a565eef65b2066cfb4 Mon Sep 17 00:00:00 2001 From: Matthias Lederhofer Date: Wed, 2 Aug 2006 22:29:36 +0200 Subject: gitweb: require $ENV{'GITWEB_CONFIG'} With this patch it is possible to use gitweb.perl for developing by loading the configuration from $GITWEB_CONFIG. This might also be useful for normal usage of gitweb. Example: % cat cfg $GIT = '/usr/bin/git'; $projectroot = '/home/matled/src/git'; $projects_list = '/home/matled/src/git/git/gitweb/list'; % cat run #!/bin/sh export GATEWAY_INTERFACE="CGI/1.1" export HTTP_ACCEPT="*/*" export REQUEST_METHOD="GET" export GITWEB_CONFIG='./cfg' export QUERY_STRING=""$1"" exec ./gitweb.perl % time ./run p=git/.git > /dev/null This makes it easy to check for warnings and do performance tests after changes, you can also pipe this to lynx -dump -force-html /dev/stdin to get more than just html. This also documents the original patch adding require $GITWEB_CONFIG. Signed-off-by: Matthias Lederhofer Signed-off-by: Junio C Hamano --- gitweb/README | 5 +++++ gitweb/gitweb.perl | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/gitweb/README b/gitweb/README index b91d42a909..27c6dac143 100644 --- a/gitweb/README +++ b/gitweb/README @@ -23,6 +23,11 @@ You can specify the following configuration variables when building GIT: Points to the location where you put gitweb.css on your web server. * GITWEB_LOGO Points to the location where you put git-logo.png on your web server. + * GITWEB_CONFIG + This file will be loaded using 'require'. If the environment + $GITWEB_CONFIG is set when gitweb.cgi is executed the file in the + environment variable will be loaded instead of the file + specified when gitweb.cgi was created. Originally written by: Kay Sievers diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index d5b2de8b3a..b5548ec8f1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -60,7 +60,7 @@ our $default_text_plain_charset = undef; # (relative to the current git repository) our $mimetypes_file = undef; -our $GITWEB_CONFIG = "@@GITWEB_CONFIG@@"; +our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "@@GITWEB_CONFIG@@"; require $GITWEB_CONFIG if -e $GITWEB_CONFIG; # version of the core git binary -- cgit v1.2.3 From 06c084d28b150ac1e2ed0c6f3ce7db6ca42dfc67 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 2 Aug 2006 13:50:20 -0700 Subject: gitweb: do not use @@FOO@@ for replaced tokens This makes it easier to run gitweb/gitweb.perl without token substitution. Using @@ makes Perl emit "unintended interpolation" warnings. Signed-off-by: Junio C Hamano --- Makefile | 18 +++++++++--------- gitweb/gitweb.perl | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index a2b4acaaae..3816ef7971 100644 --- a/Makefile +++ b/Makefile @@ -584,15 +584,15 @@ git-status: git-commit gitweb/gitweb.cgi: gitweb/gitweb.perl rm -f $@ $@+ sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ - -e 's|@@GIT_VERSION@@|$(GIT_VERSION)|g' \ - -e 's|@@GIT_BINDIR@@|$(bindir)|g' \ - -e 's|@@GITWEB_CONFIG@@|$(GITWEB_CONFIG)|g' \ - -e 's|@@GITWEB_SITENAME@@|$(GITWEB_SITENAME)|g' \ - -e 's|@@GITWEB_PROJECTROOT@@|$(GITWEB_PROJECTROOT)|g' \ - -e 's|@@GITWEB_LIST@@|$(GITWEB_LIST)|g' \ - -e 's|@@GITWEB_HOMETEXT@@|$(GITWEB_HOMETEXT)|g' \ - -e 's|@@GITWEB_CSS@@|$(GITWEB_CSS)|g' \ - -e 's|@@GITWEB_LOGO@@|$(GITWEB_LOGO)|g' \ + -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ + -e 's|++GIT_BINDIR++|$(bindir)|g' \ + -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \ + -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ + -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ + -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \ + -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \ + -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ + -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ $< >$@+ chmod +x $@+ mv $@+ $@ diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b5548ec8f1..58eb5b190f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -18,18 +18,18 @@ use File::Find qw(); binmode STDOUT, ':utf8'; our $cgi = new CGI; -our $version = "@@GIT_VERSION@@"; +our $version = "++GIT_VERSION++"; our $my_url = $cgi->url(); our $my_uri = $cgi->url(-absolute => 1); our $rss_link = ""; # core git executable to use # this can just be "git" if your webserver has a sensible PATH -our $GIT = "@@GIT_BINDIR@@/git"; +our $GIT = "++GIT_BINDIR++/git"; # absolute fs-path which will be prepended to the project path #our $projectroot = "/pub/scm"; -our $projectroot = "@@GITWEB_PROJECTROOT@@"; +our $projectroot = "++GITWEB_PROJECTROOT++"; # location for temporary files needed for diffs our $git_temp = "/tmp/gitweb"; @@ -39,18 +39,18 @@ our $home_link = $my_uri; # name of your site or organization to appear in page titles # replace this with something more descriptive for clearer bookmarks -our $site_name = "@@GITWEB_SITENAME@@" || $ENV{'SERVER_NAME'} || "Untitled"; +our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled"; # html text to include at home page -our $home_text = "@@GITWEB_HOMETEXT@@"; +our $home_text = "++GITWEB_HOMETEXT++"; # URI of default stylesheet -our $stylesheet = "@@GITWEB_CSS@@"; +our $stylesheet = "++GITWEB_CSS++"; # URI of GIT logo -our $logo = "@@GITWEB_LOGO@@"; +our $logo = "++GITWEB_LOGO++"; # source of projects list -our $projects_list = "@@GITWEB_LIST@@"; +our $projects_list = "++GITWEB_LIST++"; # default blob_plain mimetype and default charset for text/plain blob our $default_blob_plain_mimetype = 'text/plain'; @@ -60,7 +60,7 @@ our $default_text_plain_charset = undef; # (relative to the current git repository) our $mimetypes_file = undef; -our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "@@GITWEB_CONFIG@@"; +our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; require $GITWEB_CONFIG if -e $GITWEB_CONFIG; # version of the core git binary -- cgit v1.2.3 From 657b062dbab2e17c750050d76de3e76ea28e603e Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 3 Aug 2006 00:38:11 +0200 Subject: autoconf: Add support for setting SHELL_PATH and PERL_PATH This patch adds support for setting SHELL_PATH and PERL_PATH to autoconf generated ./configure script via --with-shell=PATH and --with-perl=PATH options. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- configure.ac | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index c1f7751e6f..1669e40e26 100644 --- a/configure.ac +++ b/configure.ac @@ -19,17 +19,47 @@ echo "# ${config_append}. Generated by configure." > "${config_append}" # Append LINE to file ${config_append} AC_DEFUN([GIT_CONF_APPEND_LINE], [echo "$1" >> "${config_append}"])# GIT_CONF_APPEND_LINE +# +# GIT_ARG_SET_PATH(PROGRAM) +# ------------------------- +# Provide --with-PROGRAM=PATH option to set PATH to PROGRAM +AC_DEFUN([GIT_ARG_SET_PATH], +[AC_ARG_WITH([$1], + [AS_HELP_STRING([--with-$1=PATH], + [provide PATH to $1])], + [GIT_CONF_APPEND_PATH($1)],[]) +])# GIT_ARG_SET_PATH +# +# GIT_CONF_APPEND_PATH(PROGRAM) +# ------------------------------ +# Parse --with-PROGRAM=PATH option to set PROGRAM_PATH=PATH +# Used by GIT_ARG_SET_PATH(PROGRAM) +AC_DEFUN([GIT_CONF_APPEND_PATH], +[PROGRAM=m4_toupper($1); \ +if test "$withval" = "no"; then \ + AC_MSG_WARN([You cannot use git without $1]); \ +else \ + if test "$withval" = "yes"; then \ + AC_MSG_WARN([You should provide path for --with-$1=PATH]); \ + else \ + GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval); \ + fi; \ +fi; \ +]) # GIT_CONF_APPEND_PATH ## Checks for programs. AC_MSG_NOTICE([CHECKS for programs]) # +GIT_ARG_SET_PATH(shell) +GIT_ARG_SET_PATH(perl) +# +# Define NO_PYTHON if you want to lose all benefits of the recursive merge. +# Define PYTHON_PATH to provide path to Python. AC_PROG_CC #AC_PROG_INSTALL # needs install-sh or install.sh in sources AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) -# -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. ## Checks for libraries. @@ -127,6 +157,7 @@ AC_CHECK_FUNC(setenv,[], ## Site configuration ## --with-PACKAGE[=ARG] and --without-PACKAGE +# # Define NO_SVN_TESTS if you want to skip time-consuming SVN interopability # tests. These tests take up a significant amount of the total test time # but are not needed unless you plan to talk to SVN repos. -- cgit v1.2.3 From c4b1b1401cedefe480e32ceccd4ed678f9409e89 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 3 Aug 2006 00:38:56 +0200 Subject: autoconf: Move site configuration section earlier in configure.ac Move site configuration section, i.e. --with-PACKAGE[=ARG] and --without-PACKAGE, --enable-FEATURE[=ARG] and --disable-FEATURE options to the beginning of configure.ac file, just after definitions of macros. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- configure.ac | 100 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/configure.ac b/configure.ac index 1669e40e26..cfd1d4a0bd 100644 --- a/configure.ac +++ b/configure.ac @@ -48,6 +48,56 @@ fi; \ ]) # GIT_CONF_APPEND_PATH +## Site configuration +## --with-PACKAGE[=ARG] and --without-PACKAGE +# +# Define NO_SVN_TESTS if you want to skip time-consuming SVN interopability +# tests. These tests take up a significant amount of the total test time +# but are not needed unless you plan to talk to SVN repos. +# +# Define MOZILLA_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast +# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default +# choice) has very fast version optimized for i586. +# +# Define PPC_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine optimized for PowerPC. +# +# Define ARM_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine optimized for ARM. +# +# Define NO_OPENSSL environment variable if you do not have OpenSSL. +# This also implies MOZILLA_SHA1. +# +# Define NO_CURL if you do not have curl installed. git-http-pull and +# git-http-push are not built, and you cannot use http:// and https:// +# transports. +# +# Define CURLDIR=/foo/bar if your curl header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +# +# Define NO_EXPAT if you do not have expat installed. git-http-push is +# not built, and you cannot push using http:// and https:// transports. +# +# Define NO_MMAP if you want to avoid mmap. +# +# Define NO_PYTHON if you want to loose all benefits of the recursive merge. +# +## --enable-FEATURE[=ARG] and --disable-FEATURE +# Define COLLISION_CHECK below if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# sufficient guarantee that no collisions between objects will ever happen. +# +# Define USE_NSEC below if you want git to care about sub-second file mtimes +# and ctimes. Note that you need recent 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). +# +# Define USE_STDEV below if you want git to care about the underlying device +# change being considered an inode change from the update-cache perspective. + + ## Checks for programs. AC_MSG_NOTICE([CHECKS for programs]) # @@ -155,56 +205,6 @@ AC_CHECK_FUNC(setenv,[], # a missing newline at the end of the file. -## Site configuration -## --with-PACKAGE[=ARG] and --without-PACKAGE -# -# Define NO_SVN_TESTS if you want to skip time-consuming SVN interopability -# tests. These tests take up a significant amount of the total test time -# but are not needed unless you plan to talk to SVN repos. -# -# Define MOZILLA_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast -# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default -# choice) has very fast version optimized for i586. -# -# Define PPC_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine optimized for PowerPC. -# -# Define ARM_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine optimized for ARM. -# -# Define NO_OPENSSL environment variable if you do not have OpenSSL. -# This also implies MOZILLA_SHA1. -# -# Define NO_CURL if you do not have curl installed. git-http-pull and -# git-http-push are not built, and you cannot use http:// and https:// -# transports. -# -# Define CURLDIR=/foo/bar if your curl header and library files are in -# /foo/bar/include and /foo/bar/lib directories. -# -# Define NO_EXPAT if you do not have expat installed. git-http-push is -# not built, and you cannot push using http:// and https:// transports. -# -# Define NO_MMAP if you want to avoid mmap. -# -# Define NO_PYTHON if you want to loose all benefits of the recursive merge. -# -## --enable-FEATURE[=ARG] and --disable-FEATURE -# Define COLLISION_CHECK below if you believe that SHA1's -# 1461501637330902918203684832716283019655932542976 hashes do not give you -# sufficient guarantee that no collisions between objects will ever happen. -# -# Define USE_NSEC below if you want git to care about sub-second file mtimes -# and ctimes. Note that you need recent 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). -# -# Define USE_STDEV below if you want git to care about the underlying device -# change being considered an inode change from the update-cache perspective. - - ## Output files AC_CONFIG_FILES(["${config_file}":"${config_in}":"${config_append}"]) AC_OUTPUT -- cgit v1.2.3 From eb858c56cb9dcced05d61d47c0351b4f8768d379 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 3 Aug 2006 00:40:40 +0200 Subject: autoconf: Add support for setting PYTHON_PATH or NO_PYTHON This patch adds support for setting either PYTHON_PATH or NO_PYTHON to autoconf generated ./configure script via --with-python=PATH (sets PYTHON_PATH) or --without-python (sets NO_PYTHON). Autodetect PYTHON_PATH via AC_PATH_PROGS. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- configure.ac | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/configure.ac b/configure.ac index cfd1d4a0bd..0a54b44939 100644 --- a/configure.ac +++ b/configure.ac @@ -103,6 +103,18 @@ AC_MSG_NOTICE([CHECKS for programs]) # GIT_ARG_SET_PATH(shell) GIT_ARG_SET_PATH(perl) +AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python]) +AS_HELP_STRING([--no-python], [don't use python scripts])], + [if test "$withval" = "no"; then \ + NO_PYTHON=YesPlease; \ + elif test "$withval" != "yes"; then \ + PYTHON_PATH=$withval; \ + fi; \ + ]) +AC_SUBST(NO_PYTHON) +AC_SUBST(PYTHON_PATH) + + # # Define NO_PYTHON if you want to lose all benefits of the recursive merge. # Define PYTHON_PATH to provide path to Python. @@ -110,6 +122,16 @@ AC_PROG_CC #AC_PROG_INSTALL # needs install-sh or install.sh in sources AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) +if test -z "$NO_PYTHON"; then + AC_PATH_PROGS(PYTHON_PATH, [python2.4 python2.3 python2 python]) + if test -n "$PYTHON_PATH"; then + GIT_CONF_APPEND_LINE([PYTHON_PATH=@PYTHON_PATH@]) + else + GIT_CONF_APPEND_LINE([NO_PYTHON=@NO_PYTHON@]) + fi +else + GIT_CONF_APPEND_LINE([NO_PYTHON=@NO_PYTHON@]) +fi ## Checks for libraries. -- cgit v1.2.3 From d6b64ed0f3549f56c4d948dc2c0f12abc52fd6c9 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 3 Aug 2006 17:24:35 +0200 Subject: Make git-name-rev a builtin Signed-off-by: Matthias Kestenholz Signed-off-by: Junio C Hamano --- Makefile | 7 +- builtin-name-rev.c | 256 +++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 1 + name-rev.c | 256 ----------------------------------------------------- 5 files changed, 262 insertions(+), 259 deletions(-) create mode 100644 builtin-name-rev.c delete mode 100644 name-rev.c diff --git a/Makefile b/Makefile index 700c77f564..132c9cf012 100644 --- a/Makefile +++ b/Makefile @@ -185,7 +185,7 @@ PROGRAMS = \ git-unpack-objects$X git-update-server-info$X \ git-upload-pack$X git-verify-pack$X \ git-symbolic-ref$X \ - git-name-rev$X git-pack-redundant$X git-var$X \ + git-pack-redundant$X git-var$X \ git-describe$X git-merge-tree$X git-blame$X git-imap-send$X BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \ @@ -198,7 +198,7 @@ BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \ git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \ git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \ git-fmt-merge-msg$X git-prune$X git-mv$X git-prune-packed$X \ - git-repo-config$X + git-repo-config$X git-name-rev$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -255,7 +255,8 @@ BUILTIN_OBJS = \ builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \ builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \ builtin-update-ref.o builtin-fmt-merge-msg.o builtin-prune.o \ - builtin-mv.o builtin-prune-packed.o builtin-repo-config.o + builtin-mv.o builtin-prune-packed.o builtin-repo-config.o \ + builtin-name-rev.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-name-rev.c b/builtin-name-rev.c new file mode 100644 index 0000000000..571bba4817 --- /dev/null +++ b/builtin-name-rev.c @@ -0,0 +1,256 @@ +#include +#include "builtin.h" +#include "cache.h" +#include "commit.h" +#include "tag.h" +#include "refs.h" + +static const char name_rev_usage[] = + "git-name-rev [--tags] ( --all | --stdin | committish [committish...] )\n"; + +typedef struct rev_name { + const char *tip_name; + int merge_traversals; + int generation; +} rev_name; + +static long cutoff = LONG_MAX; + +static void name_rev(struct commit *commit, + const char *tip_name, int merge_traversals, int generation, + int deref) +{ + struct rev_name *name = (struct rev_name *)commit->util; + struct commit_list *parents; + int parent_number = 1; + + if (!commit->object.parsed) + parse_commit(commit); + + if (commit->date < cutoff) + return; + + if (deref) { + char *new_name = xmalloc(strlen(tip_name)+3); + strcpy(new_name, tip_name); + strcat(new_name, "^0"); + tip_name = new_name; + + if (generation) + die("generation: %d, but deref?", generation); + } + + if (name == NULL) { + name = xmalloc(sizeof(rev_name)); + commit->util = name; + goto copy_data; + } else if (name->merge_traversals > merge_traversals || + (name->merge_traversals == merge_traversals && + name->generation > generation)) { +copy_data: + name->tip_name = tip_name; + name->merge_traversals = merge_traversals; + name->generation = generation; + } else + return; + + for (parents = commit->parents; + parents; + parents = parents->next, parent_number++) { + if (parent_number > 1) { + char *new_name = xmalloc(strlen(tip_name)+8); + + if (generation > 0) + sprintf(new_name, "%s~%d^%d", tip_name, + generation, parent_number); + else + sprintf(new_name, "%s^%d", tip_name, parent_number); + + name_rev(parents->item, new_name, + merge_traversals + 1 , 0, 0); + } else { + name_rev(parents->item, tip_name, merge_traversals, + generation + 1, 0); + } + } +} + +static int tags_only = 0; + +static int name_ref(const char *path, const unsigned char *sha1) +{ + struct object *o = parse_object(sha1); + int deref = 0; + + if (tags_only && strncmp(path, "refs/tags/", 10)) + return 0; + + while (o && o->type == OBJ_TAG) { + struct tag *t = (struct tag *) o; + if (!t->tagged) + break; /* broken repository */ + o = parse_object(t->tagged->sha1); + deref = 1; + } + if (o && o->type == OBJ_COMMIT) { + struct commit *commit = (struct commit *)o; + + if (!strncmp(path, "refs/heads/", 11)) + path = path + 11; + else if (!strncmp(path, "refs/", 5)) + path = path + 5; + + name_rev(commit, strdup(path), 0, 0, deref); + } + return 0; +} + +/* returns a static buffer */ +static const char* get_rev_name(struct object *o) +{ + static char buffer[1024]; + struct rev_name *n; + struct commit *c; + + if (o->type != OBJ_COMMIT) + return "undefined"; + c = (struct commit *) o; + n = c->util; + if (!n) + return "undefined"; + + if (!n->generation) + return n->tip_name; + + snprintf(buffer, sizeof(buffer), "%s~%d", n->tip_name, n->generation); + + return buffer; +} + +int cmd_name_rev(int argc, const char **argv, const char *prefix) +{ + struct object_array revs = { 0, 0, NULL }; + int as_is = 0, all = 0, transform_stdin = 0; + + git_config(git_default_config); + + if (argc < 2) + usage(name_rev_usage); + + for (--argc, ++argv; argc; --argc, ++argv) { + unsigned char sha1[20]; + struct object *o; + struct commit *commit; + + if (!as_is && (*argv)[0] == '-') { + if (!strcmp(*argv, "--")) { + as_is = 1; + continue; + } else if (!strcmp(*argv, "--tags")) { + tags_only = 1; + continue; + } else if (!strcmp(*argv, "--all")) { + if (argc > 1) + die("Specify either a list, or --all, not both!"); + all = 1; + cutoff = 0; + continue; + } else if (!strcmp(*argv, "--stdin")) { + if (argc > 1) + die("Specify either a list, or --stdin, not both!"); + transform_stdin = 1; + cutoff = 0; + continue; + } + usage(name_rev_usage); + } + + if (get_sha1(*argv, sha1)) { + fprintf(stderr, "Could not get sha1 for %s. Skipping.\n", + *argv); + continue; + } + + o = deref_tag(parse_object(sha1), *argv, 0); + if (!o || o->type != OBJ_COMMIT) { + fprintf(stderr, "Could not get commit for %s. Skipping.\n", + *argv); + continue; + } + + commit = (struct commit *)o; + + if (cutoff > commit->date) + cutoff = commit->date; + + add_object_array((struct object *)commit, *argv, &revs); + } + + for_each_ref(name_ref); + + if (transform_stdin) { + char buffer[2048]; + char *p, *p_start; + + while (!feof(stdin)) { + int forty = 0; + p = fgets(buffer, sizeof(buffer), stdin); + if (!p) + break; + + for (p_start = p; *p; p++) { +#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f')) + if (!ishex(*p)) + forty = 0; + else if (++forty == 40 && + !ishex(*(p+1))) { + unsigned char sha1[40]; + const char *name = "undefined"; + char c = *(p+1); + + forty = 0; + + *(p+1) = 0; + if (!get_sha1(p - 39, sha1)) { + struct object *o = + lookup_object(sha1); + if (o) + name = get_rev_name(o); + } + *(p+1) = c; + + if (!strcmp(name, "undefined")) + continue; + + fwrite(p_start, p - p_start + 1, 1, + stdout); + printf(" (%s)", name); + p_start = p + 1; + } + } + + /* flush */ + if (p_start != p) + fwrite(p_start, p - p_start, 1, stdout); + } + } else if (all) { + int i, max; + + max = get_max_object_index(); + for (i = 0; i < max; i++) { + struct object * obj = get_indexed_object(i); + if (!obj) + continue; + printf("%s %s\n", sha1_to_hex(obj->sha1), get_rev_name(obj)); + } + } else { + int i; + for (i = 0; i < revs.nr; i++) + printf("%s %s\n", + revs.objects[i].name, + get_rev_name(revs.objects[i].item)); + } + + return 0; +} + diff --git a/builtin.h b/builtin.h index 26ebcaf213..d1d9dc14f9 100644 --- a/builtin.h +++ b/builtin.h @@ -49,6 +49,7 @@ extern int cmd_update_ref(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_mv(int argc, const char **argv, const char *prefix); extern int cmd_repo_config(int argc, const char **argv, const char *prefix); +extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); diff --git a/git.c b/git.c index 6e72a893b7..501a7815fc 100644 --- a/git.c +++ b/git.c @@ -265,6 +265,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "mv", cmd_mv, NEEDS_PREFIX }, { "prune-packed", cmd_prune_packed, NEEDS_PREFIX }, { "repo-config", cmd_repo_config }, + { "name-rev", cmd_name_rev, NEEDS_PREFIX }, }; int i; diff --git a/name-rev.c b/name-rev.c deleted file mode 100644 index f92f14e32f..0000000000 --- a/name-rev.c +++ /dev/null @@ -1,256 +0,0 @@ -#include -#include "cache.h" -#include "commit.h" -#include "tag.h" -#include "refs.h" - -static const char name_rev_usage[] = - "git-name-rev [--tags] ( --all | --stdin | committish [committish...] )\n"; - -typedef struct rev_name { - const char *tip_name; - int merge_traversals; - int generation; -} rev_name; - -static long cutoff = LONG_MAX; - -static void name_rev(struct commit *commit, - const char *tip_name, int merge_traversals, int generation, - int deref) -{ - struct rev_name *name = (struct rev_name *)commit->util; - struct commit_list *parents; - int parent_number = 1; - - if (!commit->object.parsed) - parse_commit(commit); - - if (commit->date < cutoff) - return; - - if (deref) { - char *new_name = xmalloc(strlen(tip_name)+3); - strcpy(new_name, tip_name); - strcat(new_name, "^0"); - tip_name = new_name; - - if (generation) - die("generation: %d, but deref?", generation); - } - - if (name == NULL) { - name = xmalloc(sizeof(rev_name)); - commit->util = name; - goto copy_data; - } else if (name->merge_traversals > merge_traversals || - (name->merge_traversals == merge_traversals && - name->generation > generation)) { -copy_data: - name->tip_name = tip_name; - name->merge_traversals = merge_traversals; - name->generation = generation; - } else - return; - - for (parents = commit->parents; - parents; - parents = parents->next, parent_number++) { - if (parent_number > 1) { - char *new_name = xmalloc(strlen(tip_name)+8); - - if (generation > 0) - sprintf(new_name, "%s~%d^%d", tip_name, - generation, parent_number); - else - sprintf(new_name, "%s^%d", tip_name, parent_number); - - name_rev(parents->item, new_name, - merge_traversals + 1 , 0, 0); - } else { - name_rev(parents->item, tip_name, merge_traversals, - generation + 1, 0); - } - } -} - -static int tags_only = 0; - -static int name_ref(const char *path, const unsigned char *sha1) -{ - struct object *o = parse_object(sha1); - int deref = 0; - - if (tags_only && strncmp(path, "refs/tags/", 10)) - return 0; - - while (o && o->type == OBJ_TAG) { - struct tag *t = (struct tag *) o; - if (!t->tagged) - break; /* broken repository */ - o = parse_object(t->tagged->sha1); - deref = 1; - } - if (o && o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - - if (!strncmp(path, "refs/heads/", 11)) - path = path + 11; - else if (!strncmp(path, "refs/", 5)) - path = path + 5; - - name_rev(commit, strdup(path), 0, 0, deref); - } - return 0; -} - -/* returns a static buffer */ -static const char* get_rev_name(struct object *o) -{ - static char buffer[1024]; - struct rev_name *n; - struct commit *c; - - if (o->type != OBJ_COMMIT) - return "undefined"; - c = (struct commit *) o; - n = c->util; - if (!n) - return "undefined"; - - if (!n->generation) - return n->tip_name; - - snprintf(buffer, sizeof(buffer), "%s~%d", n->tip_name, n->generation); - - return buffer; -} - -int main(int argc, char **argv) -{ - struct object_array revs = { 0, 0, NULL }; - int as_is = 0, all = 0, transform_stdin = 0; - - setup_git_directory(); - git_config(git_default_config); - - if (argc < 2) - usage(name_rev_usage); - - for (--argc, ++argv; argc; --argc, ++argv) { - unsigned char sha1[20]; - struct object *o; - struct commit *commit; - - if (!as_is && (*argv)[0] == '-') { - if (!strcmp(*argv, "--")) { - as_is = 1; - continue; - } else if (!strcmp(*argv, "--tags")) { - tags_only = 1; - continue; - } else if (!strcmp(*argv, "--all")) { - if (argc > 1) - die("Specify either a list, or --all, not both!"); - all = 1; - cutoff = 0; - continue; - } else if (!strcmp(*argv, "--stdin")) { - if (argc > 1) - die("Specify either a list, or --stdin, not both!"); - transform_stdin = 1; - cutoff = 0; - continue; - } - usage(name_rev_usage); - } - - if (get_sha1(*argv, sha1)) { - fprintf(stderr, "Could not get sha1 for %s. Skipping.\n", - *argv); - continue; - } - - o = deref_tag(parse_object(sha1), *argv, 0); - if (!o || o->type != OBJ_COMMIT) { - fprintf(stderr, "Could not get commit for %s. Skipping.\n", - *argv); - continue; - } - - commit = (struct commit *)o; - - if (cutoff > commit->date) - cutoff = commit->date; - - add_object_array((struct object *)commit, *argv, &revs); - } - - for_each_ref(name_ref); - - if (transform_stdin) { - char buffer[2048]; - char *p, *p_start; - - while (!feof(stdin)) { - int forty = 0; - p = fgets(buffer, sizeof(buffer), stdin); - if (!p) - break; - - for (p_start = p; *p; p++) { -#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f')) - if (!ishex(*p)) - forty = 0; - else if (++forty == 40 && - !ishex(*(p+1))) { - unsigned char sha1[40]; - const char *name = "undefined"; - char c = *(p+1); - - forty = 0; - - *(p+1) = 0; - if (!get_sha1(p - 39, sha1)) { - struct object *o = - lookup_object(sha1); - if (o) - name = get_rev_name(o); - } - *(p+1) = c; - - if (!strcmp(name, "undefined")) - continue; - - fwrite(p_start, p - p_start + 1, 1, - stdout); - printf(" (%s)", name); - p_start = p + 1; - } - } - - /* flush */ - if (p_start != p) - fwrite(p_start, p - p_start, 1, stdout); - } - } else if (all) { - int i, max; - - max = get_max_object_index(); - for (i = 0; i < max; i++) { - struct object * obj = get_indexed_object(i); - if (!obj) - continue; - printf("%s %s\n", sha1_to_hex(obj->sha1), get_rev_name(obj)); - } - } else { - int i; - for (i = 0; i < revs.nr; i++) - printf("%s %s\n", - revs.objects[i].name, - get_rev_name(revs.objects[i].item)); - } - - return 0; -} - -- cgit v1.2.3 From 5d4a60033543e063bc9d77ca957de0187fb58fb3 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 3 Aug 2006 17:24:36 +0200 Subject: Make git-pack-objects a builtin Signed-off-by: Matthias Kestenholz Signed-off-by: Junio C Hamano --- Makefile | 6 +- builtin-pack-objects.c | 1376 ++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 1 + pack-objects.c | 1376 ------------------------------------------------ 5 files changed, 1381 insertions(+), 1379 deletions(-) create mode 100644 builtin-pack-objects.c delete mode 100644 pack-objects.c diff --git a/Makefile b/Makefile index 132c9cf012..4fd62bcf6d 100644 --- a/Makefile +++ b/Makefile @@ -177,7 +177,7 @@ PROGRAMS = \ git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ git-merge-base$X \ - git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \ + git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \ git-peek-remote$X git-receive-pack$X \ git-send-pack$X git-shell$X \ git-show-index$X git-ssh-fetch$X \ @@ -198,7 +198,7 @@ BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \ git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \ git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \ git-fmt-merge-msg$X git-prune$X git-mv$X git-prune-packed$X \ - git-repo-config$X git-name-rev$X + git-repo-config$X git-name-rev$X git-pack-objects$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -256,7 +256,7 @@ BUILTIN_OBJS = \ builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \ builtin-update-ref.o builtin-fmt-merge-msg.o builtin-prune.o \ builtin-mv.o builtin-prune-packed.o builtin-repo-config.o \ - builtin-name-rev.o + builtin-name-rev.o builtin-pack-objects.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c new file mode 100644 index 0000000000..2301cd5c0f --- /dev/null +++ b/builtin-pack-objects.c @@ -0,0 +1,1376 @@ +#include "builtin.h" +#include "cache.h" +#include "object.h" +#include "blob.h" +#include "commit.h" +#include "tag.h" +#include "tree.h" +#include "delta.h" +#include "pack.h" +#include "csum-file.h" +#include "tree-walk.h" +#include +#include + +static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list"; + +struct object_entry { + unsigned char sha1[20]; + unsigned long size; /* uncompressed size */ + unsigned long offset; /* offset into the final pack file; + * nonzero if already written. + */ + unsigned int depth; /* delta depth */ + unsigned int delta_limit; /* base adjustment for in-pack delta */ + unsigned int hash; /* name hint hash */ + enum object_type type; + enum object_type in_pack_type; /* could be delta */ + unsigned long delta_size; /* delta data size (uncompressed) */ + struct object_entry *delta; /* delta base object */ + struct packed_git *in_pack; /* already in pack */ + unsigned int in_pack_offset; + struct object_entry *delta_child; /* deltified objects who bases me */ + struct object_entry *delta_sibling; /* other deltified objects who + * uses the same base as me + */ + int preferred_base; /* we do not pack this, but is encouraged to + * be used as the base objectto delta huge + * objects against. + */ +}; + +/* + * Objects we are going to pack are collected in objects array (dynamically + * expanded). nr_objects & nr_alloc controls this array. They are stored + * in the order we see -- typically rev-list --objects order that gives us + * nice "minimum seek" order. + * + * sorted-by-sha ans sorted-by-type are arrays of pointers that point at + * elements in the objects array. The former is used to build the pack + * index (lists object names in the ascending order to help offset lookup), + * and the latter is used to group similar things together by try_delta() + * heuristics. + */ + +static unsigned char object_list_sha1[20]; +static int non_empty = 0; +static int no_reuse_delta = 0; +static int local = 0; +static int incremental = 0; +static struct object_entry **sorted_by_sha, **sorted_by_type; +static struct object_entry *objects = NULL; +static int nr_objects = 0, nr_alloc = 0, nr_result = 0; +static const char *base_name; +static unsigned char pack_file_sha1[20]; +static int progress = 1; +static volatile sig_atomic_t progress_update = 0; +static int window = 10; + +/* + * The object names in objects array are hashed with this hashtable, + * to help looking up the entry by object name. Binary search from + * sorted_by_sha is also possible but this was easier to code and faster. + * This hashtable is built after all the objects are seen. + */ +static int *object_ix = NULL; +static int object_ix_hashsz = 0; + +/* + * Pack index for existing packs give us easy access to the offsets into + * corresponding pack file where each object's data starts, but the entries + * do not store the size of the compressed representation (uncompressed + * size is easily available by examining the pack entry header). We build + * a hashtable of existing packs (pack_revindex), and keep reverse index + * here -- pack index file is sorted by object name mapping to offset; this + * pack_revindex[].revindex array is an ordered list of offsets, so if you + * know the offset of an object, next offset is where its packed + * representation ends. + */ +struct pack_revindex { + struct packed_git *p; + unsigned long *revindex; +} *pack_revindex = NULL; +static int pack_revindex_hashsz = 0; + +/* + * stats + */ +static int written = 0; +static int written_delta = 0; +static int reused = 0; +static int reused_delta = 0; + +static int pack_revindex_ix(struct packed_git *p) +{ + unsigned long ui = (unsigned long)p; + int i; + + ui = ui ^ (ui >> 16); /* defeat structure alignment */ + i = (int)(ui % pack_revindex_hashsz); + while (pack_revindex[i].p) { + if (pack_revindex[i].p == p) + return i; + if (++i == pack_revindex_hashsz) + i = 0; + } + return -1 - i; +} + +static void prepare_pack_ix(void) +{ + int num; + struct packed_git *p; + for (num = 0, p = packed_git; p; p = p->next) + num++; + if (!num) + return; + pack_revindex_hashsz = num * 11; + pack_revindex = xcalloc(sizeof(*pack_revindex), pack_revindex_hashsz); + for (p = packed_git; p; p = p->next) { + num = pack_revindex_ix(p); + num = - 1 - num; + pack_revindex[num].p = p; + } + /* revindex elements are lazily initialized */ +} + +static int cmp_offset(const void *a_, const void *b_) +{ + unsigned long a = *(unsigned long *) a_; + unsigned long b = *(unsigned long *) b_; + if (a < b) + return -1; + else if (a == b) + return 0; + else + return 1; +} + +/* + * Ordered list of offsets of objects in the pack. + */ +static void prepare_pack_revindex(struct pack_revindex *rix) +{ + struct packed_git *p = rix->p; + int num_ent = num_packed_objects(p); + int i; + void *index = p->index_base + 256; + + rix->revindex = xmalloc(sizeof(unsigned long) * (num_ent + 1)); + for (i = 0; i < num_ent; i++) { + unsigned int hl = *((unsigned int *)((char *) index + 24*i)); + rix->revindex[i] = ntohl(hl); + } + /* This knows the pack format -- the 20-byte trailer + * follows immediately after the last object data. + */ + rix->revindex[num_ent] = p->pack_size - 20; + qsort(rix->revindex, num_ent, sizeof(unsigned long), cmp_offset); +} + +static unsigned long find_packed_object_size(struct packed_git *p, + unsigned long ofs) +{ + int num; + int lo, hi; + struct pack_revindex *rix; + unsigned long *revindex; + num = pack_revindex_ix(p); + if (num < 0) + die("internal error: pack revindex uninitialized"); + rix = &pack_revindex[num]; + if (!rix->revindex) + prepare_pack_revindex(rix); + revindex = rix->revindex; + lo = 0; + hi = num_packed_objects(p) + 1; + do { + int mi = (lo + hi) / 2; + if (revindex[mi] == ofs) { + return revindex[mi+1] - ofs; + } + else if (ofs < revindex[mi]) + hi = mi; + else + lo = mi + 1; + } while (lo < hi); + die("internal error: pack revindex corrupt"); +} + +static void *delta_against(void *buf, unsigned long size, struct object_entry *entry) +{ + unsigned long othersize, delta_size; + char type[10]; + void *otherbuf = read_sha1_file(entry->delta->sha1, type, &othersize); + void *delta_buf; + + if (!otherbuf) + die("unable to read %s", sha1_to_hex(entry->delta->sha1)); + delta_buf = diff_delta(otherbuf, othersize, + buf, size, &delta_size, 0); + if (!delta_buf || delta_size != entry->delta_size) + die("delta size changed"); + free(buf); + free(otherbuf); + return delta_buf; +} + +/* + * The per-object header is a pretty dense thing, which is + * - first byte: low four bits are "size", then three bits of "type", + * and the high bit is "size continues". + * - each byte afterwards: low seven bits are size continuation, + * with the high bit being "size continues" + */ +static int encode_header(enum object_type type, unsigned long size, unsigned char *hdr) +{ + int n = 1; + unsigned char c; + + if (type < OBJ_COMMIT || type > OBJ_DELTA) + die("bad type %d", type); + + c = (type << 4) | (size & 15); + size >>= 4; + while (size) { + *hdr++ = c | 0x80; + c = size & 0x7f; + size >>= 7; + n++; + } + *hdr = c; + return n; +} + +static unsigned long write_object(struct sha1file *f, + struct object_entry *entry) +{ + unsigned long size; + char type[10]; + void *buf; + unsigned char header[10]; + unsigned hdrlen, datalen; + enum object_type obj_type; + int to_reuse = 0; + + if (entry->preferred_base) + return 0; + + obj_type = entry->type; + if (! entry->in_pack) + to_reuse = 0; /* can't reuse what we don't have */ + else if (obj_type == OBJ_DELTA) + to_reuse = 1; /* check_object() decided it for us */ + else if (obj_type != entry->in_pack_type) + to_reuse = 0; /* pack has delta which is unusable */ + else if (entry->delta) + to_reuse = 0; /* we want to pack afresh */ + else + to_reuse = 1; /* we have it in-pack undeltified, + * and we do not need to deltify it. + */ + + if (! to_reuse) { + buf = read_sha1_file(entry->sha1, type, &size); + if (!buf) + die("unable to read %s", sha1_to_hex(entry->sha1)); + if (size != entry->size) + die("object %s size inconsistency (%lu vs %lu)", + sha1_to_hex(entry->sha1), size, entry->size); + if (entry->delta) { + buf = delta_against(buf, size, entry); + size = entry->delta_size; + obj_type = OBJ_DELTA; + } + /* + * The object header is a byte of 'type' followed by zero or + * more bytes of length. For deltas, the 20 bytes of delta + * sha1 follows that. + */ + hdrlen = encode_header(obj_type, size, header); + sha1write(f, header, hdrlen); + + if (entry->delta) { + sha1write(f, entry->delta, 20); + hdrlen += 20; + } + datalen = sha1write_compressed(f, buf, size); + free(buf); + } + else { + struct packed_git *p = entry->in_pack; + use_packed_git(p); + + datalen = find_packed_object_size(p, entry->in_pack_offset); + buf = (char *) p->pack_base + entry->in_pack_offset; + sha1write(f, buf, datalen); + unuse_packed_git(p); + hdrlen = 0; /* not really */ + if (obj_type == OBJ_DELTA) + reused_delta++; + reused++; + } + if (obj_type == OBJ_DELTA) + written_delta++; + written++; + return hdrlen + datalen; +} + +static unsigned long write_one(struct sha1file *f, + struct object_entry *e, + unsigned long offset) +{ + if (e->offset) + /* offset starts from header size and cannot be zero + * if it is written already. + */ + return offset; + e->offset = offset; + offset += write_object(f, e); + /* if we are deltified, write out its base object. */ + if (e->delta) + offset = write_one(f, e->delta, offset); + return offset; +} + +static void write_pack_file(void) +{ + int i; + struct sha1file *f; + unsigned long offset; + struct pack_header hdr; + unsigned last_percent = 999; + int do_progress = 0; + + if (!base_name) + f = sha1fd(1, ""); + else { + f = sha1create("%s-%s.%s", base_name, + sha1_to_hex(object_list_sha1), "pack"); + do_progress = progress; + } + if (do_progress) + fprintf(stderr, "Writing %d objects.\n", nr_result); + + hdr.hdr_signature = htonl(PACK_SIGNATURE); + hdr.hdr_version = htonl(PACK_VERSION); + hdr.hdr_entries = htonl(nr_result); + sha1write(f, &hdr, sizeof(hdr)); + offset = sizeof(hdr); + if (!nr_result) + goto done; + for (i = 0; i < nr_objects; i++) { + offset = write_one(f, objects + i, offset); + if (do_progress) { + unsigned percent = written * 100 / nr_result; + if (progress_update || percent != last_percent) { + fprintf(stderr, "%4u%% (%u/%u) done\r", + percent, written, nr_result); + progress_update = 0; + last_percent = percent; + } + } + } + if (do_progress) + fputc('\n', stderr); + done: + sha1close(f, pack_file_sha1, 1); +} + +static void write_index_file(void) +{ + int i; + struct sha1file *f = sha1create("%s-%s.%s", base_name, + sha1_to_hex(object_list_sha1), "idx"); + struct object_entry **list = sorted_by_sha; + struct object_entry **last = list + nr_result; + unsigned int array[256]; + + /* + * Write the first-level table (the list is sorted, + * but we use a 256-entry lookup to be able to avoid + * having to do eight extra binary search iterations). + */ + for (i = 0; i < 256; i++) { + struct object_entry **next = list; + while (next < last) { + struct object_entry *entry = *next; + if (entry->sha1[0] != i) + break; + next++; + } + array[i] = htonl(next - sorted_by_sha); + list = next; + } + sha1write(f, array, 256 * sizeof(int)); + + /* + * Write the actual SHA1 entries.. + */ + list = sorted_by_sha; + for (i = 0; i < nr_result; i++) { + struct object_entry *entry = *list++; + unsigned int offset = htonl(entry->offset); + sha1write(f, &offset, 4); + sha1write(f, entry->sha1, 20); + } + sha1write(f, pack_file_sha1, 20); + sha1close(f, NULL, 1); +} + +static int locate_object_entry_hash(const unsigned char *sha1) +{ + int i; + unsigned int ui; + memcpy(&ui, sha1, sizeof(unsigned int)); + i = ui % object_ix_hashsz; + while (0 < object_ix[i]) { + if (!memcmp(sha1, objects[object_ix[i]-1].sha1, 20)) + return i; + if (++i == object_ix_hashsz) + i = 0; + } + return -1 - i; +} + +static struct object_entry *locate_object_entry(const unsigned char *sha1) +{ + int i; + + if (!object_ix_hashsz) + return NULL; + + i = locate_object_entry_hash(sha1); + if (0 <= i) + return &objects[object_ix[i]-1]; + return NULL; +} + +static void rehash_objects(void) +{ + int i; + struct object_entry *oe; + + object_ix_hashsz = nr_objects * 3; + if (object_ix_hashsz < 1024) + object_ix_hashsz = 1024; + object_ix = xrealloc(object_ix, sizeof(int) * object_ix_hashsz); + memset(object_ix, 0, sizeof(int) * object_ix_hashsz); + for (i = 0, oe = objects; i < nr_objects; i++, oe++) { + int ix = locate_object_entry_hash(oe->sha1); + if (0 <= ix) + continue; + ix = -1 - ix; + object_ix[ix] = i + 1; + } +} + +static unsigned name_hash(const char *name) +{ + unsigned char c; + unsigned hash = 0; + + /* + * This effectively just creates a sortable number from the + * last sixteen non-whitespace characters. Last characters + * count "most", so things that end in ".c" sort together. + */ + while ((c = *name++) != 0) { + if (isspace(c)) + continue; + hash = (hash >> 2) + (c << 24); + } + return hash; +} + +static int add_object_entry(const unsigned char *sha1, unsigned hash, int exclude) +{ + unsigned int idx = nr_objects; + struct object_entry *entry; + struct packed_git *p; + unsigned int found_offset = 0; + struct packed_git *found_pack = NULL; + int ix, status = 0; + + if (!exclude) { + for (p = packed_git; p; p = p->next) { + struct pack_entry e; + if (find_pack_entry_one(sha1, &e, p)) { + if (incremental) + return 0; + if (local && !p->pack_local) + return 0; + if (!found_pack) { + found_offset = e.offset; + found_pack = e.p; + } + } + } + } + if ((entry = locate_object_entry(sha1)) != NULL) + goto already_added; + + if (idx >= nr_alloc) { + unsigned int needed = (idx + 1024) * 3 / 2; + objects = xrealloc(objects, needed * sizeof(*entry)); + nr_alloc = needed; + } + entry = objects + idx; + nr_objects = idx + 1; + memset(entry, 0, sizeof(*entry)); + memcpy(entry->sha1, sha1, 20); + entry->hash = hash; + + if (object_ix_hashsz * 3 <= nr_objects * 4) + rehash_objects(); + else { + ix = locate_object_entry_hash(entry->sha1); + if (0 <= ix) + die("internal error in object hashing."); + object_ix[-1 - ix] = idx + 1; + } + status = 1; + + already_added: + if (progress_update) { + fprintf(stderr, "Counting objects...%d\r", nr_objects); + progress_update = 0; + } + if (exclude) + entry->preferred_base = 1; + else { + if (found_pack) { + entry->in_pack = found_pack; + entry->in_pack_offset = found_offset; + } + } + return status; +} + +struct pbase_tree_cache { + unsigned char sha1[20]; + int ref; + int temporary; + void *tree_data; + unsigned long tree_size; +}; + +static struct pbase_tree_cache *(pbase_tree_cache[256]); +static int pbase_tree_cache_ix(const unsigned char *sha1) +{ + return sha1[0] % ARRAY_SIZE(pbase_tree_cache); +} +static int pbase_tree_cache_ix_incr(int ix) +{ + return (ix+1) % ARRAY_SIZE(pbase_tree_cache); +} + +static struct pbase_tree { + struct pbase_tree *next; + /* This is a phony "cache" entry; we are not + * going to evict it nor find it through _get() + * mechanism -- this is for the toplevel node that + * would almost always change with any commit. + */ + struct pbase_tree_cache pcache; +} *pbase_tree; + +static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1) +{ + struct pbase_tree_cache *ent, *nent; + void *data; + unsigned long size; + char type[20]; + int neigh; + int my_ix = pbase_tree_cache_ix(sha1); + int available_ix = -1; + + /* pbase-tree-cache acts as a limited hashtable. + * your object will be found at your index or within a few + * slots after that slot if it is cached. + */ + for (neigh = 0; neigh < 8; neigh++) { + ent = pbase_tree_cache[my_ix]; + if (ent && !memcmp(ent->sha1, sha1, 20)) { + ent->ref++; + return ent; + } + else if (((available_ix < 0) && (!ent || !ent->ref)) || + ((0 <= available_ix) && + (!ent && pbase_tree_cache[available_ix]))) + available_ix = my_ix; + if (!ent) + break; + my_ix = pbase_tree_cache_ix_incr(my_ix); + } + + /* Did not find one. Either we got a bogus request or + * we need to read and perhaps cache. + */ + data = read_sha1_file(sha1, type, &size); + if (!data) + return NULL; + if (strcmp(type, tree_type)) { + free(data); + return NULL; + } + + /* We need to either cache or return a throwaway copy */ + + if (available_ix < 0) + ent = NULL; + else { + ent = pbase_tree_cache[available_ix]; + my_ix = available_ix; + } + + if (!ent) { + nent = xmalloc(sizeof(*nent)); + nent->temporary = (available_ix < 0); + } + else { + /* evict and reuse */ + free(ent->tree_data); + nent = ent; + } + memcpy(nent->sha1, sha1, 20); + nent->tree_data = data; + nent->tree_size = size; + nent->ref = 1; + if (!nent->temporary) + pbase_tree_cache[my_ix] = nent; + return nent; +} + +static void pbase_tree_put(struct pbase_tree_cache *cache) +{ + if (!cache->temporary) { + cache->ref--; + return; + } + free(cache->tree_data); + free(cache); +} + +static int name_cmp_len(const char *name) +{ + int i; + for (i = 0; name[i] && name[i] != '\n' && name[i] != '/'; i++) + ; + return i; +} + +static void add_pbase_object(struct tree_desc *tree, + const char *name, + int cmplen, + const char *fullname) +{ + struct name_entry entry; + + while (tree_entry(tree,&entry)) { + unsigned long size; + char type[20]; + + if (entry.pathlen != cmplen || + memcmp(entry.path, name, cmplen) || + !has_sha1_file(entry.sha1) || + sha1_object_info(entry.sha1, type, &size)) + continue; + if (name[cmplen] != '/') { + unsigned hash = name_hash(fullname); + add_object_entry(entry.sha1, hash, 1); + return; + } + if (!strcmp(type, tree_type)) { + struct tree_desc sub; + struct pbase_tree_cache *tree; + const char *down = name+cmplen+1; + int downlen = name_cmp_len(down); + + tree = pbase_tree_get(entry.sha1); + if (!tree) + return; + sub.buf = tree->tree_data; + sub.size = tree->tree_size; + + add_pbase_object(&sub, down, downlen, fullname); + pbase_tree_put(tree); + } + } +} + +static unsigned *done_pbase_paths; +static int done_pbase_paths_num; +static int done_pbase_paths_alloc; +static int done_pbase_path_pos(unsigned hash) +{ + int lo = 0; + int hi = done_pbase_paths_num; + while (lo < hi) { + int mi = (hi + lo) / 2; + if (done_pbase_paths[mi] == hash) + return mi; + if (done_pbase_paths[mi] < hash) + hi = mi; + else + lo = mi + 1; + } + return -lo-1; +} + +static int check_pbase_path(unsigned hash) +{ + int pos = (!done_pbase_paths) ? -1 : done_pbase_path_pos(hash); + if (0 <= pos) + return 1; + pos = -pos - 1; + if (done_pbase_paths_alloc <= done_pbase_paths_num) { + done_pbase_paths_alloc = alloc_nr(done_pbase_paths_alloc); + done_pbase_paths = xrealloc(done_pbase_paths, + done_pbase_paths_alloc * + sizeof(unsigned)); + } + done_pbase_paths_num++; + if (pos < done_pbase_paths_num) + memmove(done_pbase_paths + pos + 1, + done_pbase_paths + pos, + (done_pbase_paths_num - pos - 1) * sizeof(unsigned)); + done_pbase_paths[pos] = hash; + return 0; +} + +static void add_preferred_base_object(char *name, unsigned hash) +{ + struct pbase_tree *it; + int cmplen = name_cmp_len(name); + + if (check_pbase_path(hash)) + return; + + for (it = pbase_tree; it; it = it->next) { + if (cmplen == 0) { + hash = name_hash(""); + add_object_entry(it->pcache.sha1, hash, 1); + } + else { + struct tree_desc tree; + tree.buf = it->pcache.tree_data; + tree.size = it->pcache.tree_size; + add_pbase_object(&tree, name, cmplen, name); + } + } +} + +static void add_preferred_base(unsigned char *sha1) +{ + struct pbase_tree *it; + void *data; + unsigned long size; + unsigned char tree_sha1[20]; + + data = read_object_with_reference(sha1, tree_type, &size, tree_sha1); + if (!data) + return; + + for (it = pbase_tree; it; it = it->next) { + if (!memcmp(it->pcache.sha1, tree_sha1, 20)) { + free(data); + return; + } + } + + it = xcalloc(1, sizeof(*it)); + it->next = pbase_tree; + pbase_tree = it; + + memcpy(it->pcache.sha1, tree_sha1, 20); + it->pcache.tree_data = data; + it->pcache.tree_size = size; +} + +static void check_object(struct object_entry *entry) +{ + char type[20]; + + if (entry->in_pack && !entry->preferred_base) { + unsigned char base[20]; + unsigned long size; + struct object_entry *base_entry; + + /* We want in_pack_type even if we do not reuse delta. + * There is no point not reusing non-delta representations. + */ + check_reuse_pack_delta(entry->in_pack, + entry->in_pack_offset, + base, &size, + &entry->in_pack_type); + + /* Check if it is delta, and the base is also an object + * we are going to pack. If so we will reuse the existing + * delta. + */ + if (!no_reuse_delta && + entry->in_pack_type == OBJ_DELTA && + (base_entry = locate_object_entry(base)) && + (!base_entry->preferred_base)) { + + /* Depth value does not matter - find_deltas() + * will never consider reused delta as the + * base object to deltify other objects + * against, in order to avoid circular deltas. + */ + + /* uncompressed size of the delta data */ + entry->size = entry->delta_size = size; + entry->delta = base_entry; + entry->type = OBJ_DELTA; + + entry->delta_sibling = base_entry->delta_child; + base_entry->delta_child = entry; + + return; + } + /* Otherwise we would do the usual */ + } + + if (sha1_object_info(entry->sha1, type, &entry->size)) + die("unable to get type of object %s", + sha1_to_hex(entry->sha1)); + + if (!strcmp(type, commit_type)) { + entry->type = OBJ_COMMIT; + } else if (!strcmp(type, tree_type)) { + entry->type = OBJ_TREE; + } else if (!strcmp(type, blob_type)) { + entry->type = OBJ_BLOB; + } else if (!strcmp(type, tag_type)) { + entry->type = OBJ_TAG; + } else + die("unable to pack object %s of type %s", + sha1_to_hex(entry->sha1), type); +} + +static unsigned int check_delta_limit(struct object_entry *me, unsigned int n) +{ + struct object_entry *child = me->delta_child; + unsigned int m = n; + while (child) { + unsigned int c = check_delta_limit(child, n + 1); + if (m < c) + m = c; + child = child->delta_sibling; + } + return m; +} + +static void get_object_details(void) +{ + int i; + struct object_entry *entry; + + prepare_pack_ix(); + for (i = 0, entry = objects; i < nr_objects; i++, entry++) + check_object(entry); + + if (nr_objects == nr_result) { + /* + * Depth of objects that depend on the entry -- this + * is subtracted from depth-max to break too deep + * delta chain because of delta data reusing. + * However, we loosen this restriction when we know we + * are creating a thin pack -- it will have to be + * expanded on the other end anyway, so do not + * artificially cut the delta chain and let it go as + * deep as it wants. + */ + for (i = 0, entry = objects; i < nr_objects; i++, entry++) + if (!entry->delta && entry->delta_child) + entry->delta_limit = + check_delta_limit(entry, 1); + } +} + +typedef int (*entry_sort_t)(const struct object_entry *, const struct object_entry *); + +static entry_sort_t current_sort; + +static int sort_comparator(const void *_a, const void *_b) +{ + struct object_entry *a = *(struct object_entry **)_a; + struct object_entry *b = *(struct object_entry **)_b; + return current_sort(a,b); +} + +static struct object_entry **create_sorted_list(entry_sort_t sort) +{ + struct object_entry **list = xmalloc(nr_objects * sizeof(struct object_entry *)); + int i; + + for (i = 0; i < nr_objects; i++) + list[i] = objects + i; + current_sort = sort; + qsort(list, nr_objects, sizeof(struct object_entry *), sort_comparator); + return list; +} + +static int sha1_sort(const struct object_entry *a, const struct object_entry *b) +{ + return memcmp(a->sha1, b->sha1, 20); +} + +static struct object_entry **create_final_object_list(void) +{ + struct object_entry **list; + int i, j; + + for (i = nr_result = 0; i < nr_objects; i++) + if (!objects[i].preferred_base) + nr_result++; + list = xmalloc(nr_result * sizeof(struct object_entry *)); + for (i = j = 0; i < nr_objects; i++) { + if (!objects[i].preferred_base) + list[j++] = objects + i; + } + current_sort = sha1_sort; + qsort(list, nr_result, sizeof(struct object_entry *), sort_comparator); + return list; +} + +static int type_size_sort(const struct object_entry *a, const struct object_entry *b) +{ + if (a->type < b->type) + return -1; + if (a->type > b->type) + return 1; + if (a->hash < b->hash) + return -1; + if (a->hash > b->hash) + return 1; + if (a->preferred_base < b->preferred_base) + return -1; + if (a->preferred_base > b->preferred_base) + return 1; + if (a->size < b->size) + return -1; + if (a->size > b->size) + return 1; + return a < b ? -1 : (a > b); +} + +struct unpacked { + struct object_entry *entry; + void *data; + struct delta_index *index; +}; + +/* + * We search for deltas _backwards_ in a list sorted by type and + * by size, so that we see progressively smaller and smaller files. + * That's because we prefer deltas to be from the bigger file + * to the smaller - deletes are potentially cheaper, but perhaps + * more importantly, the bigger file is likely the more recent + * one. + */ +static int try_delta(struct unpacked *trg, struct unpacked *src, + unsigned max_depth) +{ + struct object_entry *trg_entry = trg->entry; + struct object_entry *src_entry = src->entry; + unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz; + char type[10]; + void *delta_buf; + + /* Don't bother doing diffs between different types */ + if (trg_entry->type != src_entry->type) + return -1; + + /* We do not compute delta to *create* objects we are not + * going to pack. + */ + if (trg_entry->preferred_base) + return -1; + + /* + * We do not bother to try a delta that we discarded + * on an earlier try, but only when reusing delta data. + */ + if (!no_reuse_delta && trg_entry->in_pack && + trg_entry->in_pack == src_entry->in_pack) + return 0; + + /* + * If the current object is at pack edge, take the depth the + * objects that depend on the current object into account -- + * otherwise they would become too deep. + */ + if (trg_entry->delta_child) { + if (max_depth <= trg_entry->delta_limit) + return 0; + max_depth -= trg_entry->delta_limit; + } + if (src_entry->depth >= max_depth) + return 0; + + /* Now some size filtering heuristics. */ + trg_size = trg_entry->size; + max_size = trg_size/2 - 20; + max_size = max_size * (max_depth - src_entry->depth) / max_depth; + if (max_size == 0) + return 0; + if (trg_entry->delta && trg_entry->delta_size <= max_size) + max_size = trg_entry->delta_size-1; + src_size = src_entry->size; + sizediff = src_size < trg_size ? trg_size - src_size : 0; + if (sizediff >= max_size) + return 0; + + /* Load data if not already done */ + if (!trg->data) { + trg->data = read_sha1_file(trg_entry->sha1, type, &sz); + if (sz != trg_size) + die("object %s inconsistent object length (%lu vs %lu)", + sha1_to_hex(trg_entry->sha1), sz, trg_size); + } + if (!src->data) { + src->data = read_sha1_file(src_entry->sha1, type, &sz); + if (sz != src_size) + die("object %s inconsistent object length (%lu vs %lu)", + sha1_to_hex(src_entry->sha1), sz, src_size); + } + if (!src->index) { + src->index = create_delta_index(src->data, src_size); + if (!src->index) + die("out of memory"); + } + + delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size); + if (!delta_buf) + return 0; + + trg_entry->delta = src_entry; + trg_entry->delta_size = delta_size; + trg_entry->depth = src_entry->depth + 1; + free(delta_buf); + return 1; +} + +static void progress_interval(int signum) +{ + progress_update = 1; +} + +static void find_deltas(struct object_entry **list, int window, int depth) +{ + int i, idx; + unsigned int array_size = window * sizeof(struct unpacked); + struct unpacked *array = xmalloc(array_size); + unsigned processed = 0; + unsigned last_percent = 999; + + memset(array, 0, array_size); + i = nr_objects; + idx = 0; + if (progress) + fprintf(stderr, "Deltifying %d objects.\n", nr_result); + + while (--i >= 0) { + struct object_entry *entry = list[i]; + struct unpacked *n = array + idx; + int j; + + if (!entry->preferred_base) + processed++; + + if (progress) { + unsigned percent = processed * 100 / nr_result; + if (percent != last_percent || progress_update) { + fprintf(stderr, "%4u%% (%u/%u) done\r", + percent, processed, nr_result); + progress_update = 0; + last_percent = percent; + } + } + + if (entry->delta) + /* This happens if we decided to reuse existing + * delta from a pack. "!no_reuse_delta &&" is implied. + */ + continue; + + if (entry->size < 50) + continue; + free_delta_index(n->index); + n->index = NULL; + free(n->data); + n->data = NULL; + n->entry = entry; + + j = window; + while (--j > 0) { + unsigned int other_idx = idx + j; + struct unpacked *m; + if (other_idx >= window) + other_idx -= window; + m = array + other_idx; + if (!m->entry) + break; + if (try_delta(n, m, depth) < 0) + break; + } + /* if we made n a delta, and if n is already at max + * depth, leaving it in the window is pointless. we + * should evict it first. + */ + if (entry->delta && depth <= entry->depth) + continue; + + idx++; + if (idx >= window) + idx = 0; + } + + if (progress) + fputc('\n', stderr); + + for (i = 0; i < window; ++i) { + free_delta_index(array[i].index); + free(array[i].data); + } + free(array); +} + +static void prepare_pack(int window, int depth) +{ + get_object_details(); + sorted_by_type = create_sorted_list(type_size_sort); + if (window && depth) + find_deltas(sorted_by_type, window+1, depth); +} + +static int reuse_cached_pack(unsigned char *sha1, int pack_to_stdout) +{ + static const char cache[] = "pack-cache/pack-%s.%s"; + char *cached_pack, *cached_idx; + int ifd, ofd, ifd_ix = -1; + + cached_pack = git_path(cache, sha1_to_hex(sha1), "pack"); + ifd = open(cached_pack, O_RDONLY); + if (ifd < 0) + return 0; + + if (!pack_to_stdout) { + cached_idx = git_path(cache, sha1_to_hex(sha1), "idx"); + ifd_ix = open(cached_idx, O_RDONLY); + if (ifd_ix < 0) { + close(ifd); + return 0; + } + } + + if (progress) + fprintf(stderr, "Reusing %d objects pack %s\n", nr_objects, + sha1_to_hex(sha1)); + + if (pack_to_stdout) { + if (copy_fd(ifd, 1)) + exit(1); + close(ifd); + } + else { + char name[PATH_MAX]; + snprintf(name, sizeof(name), + "%s-%s.%s", base_name, sha1_to_hex(sha1), "pack"); + ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666); + if (ofd < 0) + die("unable to open %s (%s)", name, strerror(errno)); + if (copy_fd(ifd, ofd)) + exit(1); + close(ifd); + + snprintf(name, sizeof(name), + "%s-%s.%s", base_name, sha1_to_hex(sha1), "idx"); + ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666); + if (ofd < 0) + die("unable to open %s (%s)", name, strerror(errno)); + if (copy_fd(ifd_ix, ofd)) + exit(1); + close(ifd_ix); + puts(sha1_to_hex(sha1)); + } + + return 1; +} + +static void setup_progress_signal(void) +{ + struct sigaction sa; + struct itimerval v; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = progress_interval; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + + v.it_interval.tv_sec = 1; + v.it_interval.tv_usec = 0; + v.it_value = v.it_interval; + setitimer(ITIMER_REAL, &v, NULL); +} + +static int git_pack_config(const char *k, const char *v) +{ + if(!strcmp(k, "pack.window")) { + window = git_config_int(k, v); + return 0; + } + return git_default_config(k, v); +} + +int cmd_pack_objects(int argc, const char **argv, const char *prefix) +{ + SHA_CTX ctx; + char line[40 + 1 + PATH_MAX + 2]; + int depth = 10, pack_to_stdout = 0; + struct object_entry **list; + int num_preferred_base = 0; + int i; + + git_config(git_pack_config); + + progress = isatty(2); + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (*arg == '-') { + if (!strcmp("--non-empty", arg)) { + non_empty = 1; + continue; + } + if (!strcmp("--local", arg)) { + local = 1; + continue; + } + if (!strcmp("--progress", arg)) { + progress = 1; + continue; + } + if (!strcmp("--incremental", arg)) { + incremental = 1; + continue; + } + if (!strncmp("--window=", arg, 9)) { + char *end; + window = strtoul(arg+9, &end, 0); + if (!arg[9] || *end) + usage(pack_usage); + continue; + } + if (!strncmp("--depth=", arg, 8)) { + char *end; + depth = strtoul(arg+8, &end, 0); + if (!arg[8] || *end) + usage(pack_usage); + continue; + } + if (!strcmp("--progress", arg)) { + progress = 1; + continue; + } + if (!strcmp("-q", arg)) { + progress = 0; + continue; + } + if (!strcmp("--no-reuse-delta", arg)) { + no_reuse_delta = 1; + continue; + } + if (!strcmp("--stdout", arg)) { + pack_to_stdout = 1; + continue; + } + usage(pack_usage); + } + if (base_name) + usage(pack_usage); + base_name = arg; + } + + if (pack_to_stdout != !base_name) + usage(pack_usage); + + prepare_packed_git(); + + if (progress) { + fprintf(stderr, "Generating pack...\n"); + setup_progress_signal(); + } + + for (;;) { + unsigned char sha1[20]; + unsigned hash; + + if (!fgets(line, sizeof(line), stdin)) { + if (feof(stdin)) + break; + if (!ferror(stdin)) + die("fgets returned NULL, not EOF, not error!"); + if (errno != EINTR) + die("fgets: %s", strerror(errno)); + clearerr(stdin); + continue; + } + + if (line[0] == '-') { + if (get_sha1_hex(line+1, sha1)) + die("expected edge sha1, got garbage:\n %s", + line+1); + if (num_preferred_base++ < window) + add_preferred_base(sha1); + continue; + } + if (get_sha1_hex(line, sha1)) + die("expected sha1, got garbage:\n %s", line); + hash = name_hash(line+41); + add_preferred_base_object(line+41, hash); + add_object_entry(sha1, hash, 0); + } + if (progress) + fprintf(stderr, "Done counting %d objects.\n", nr_objects); + sorted_by_sha = create_final_object_list(); + if (non_empty && !nr_result) + return 0; + + SHA1_Init(&ctx); + list = sorted_by_sha; + for (i = 0; i < nr_result; i++) { + struct object_entry *entry = *list++; + SHA1_Update(&ctx, entry->sha1, 20); + } + SHA1_Final(object_list_sha1, &ctx); + if (progress && (nr_objects != nr_result)) + fprintf(stderr, "Result has %d objects.\n", nr_result); + + if (reuse_cached_pack(object_list_sha1, pack_to_stdout)) + ; + else { + if (nr_result) + prepare_pack(window, depth); + if (progress && pack_to_stdout) { + /* the other end usually displays progress itself */ + struct itimerval v = {{0,},}; + setitimer(ITIMER_REAL, &v, NULL); + signal(SIGALRM, SIG_IGN ); + progress_update = 0; + } + write_pack_file(); + if (!pack_to_stdout) { + write_index_file(); + puts(sha1_to_hex(object_list_sha1)); + } + } + if (progress) + fprintf(stderr, "Total %d, written %d (delta %d), reused %d (delta %d)\n", + nr_result, written, written_delta, reused, reused_delta); + return 0; +} diff --git a/builtin.h b/builtin.h index d1d9dc14f9..bc57a04c05 100644 --- a/builtin.h +++ b/builtin.h @@ -50,6 +50,7 @@ extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_repo_config(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); +extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); diff --git a/git.c b/git.c index 501a7815fc..5d58946f41 100644 --- a/git.c +++ b/git.c @@ -266,6 +266,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "prune-packed", cmd_prune_packed, NEEDS_PREFIX }, { "repo-config", cmd_repo_config }, { "name-rev", cmd_name_rev, NEEDS_PREFIX }, + { "pack-objects", cmd_pack_objects, NEEDS_PREFIX }, }; int i; diff --git a/pack-objects.c b/pack-objects.c deleted file mode 100644 index 861c7f08ff..0000000000 --- a/pack-objects.c +++ /dev/null @@ -1,1376 +0,0 @@ -#include "cache.h" -#include "object.h" -#include "blob.h" -#include "commit.h" -#include "tag.h" -#include "tree.h" -#include "delta.h" -#include "pack.h" -#include "csum-file.h" -#include "tree-walk.h" -#include -#include - -static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list"; - -struct object_entry { - unsigned char sha1[20]; - unsigned long size; /* uncompressed size */ - unsigned long offset; /* offset into the final pack file; - * nonzero if already written. - */ - unsigned int depth; /* delta depth */ - unsigned int delta_limit; /* base adjustment for in-pack delta */ - unsigned int hash; /* name hint hash */ - enum object_type type; - enum object_type in_pack_type; /* could be delta */ - unsigned long delta_size; /* delta data size (uncompressed) */ - struct object_entry *delta; /* delta base object */ - struct packed_git *in_pack; /* already in pack */ - unsigned int in_pack_offset; - struct object_entry *delta_child; /* deltified objects who bases me */ - struct object_entry *delta_sibling; /* other deltified objects who - * uses the same base as me - */ - int preferred_base; /* we do not pack this, but is encouraged to - * be used as the base objectto delta huge - * objects against. - */ -}; - -/* - * Objects we are going to pack are collected in objects array (dynamically - * expanded). nr_objects & nr_alloc controls this array. They are stored - * in the order we see -- typically rev-list --objects order that gives us - * nice "minimum seek" order. - * - * sorted-by-sha ans sorted-by-type are arrays of pointers that point at - * elements in the objects array. The former is used to build the pack - * index (lists object names in the ascending order to help offset lookup), - * and the latter is used to group similar things together by try_delta() - * heuristics. - */ - -static unsigned char object_list_sha1[20]; -static int non_empty = 0; -static int no_reuse_delta = 0; -static int local = 0; -static int incremental = 0; -static struct object_entry **sorted_by_sha, **sorted_by_type; -static struct object_entry *objects = NULL; -static int nr_objects = 0, nr_alloc = 0, nr_result = 0; -static const char *base_name; -static unsigned char pack_file_sha1[20]; -static int progress = 1; -static volatile sig_atomic_t progress_update = 0; -static int window = 10; - -/* - * The object names in objects array are hashed with this hashtable, - * to help looking up the entry by object name. Binary search from - * sorted_by_sha is also possible but this was easier to code and faster. - * This hashtable is built after all the objects are seen. - */ -static int *object_ix = NULL; -static int object_ix_hashsz = 0; - -/* - * Pack index for existing packs give us easy access to the offsets into - * corresponding pack file where each object's data starts, but the entries - * do not store the size of the compressed representation (uncompressed - * size is easily available by examining the pack entry header). We build - * a hashtable of existing packs (pack_revindex), and keep reverse index - * here -- pack index file is sorted by object name mapping to offset; this - * pack_revindex[].revindex array is an ordered list of offsets, so if you - * know the offset of an object, next offset is where its packed - * representation ends. - */ -struct pack_revindex { - struct packed_git *p; - unsigned long *revindex; -} *pack_revindex = NULL; -static int pack_revindex_hashsz = 0; - -/* - * stats - */ -static int written = 0; -static int written_delta = 0; -static int reused = 0; -static int reused_delta = 0; - -static int pack_revindex_ix(struct packed_git *p) -{ - unsigned long ui = (unsigned long)p; - int i; - - ui = ui ^ (ui >> 16); /* defeat structure alignment */ - i = (int)(ui % pack_revindex_hashsz); - while (pack_revindex[i].p) { - if (pack_revindex[i].p == p) - return i; - if (++i == pack_revindex_hashsz) - i = 0; - } - return -1 - i; -} - -static void prepare_pack_ix(void) -{ - int num; - struct packed_git *p; - for (num = 0, p = packed_git; p; p = p->next) - num++; - if (!num) - return; - pack_revindex_hashsz = num * 11; - pack_revindex = xcalloc(sizeof(*pack_revindex), pack_revindex_hashsz); - for (p = packed_git; p; p = p->next) { - num = pack_revindex_ix(p); - num = - 1 - num; - pack_revindex[num].p = p; - } - /* revindex elements are lazily initialized */ -} - -static int cmp_offset(const void *a_, const void *b_) -{ - unsigned long a = *(unsigned long *) a_; - unsigned long b = *(unsigned long *) b_; - if (a < b) - return -1; - else if (a == b) - return 0; - else - return 1; -} - -/* - * Ordered list of offsets of objects in the pack. - */ -static void prepare_pack_revindex(struct pack_revindex *rix) -{ - struct packed_git *p = rix->p; - int num_ent = num_packed_objects(p); - int i; - void *index = p->index_base + 256; - - rix->revindex = xmalloc(sizeof(unsigned long) * (num_ent + 1)); - for (i = 0; i < num_ent; i++) { - unsigned int hl = *((unsigned int *)((char *) index + 24*i)); - rix->revindex[i] = ntohl(hl); - } - /* This knows the pack format -- the 20-byte trailer - * follows immediately after the last object data. - */ - rix->revindex[num_ent] = p->pack_size - 20; - qsort(rix->revindex, num_ent, sizeof(unsigned long), cmp_offset); -} - -static unsigned long find_packed_object_size(struct packed_git *p, - unsigned long ofs) -{ - int num; - int lo, hi; - struct pack_revindex *rix; - unsigned long *revindex; - num = pack_revindex_ix(p); - if (num < 0) - die("internal error: pack revindex uninitialized"); - rix = &pack_revindex[num]; - if (!rix->revindex) - prepare_pack_revindex(rix); - revindex = rix->revindex; - lo = 0; - hi = num_packed_objects(p) + 1; - do { - int mi = (lo + hi) / 2; - if (revindex[mi] == ofs) { - return revindex[mi+1] - ofs; - } - else if (ofs < revindex[mi]) - hi = mi; - else - lo = mi + 1; - } while (lo < hi); - die("internal error: pack revindex corrupt"); -} - -static void *delta_against(void *buf, unsigned long size, struct object_entry *entry) -{ - unsigned long othersize, delta_size; - char type[10]; - void *otherbuf = read_sha1_file(entry->delta->sha1, type, &othersize); - void *delta_buf; - - if (!otherbuf) - die("unable to read %s", sha1_to_hex(entry->delta->sha1)); - delta_buf = diff_delta(otherbuf, othersize, - buf, size, &delta_size, 0); - if (!delta_buf || delta_size != entry->delta_size) - die("delta size changed"); - free(buf); - free(otherbuf); - return delta_buf; -} - -/* - * The per-object header is a pretty dense thing, which is - * - first byte: low four bits are "size", then three bits of "type", - * and the high bit is "size continues". - * - each byte afterwards: low seven bits are size continuation, - * with the high bit being "size continues" - */ -static int encode_header(enum object_type type, unsigned long size, unsigned char *hdr) -{ - int n = 1; - unsigned char c; - - if (type < OBJ_COMMIT || type > OBJ_DELTA) - die("bad type %d", type); - - c = (type << 4) | (size & 15); - size >>= 4; - while (size) { - *hdr++ = c | 0x80; - c = size & 0x7f; - size >>= 7; - n++; - } - *hdr = c; - return n; -} - -static unsigned long write_object(struct sha1file *f, - struct object_entry *entry) -{ - unsigned long size; - char type[10]; - void *buf; - unsigned char header[10]; - unsigned hdrlen, datalen; - enum object_type obj_type; - int to_reuse = 0; - - if (entry->preferred_base) - return 0; - - obj_type = entry->type; - if (! entry->in_pack) - to_reuse = 0; /* can't reuse what we don't have */ - else if (obj_type == OBJ_DELTA) - to_reuse = 1; /* check_object() decided it for us */ - else if (obj_type != entry->in_pack_type) - to_reuse = 0; /* pack has delta which is unusable */ - else if (entry->delta) - to_reuse = 0; /* we want to pack afresh */ - else - to_reuse = 1; /* we have it in-pack undeltified, - * and we do not need to deltify it. - */ - - if (! to_reuse) { - buf = read_sha1_file(entry->sha1, type, &size); - if (!buf) - die("unable to read %s", sha1_to_hex(entry->sha1)); - if (size != entry->size) - die("object %s size inconsistency (%lu vs %lu)", - sha1_to_hex(entry->sha1), size, entry->size); - if (entry->delta) { - buf = delta_against(buf, size, entry); - size = entry->delta_size; - obj_type = OBJ_DELTA; - } - /* - * The object header is a byte of 'type' followed by zero or - * more bytes of length. For deltas, the 20 bytes of delta - * sha1 follows that. - */ - hdrlen = encode_header(obj_type, size, header); - sha1write(f, header, hdrlen); - - if (entry->delta) { - sha1write(f, entry->delta, 20); - hdrlen += 20; - } - datalen = sha1write_compressed(f, buf, size); - free(buf); - } - else { - struct packed_git *p = entry->in_pack; - use_packed_git(p); - - datalen = find_packed_object_size(p, entry->in_pack_offset); - buf = (char *) p->pack_base + entry->in_pack_offset; - sha1write(f, buf, datalen); - unuse_packed_git(p); - hdrlen = 0; /* not really */ - if (obj_type == OBJ_DELTA) - reused_delta++; - reused++; - } - if (obj_type == OBJ_DELTA) - written_delta++; - written++; - return hdrlen + datalen; -} - -static unsigned long write_one(struct sha1file *f, - struct object_entry *e, - unsigned long offset) -{ - if (e->offset) - /* offset starts from header size and cannot be zero - * if it is written already. - */ - return offset; - e->offset = offset; - offset += write_object(f, e); - /* if we are deltified, write out its base object. */ - if (e->delta) - offset = write_one(f, e->delta, offset); - return offset; -} - -static void write_pack_file(void) -{ - int i; - struct sha1file *f; - unsigned long offset; - struct pack_header hdr; - unsigned last_percent = 999; - int do_progress = 0; - - if (!base_name) - f = sha1fd(1, ""); - else { - f = sha1create("%s-%s.%s", base_name, - sha1_to_hex(object_list_sha1), "pack"); - do_progress = progress; - } - if (do_progress) - fprintf(stderr, "Writing %d objects.\n", nr_result); - - hdr.hdr_signature = htonl(PACK_SIGNATURE); - hdr.hdr_version = htonl(PACK_VERSION); - hdr.hdr_entries = htonl(nr_result); - sha1write(f, &hdr, sizeof(hdr)); - offset = sizeof(hdr); - if (!nr_result) - goto done; - for (i = 0; i < nr_objects; i++) { - offset = write_one(f, objects + i, offset); - if (do_progress) { - unsigned percent = written * 100 / nr_result; - if (progress_update || percent != last_percent) { - fprintf(stderr, "%4u%% (%u/%u) done\r", - percent, written, nr_result); - progress_update = 0; - last_percent = percent; - } - } - } - if (do_progress) - fputc('\n', stderr); - done: - sha1close(f, pack_file_sha1, 1); -} - -static void write_index_file(void) -{ - int i; - struct sha1file *f = sha1create("%s-%s.%s", base_name, - sha1_to_hex(object_list_sha1), "idx"); - struct object_entry **list = sorted_by_sha; - struct object_entry **last = list + nr_result; - unsigned int array[256]; - - /* - * Write the first-level table (the list is sorted, - * but we use a 256-entry lookup to be able to avoid - * having to do eight extra binary search iterations). - */ - for (i = 0; i < 256; i++) { - struct object_entry **next = list; - while (next < last) { - struct object_entry *entry = *next; - if (entry->sha1[0] != i) - break; - next++; - } - array[i] = htonl(next - sorted_by_sha); - list = next; - } - sha1write(f, array, 256 * sizeof(int)); - - /* - * Write the actual SHA1 entries.. - */ - list = sorted_by_sha; - for (i = 0; i < nr_result; i++) { - struct object_entry *entry = *list++; - unsigned int offset = htonl(entry->offset); - sha1write(f, &offset, 4); - sha1write(f, entry->sha1, 20); - } - sha1write(f, pack_file_sha1, 20); - sha1close(f, NULL, 1); -} - -static int locate_object_entry_hash(const unsigned char *sha1) -{ - int i; - unsigned int ui; - memcpy(&ui, sha1, sizeof(unsigned int)); - i = ui % object_ix_hashsz; - while (0 < object_ix[i]) { - if (!memcmp(sha1, objects[object_ix[i]-1].sha1, 20)) - return i; - if (++i == object_ix_hashsz) - i = 0; - } - return -1 - i; -} - -static struct object_entry *locate_object_entry(const unsigned char *sha1) -{ - int i; - - if (!object_ix_hashsz) - return NULL; - - i = locate_object_entry_hash(sha1); - if (0 <= i) - return &objects[object_ix[i]-1]; - return NULL; -} - -static void rehash_objects(void) -{ - int i; - struct object_entry *oe; - - object_ix_hashsz = nr_objects * 3; - if (object_ix_hashsz < 1024) - object_ix_hashsz = 1024; - object_ix = xrealloc(object_ix, sizeof(int) * object_ix_hashsz); - memset(object_ix, 0, sizeof(int) * object_ix_hashsz); - for (i = 0, oe = objects; i < nr_objects; i++, oe++) { - int ix = locate_object_entry_hash(oe->sha1); - if (0 <= ix) - continue; - ix = -1 - ix; - object_ix[ix] = i + 1; - } -} - -static unsigned name_hash(const char *name) -{ - unsigned char c; - unsigned hash = 0; - - /* - * This effectively just creates a sortable number from the - * last sixteen non-whitespace characters. Last characters - * count "most", so things that end in ".c" sort together. - */ - while ((c = *name++) != 0) { - if (isspace(c)) - continue; - hash = (hash >> 2) + (c << 24); - } - return hash; -} - -static int add_object_entry(const unsigned char *sha1, unsigned hash, int exclude) -{ - unsigned int idx = nr_objects; - struct object_entry *entry; - struct packed_git *p; - unsigned int found_offset = 0; - struct packed_git *found_pack = NULL; - int ix, status = 0; - - if (!exclude) { - for (p = packed_git; p; p = p->next) { - struct pack_entry e; - if (find_pack_entry_one(sha1, &e, p)) { - if (incremental) - return 0; - if (local && !p->pack_local) - return 0; - if (!found_pack) { - found_offset = e.offset; - found_pack = e.p; - } - } - } - } - if ((entry = locate_object_entry(sha1)) != NULL) - goto already_added; - - if (idx >= nr_alloc) { - unsigned int needed = (idx + 1024) * 3 / 2; - objects = xrealloc(objects, needed * sizeof(*entry)); - nr_alloc = needed; - } - entry = objects + idx; - nr_objects = idx + 1; - memset(entry, 0, sizeof(*entry)); - memcpy(entry->sha1, sha1, 20); - entry->hash = hash; - - if (object_ix_hashsz * 3 <= nr_objects * 4) - rehash_objects(); - else { - ix = locate_object_entry_hash(entry->sha1); - if (0 <= ix) - die("internal error in object hashing."); - object_ix[-1 - ix] = idx + 1; - } - status = 1; - - already_added: - if (progress_update) { - fprintf(stderr, "Counting objects...%d\r", nr_objects); - progress_update = 0; - } - if (exclude) - entry->preferred_base = 1; - else { - if (found_pack) { - entry->in_pack = found_pack; - entry->in_pack_offset = found_offset; - } - } - return status; -} - -struct pbase_tree_cache { - unsigned char sha1[20]; - int ref; - int temporary; - void *tree_data; - unsigned long tree_size; -}; - -static struct pbase_tree_cache *(pbase_tree_cache[256]); -static int pbase_tree_cache_ix(const unsigned char *sha1) -{ - return sha1[0] % ARRAY_SIZE(pbase_tree_cache); -} -static int pbase_tree_cache_ix_incr(int ix) -{ - return (ix+1) % ARRAY_SIZE(pbase_tree_cache); -} - -static struct pbase_tree { - struct pbase_tree *next; - /* This is a phony "cache" entry; we are not - * going to evict it nor find it through _get() - * mechanism -- this is for the toplevel node that - * would almost always change with any commit. - */ - struct pbase_tree_cache pcache; -} *pbase_tree; - -static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1) -{ - struct pbase_tree_cache *ent, *nent; - void *data; - unsigned long size; - char type[20]; - int neigh; - int my_ix = pbase_tree_cache_ix(sha1); - int available_ix = -1; - - /* pbase-tree-cache acts as a limited hashtable. - * your object will be found at your index or within a few - * slots after that slot if it is cached. - */ - for (neigh = 0; neigh < 8; neigh++) { - ent = pbase_tree_cache[my_ix]; - if (ent && !memcmp(ent->sha1, sha1, 20)) { - ent->ref++; - return ent; - } - else if (((available_ix < 0) && (!ent || !ent->ref)) || - ((0 <= available_ix) && - (!ent && pbase_tree_cache[available_ix]))) - available_ix = my_ix; - if (!ent) - break; - my_ix = pbase_tree_cache_ix_incr(my_ix); - } - - /* Did not find one. Either we got a bogus request or - * we need to read and perhaps cache. - */ - data = read_sha1_file(sha1, type, &size); - if (!data) - return NULL; - if (strcmp(type, tree_type)) { - free(data); - return NULL; - } - - /* We need to either cache or return a throwaway copy */ - - if (available_ix < 0) - ent = NULL; - else { - ent = pbase_tree_cache[available_ix]; - my_ix = available_ix; - } - - if (!ent) { - nent = xmalloc(sizeof(*nent)); - nent->temporary = (available_ix < 0); - } - else { - /* evict and reuse */ - free(ent->tree_data); - nent = ent; - } - memcpy(nent->sha1, sha1, 20); - nent->tree_data = data; - nent->tree_size = size; - nent->ref = 1; - if (!nent->temporary) - pbase_tree_cache[my_ix] = nent; - return nent; -} - -static void pbase_tree_put(struct pbase_tree_cache *cache) -{ - if (!cache->temporary) { - cache->ref--; - return; - } - free(cache->tree_data); - free(cache); -} - -static int name_cmp_len(const char *name) -{ - int i; - for (i = 0; name[i] && name[i] != '\n' && name[i] != '/'; i++) - ; - return i; -} - -static void add_pbase_object(struct tree_desc *tree, - const char *name, - int cmplen, - const char *fullname) -{ - struct name_entry entry; - - while (tree_entry(tree,&entry)) { - unsigned long size; - char type[20]; - - if (entry.pathlen != cmplen || - memcmp(entry.path, name, cmplen) || - !has_sha1_file(entry.sha1) || - sha1_object_info(entry.sha1, type, &size)) - continue; - if (name[cmplen] != '/') { - unsigned hash = name_hash(fullname); - add_object_entry(entry.sha1, hash, 1); - return; - } - if (!strcmp(type, tree_type)) { - struct tree_desc sub; - struct pbase_tree_cache *tree; - const char *down = name+cmplen+1; - int downlen = name_cmp_len(down); - - tree = pbase_tree_get(entry.sha1); - if (!tree) - return; - sub.buf = tree->tree_data; - sub.size = tree->tree_size; - - add_pbase_object(&sub, down, downlen, fullname); - pbase_tree_put(tree); - } - } -} - -static unsigned *done_pbase_paths; -static int done_pbase_paths_num; -static int done_pbase_paths_alloc; -static int done_pbase_path_pos(unsigned hash) -{ - int lo = 0; - int hi = done_pbase_paths_num; - while (lo < hi) { - int mi = (hi + lo) / 2; - if (done_pbase_paths[mi] == hash) - return mi; - if (done_pbase_paths[mi] < hash) - hi = mi; - else - lo = mi + 1; - } - return -lo-1; -} - -static int check_pbase_path(unsigned hash) -{ - int pos = (!done_pbase_paths) ? -1 : done_pbase_path_pos(hash); - if (0 <= pos) - return 1; - pos = -pos - 1; - if (done_pbase_paths_alloc <= done_pbase_paths_num) { - done_pbase_paths_alloc = alloc_nr(done_pbase_paths_alloc); - done_pbase_paths = xrealloc(done_pbase_paths, - done_pbase_paths_alloc * - sizeof(unsigned)); - } - done_pbase_paths_num++; - if (pos < done_pbase_paths_num) - memmove(done_pbase_paths + pos + 1, - done_pbase_paths + pos, - (done_pbase_paths_num - pos - 1) * sizeof(unsigned)); - done_pbase_paths[pos] = hash; - return 0; -} - -static void add_preferred_base_object(char *name, unsigned hash) -{ - struct pbase_tree *it; - int cmplen = name_cmp_len(name); - - if (check_pbase_path(hash)) - return; - - for (it = pbase_tree; it; it = it->next) { - if (cmplen == 0) { - hash = name_hash(""); - add_object_entry(it->pcache.sha1, hash, 1); - } - else { - struct tree_desc tree; - tree.buf = it->pcache.tree_data; - tree.size = it->pcache.tree_size; - add_pbase_object(&tree, name, cmplen, name); - } - } -} - -static void add_preferred_base(unsigned char *sha1) -{ - struct pbase_tree *it; - void *data; - unsigned long size; - unsigned char tree_sha1[20]; - - data = read_object_with_reference(sha1, tree_type, &size, tree_sha1); - if (!data) - return; - - for (it = pbase_tree; it; it = it->next) { - if (!memcmp(it->pcache.sha1, tree_sha1, 20)) { - free(data); - return; - } - } - - it = xcalloc(1, sizeof(*it)); - it->next = pbase_tree; - pbase_tree = it; - - memcpy(it->pcache.sha1, tree_sha1, 20); - it->pcache.tree_data = data; - it->pcache.tree_size = size; -} - -static void check_object(struct object_entry *entry) -{ - char type[20]; - - if (entry->in_pack && !entry->preferred_base) { - unsigned char base[20]; - unsigned long size; - struct object_entry *base_entry; - - /* We want in_pack_type even if we do not reuse delta. - * There is no point not reusing non-delta representations. - */ - check_reuse_pack_delta(entry->in_pack, - entry->in_pack_offset, - base, &size, - &entry->in_pack_type); - - /* Check if it is delta, and the base is also an object - * we are going to pack. If so we will reuse the existing - * delta. - */ - if (!no_reuse_delta && - entry->in_pack_type == OBJ_DELTA && - (base_entry = locate_object_entry(base)) && - (!base_entry->preferred_base)) { - - /* Depth value does not matter - find_deltas() - * will never consider reused delta as the - * base object to deltify other objects - * against, in order to avoid circular deltas. - */ - - /* uncompressed size of the delta data */ - entry->size = entry->delta_size = size; - entry->delta = base_entry; - entry->type = OBJ_DELTA; - - entry->delta_sibling = base_entry->delta_child; - base_entry->delta_child = entry; - - return; - } - /* Otherwise we would do the usual */ - } - - if (sha1_object_info(entry->sha1, type, &entry->size)) - die("unable to get type of object %s", - sha1_to_hex(entry->sha1)); - - if (!strcmp(type, commit_type)) { - entry->type = OBJ_COMMIT; - } else if (!strcmp(type, tree_type)) { - entry->type = OBJ_TREE; - } else if (!strcmp(type, blob_type)) { - entry->type = OBJ_BLOB; - } else if (!strcmp(type, tag_type)) { - entry->type = OBJ_TAG; - } else - die("unable to pack object %s of type %s", - sha1_to_hex(entry->sha1), type); -} - -static unsigned int check_delta_limit(struct object_entry *me, unsigned int n) -{ - struct object_entry *child = me->delta_child; - unsigned int m = n; - while (child) { - unsigned int c = check_delta_limit(child, n + 1); - if (m < c) - m = c; - child = child->delta_sibling; - } - return m; -} - -static void get_object_details(void) -{ - int i; - struct object_entry *entry; - - prepare_pack_ix(); - for (i = 0, entry = objects; i < nr_objects; i++, entry++) - check_object(entry); - - if (nr_objects == nr_result) { - /* - * Depth of objects that depend on the entry -- this - * is subtracted from depth-max to break too deep - * delta chain because of delta data reusing. - * However, we loosen this restriction when we know we - * are creating a thin pack -- it will have to be - * expanded on the other end anyway, so do not - * artificially cut the delta chain and let it go as - * deep as it wants. - */ - for (i = 0, entry = objects; i < nr_objects; i++, entry++) - if (!entry->delta && entry->delta_child) - entry->delta_limit = - check_delta_limit(entry, 1); - } -} - -typedef int (*entry_sort_t)(const struct object_entry *, const struct object_entry *); - -static entry_sort_t current_sort; - -static int sort_comparator(const void *_a, const void *_b) -{ - struct object_entry *a = *(struct object_entry **)_a; - struct object_entry *b = *(struct object_entry **)_b; - return current_sort(a,b); -} - -static struct object_entry **create_sorted_list(entry_sort_t sort) -{ - struct object_entry **list = xmalloc(nr_objects * sizeof(struct object_entry *)); - int i; - - for (i = 0; i < nr_objects; i++) - list[i] = objects + i; - current_sort = sort; - qsort(list, nr_objects, sizeof(struct object_entry *), sort_comparator); - return list; -} - -static int sha1_sort(const struct object_entry *a, const struct object_entry *b) -{ - return memcmp(a->sha1, b->sha1, 20); -} - -static struct object_entry **create_final_object_list(void) -{ - struct object_entry **list; - int i, j; - - for (i = nr_result = 0; i < nr_objects; i++) - if (!objects[i].preferred_base) - nr_result++; - list = xmalloc(nr_result * sizeof(struct object_entry *)); - for (i = j = 0; i < nr_objects; i++) { - if (!objects[i].preferred_base) - list[j++] = objects + i; - } - current_sort = sha1_sort; - qsort(list, nr_result, sizeof(struct object_entry *), sort_comparator); - return list; -} - -static int type_size_sort(const struct object_entry *a, const struct object_entry *b) -{ - if (a->type < b->type) - return -1; - if (a->type > b->type) - return 1; - if (a->hash < b->hash) - return -1; - if (a->hash > b->hash) - return 1; - if (a->preferred_base < b->preferred_base) - return -1; - if (a->preferred_base > b->preferred_base) - return 1; - if (a->size < b->size) - return -1; - if (a->size > b->size) - return 1; - return a < b ? -1 : (a > b); -} - -struct unpacked { - struct object_entry *entry; - void *data; - struct delta_index *index; -}; - -/* - * We search for deltas _backwards_ in a list sorted by type and - * by size, so that we see progressively smaller and smaller files. - * That's because we prefer deltas to be from the bigger file - * to the smaller - deletes are potentially cheaper, but perhaps - * more importantly, the bigger file is likely the more recent - * one. - */ -static int try_delta(struct unpacked *trg, struct unpacked *src, - unsigned max_depth) -{ - struct object_entry *trg_entry = trg->entry; - struct object_entry *src_entry = src->entry; - unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz; - char type[10]; - void *delta_buf; - - /* Don't bother doing diffs between different types */ - if (trg_entry->type != src_entry->type) - return -1; - - /* We do not compute delta to *create* objects we are not - * going to pack. - */ - if (trg_entry->preferred_base) - return -1; - - /* - * We do not bother to try a delta that we discarded - * on an earlier try, but only when reusing delta data. - */ - if (!no_reuse_delta && trg_entry->in_pack && - trg_entry->in_pack == src_entry->in_pack) - return 0; - - /* - * If the current object is at pack edge, take the depth the - * objects that depend on the current object into account -- - * otherwise they would become too deep. - */ - if (trg_entry->delta_child) { - if (max_depth <= trg_entry->delta_limit) - return 0; - max_depth -= trg_entry->delta_limit; - } - if (src_entry->depth >= max_depth) - return 0; - - /* Now some size filtering heuristics. */ - trg_size = trg_entry->size; - max_size = trg_size/2 - 20; - max_size = max_size * (max_depth - src_entry->depth) / max_depth; - if (max_size == 0) - return 0; - if (trg_entry->delta && trg_entry->delta_size <= max_size) - max_size = trg_entry->delta_size-1; - src_size = src_entry->size; - sizediff = src_size < trg_size ? trg_size - src_size : 0; - if (sizediff >= max_size) - return 0; - - /* Load data if not already done */ - if (!trg->data) { - trg->data = read_sha1_file(trg_entry->sha1, type, &sz); - if (sz != trg_size) - die("object %s inconsistent object length (%lu vs %lu)", - sha1_to_hex(trg_entry->sha1), sz, trg_size); - } - if (!src->data) { - src->data = read_sha1_file(src_entry->sha1, type, &sz); - if (sz != src_size) - die("object %s inconsistent object length (%lu vs %lu)", - sha1_to_hex(src_entry->sha1), sz, src_size); - } - if (!src->index) { - src->index = create_delta_index(src->data, src_size); - if (!src->index) - die("out of memory"); - } - - delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size); - if (!delta_buf) - return 0; - - trg_entry->delta = src_entry; - trg_entry->delta_size = delta_size; - trg_entry->depth = src_entry->depth + 1; - free(delta_buf); - return 1; -} - -static void progress_interval(int signum) -{ - progress_update = 1; -} - -static void find_deltas(struct object_entry **list, int window, int depth) -{ - int i, idx; - unsigned int array_size = window * sizeof(struct unpacked); - struct unpacked *array = xmalloc(array_size); - unsigned processed = 0; - unsigned last_percent = 999; - - memset(array, 0, array_size); - i = nr_objects; - idx = 0; - if (progress) - fprintf(stderr, "Deltifying %d objects.\n", nr_result); - - while (--i >= 0) { - struct object_entry *entry = list[i]; - struct unpacked *n = array + idx; - int j; - - if (!entry->preferred_base) - processed++; - - if (progress) { - unsigned percent = processed * 100 / nr_result; - if (percent != last_percent || progress_update) { - fprintf(stderr, "%4u%% (%u/%u) done\r", - percent, processed, nr_result); - progress_update = 0; - last_percent = percent; - } - } - - if (entry->delta) - /* This happens if we decided to reuse existing - * delta from a pack. "!no_reuse_delta &&" is implied. - */ - continue; - - if (entry->size < 50) - continue; - free_delta_index(n->index); - n->index = NULL; - free(n->data); - n->data = NULL; - n->entry = entry; - - j = window; - while (--j > 0) { - unsigned int other_idx = idx + j; - struct unpacked *m; - if (other_idx >= window) - other_idx -= window; - m = array + other_idx; - if (!m->entry) - break; - if (try_delta(n, m, depth) < 0) - break; - } - /* if we made n a delta, and if n is already at max - * depth, leaving it in the window is pointless. we - * should evict it first. - */ - if (entry->delta && depth <= entry->depth) - continue; - - idx++; - if (idx >= window) - idx = 0; - } - - if (progress) - fputc('\n', stderr); - - for (i = 0; i < window; ++i) { - free_delta_index(array[i].index); - free(array[i].data); - } - free(array); -} - -static void prepare_pack(int window, int depth) -{ - get_object_details(); - sorted_by_type = create_sorted_list(type_size_sort); - if (window && depth) - find_deltas(sorted_by_type, window+1, depth); -} - -static int reuse_cached_pack(unsigned char *sha1, int pack_to_stdout) -{ - static const char cache[] = "pack-cache/pack-%s.%s"; - char *cached_pack, *cached_idx; - int ifd, ofd, ifd_ix = -1; - - cached_pack = git_path(cache, sha1_to_hex(sha1), "pack"); - ifd = open(cached_pack, O_RDONLY); - if (ifd < 0) - return 0; - - if (!pack_to_stdout) { - cached_idx = git_path(cache, sha1_to_hex(sha1), "idx"); - ifd_ix = open(cached_idx, O_RDONLY); - if (ifd_ix < 0) { - close(ifd); - return 0; - } - } - - if (progress) - fprintf(stderr, "Reusing %d objects pack %s\n", nr_objects, - sha1_to_hex(sha1)); - - if (pack_to_stdout) { - if (copy_fd(ifd, 1)) - exit(1); - close(ifd); - } - else { - char name[PATH_MAX]; - snprintf(name, sizeof(name), - "%s-%s.%s", base_name, sha1_to_hex(sha1), "pack"); - ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (ofd < 0) - die("unable to open %s (%s)", name, strerror(errno)); - if (copy_fd(ifd, ofd)) - exit(1); - close(ifd); - - snprintf(name, sizeof(name), - "%s-%s.%s", base_name, sha1_to_hex(sha1), "idx"); - ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (ofd < 0) - die("unable to open %s (%s)", name, strerror(errno)); - if (copy_fd(ifd_ix, ofd)) - exit(1); - close(ifd_ix); - puts(sha1_to_hex(sha1)); - } - - return 1; -} - -static void setup_progress_signal(void) -{ - struct sigaction sa; - struct itimerval v; - - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = progress_interval; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sigaction(SIGALRM, &sa, NULL); - - v.it_interval.tv_sec = 1; - v.it_interval.tv_usec = 0; - v.it_value = v.it_interval; - setitimer(ITIMER_REAL, &v, NULL); -} - -static int git_pack_config(const char *k, const char *v) -{ - if(!strcmp(k, "pack.window")) { - window = git_config_int(k, v); - return 0; - } - return git_default_config(k, v); -} - -int main(int argc, char **argv) -{ - SHA_CTX ctx; - char line[40 + 1 + PATH_MAX + 2]; - int depth = 10, pack_to_stdout = 0; - struct object_entry **list; - int num_preferred_base = 0; - int i; - - setup_git_directory(); - git_config(git_pack_config); - - progress = isatty(2); - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (*arg == '-') { - if (!strcmp("--non-empty", arg)) { - non_empty = 1; - continue; - } - if (!strcmp("--local", arg)) { - local = 1; - continue; - } - if (!strcmp("--progress", arg)) { - progress = 1; - continue; - } - if (!strcmp("--incremental", arg)) { - incremental = 1; - continue; - } - if (!strncmp("--window=", arg, 9)) { - char *end; - window = strtoul(arg+9, &end, 0); - if (!arg[9] || *end) - usage(pack_usage); - continue; - } - if (!strncmp("--depth=", arg, 8)) { - char *end; - depth = strtoul(arg+8, &end, 0); - if (!arg[8] || *end) - usage(pack_usage); - continue; - } - if (!strcmp("--progress", arg)) { - progress = 1; - continue; - } - if (!strcmp("-q", arg)) { - progress = 0; - continue; - } - if (!strcmp("--no-reuse-delta", arg)) { - no_reuse_delta = 1; - continue; - } - if (!strcmp("--stdout", arg)) { - pack_to_stdout = 1; - continue; - } - usage(pack_usage); - } - if (base_name) - usage(pack_usage); - base_name = arg; - } - - if (pack_to_stdout != !base_name) - usage(pack_usage); - - prepare_packed_git(); - - if (progress) { - fprintf(stderr, "Generating pack...\n"); - setup_progress_signal(); - } - - for (;;) { - unsigned char sha1[20]; - unsigned hash; - - if (!fgets(line, sizeof(line), stdin)) { - if (feof(stdin)) - break; - if (!ferror(stdin)) - die("fgets returned NULL, not EOF, not error!"); - if (errno != EINTR) - die("fgets: %s", strerror(errno)); - clearerr(stdin); - continue; - } - - if (line[0] == '-') { - if (get_sha1_hex(line+1, sha1)) - die("expected edge sha1, got garbage:\n %s", - line+1); - if (num_preferred_base++ < window) - add_preferred_base(sha1); - continue; - } - if (get_sha1_hex(line, sha1)) - die("expected sha1, got garbage:\n %s", line); - hash = name_hash(line+41); - add_preferred_base_object(line+41, hash); - add_object_entry(sha1, hash, 0); - } - if (progress) - fprintf(stderr, "Done counting %d objects.\n", nr_objects); - sorted_by_sha = create_final_object_list(); - if (non_empty && !nr_result) - return 0; - - SHA1_Init(&ctx); - list = sorted_by_sha; - for (i = 0; i < nr_result; i++) { - struct object_entry *entry = *list++; - SHA1_Update(&ctx, entry->sha1, 20); - } - SHA1_Final(object_list_sha1, &ctx); - if (progress && (nr_objects != nr_result)) - fprintf(stderr, "Result has %d objects.\n", nr_result); - - if (reuse_cached_pack(object_list_sha1, pack_to_stdout)) - ; - else { - if (nr_result) - prepare_pack(window, depth); - if (progress && pack_to_stdout) { - /* the other end usually displays progress itself */ - struct itimerval v = {{0,},}; - setitimer(ITIMER_REAL, &v, NULL); - signal(SIGALRM, SIG_IGN ); - progress_update = 0; - } - write_pack_file(); - if (!pack_to_stdout) { - write_index_file(); - puts(sha1_to_hex(object_list_sha1)); - } - } - if (progress) - fprintf(stderr, "Total %d, written %d (delta %d), reused %d (delta %d)\n", - nr_result, written, written_delta, reused, reused_delta); - return 0; -} -- cgit v1.2.3 From 6441363079d85cf17aee21b8925e9745c8abda16 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 3 Aug 2006 17:24:37 +0200 Subject: Make git-unpack-objects a builtin Signed-off-by: Matthias Kestenholz Signed-off-by: Junio C Hamano --- Makefile | 7 +- builtin-unpack-objects.c | 310 ++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 1 + unpack-objects.c | 311 ----------------------------------------------- 5 files changed, 316 insertions(+), 314 deletions(-) create mode 100644 builtin-unpack-objects.c delete mode 100644 unpack-objects.c diff --git a/Makefile b/Makefile index 4fd62bcf6d..b108a8aa7a 100644 --- a/Makefile +++ b/Makefile @@ -182,7 +182,7 @@ PROGRAMS = \ git-send-pack$X git-shell$X \ git-show-index$X git-ssh-fetch$X \ git-ssh-upload$X git-unpack-file$X \ - git-unpack-objects$X git-update-server-info$X \ + git-update-server-info$X \ git-upload-pack$X git-verify-pack$X \ git-symbolic-ref$X \ git-pack-redundant$X git-var$X \ @@ -198,7 +198,8 @@ BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \ git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \ git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \ git-fmt-merge-msg$X git-prune$X git-mv$X git-prune-packed$X \ - git-repo-config$X git-name-rev$X git-pack-objects$X + git-repo-config$X git-name-rev$X git-pack-objects$X \ + git-unpack-objects$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -256,7 +257,7 @@ BUILTIN_OBJS = \ builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \ builtin-update-ref.o builtin-fmt-merge-msg.o builtin-prune.o \ builtin-mv.o builtin-prune-packed.o builtin-repo-config.o \ - builtin-name-rev.o builtin-pack-objects.o + builtin-name-rev.o builtin-pack-objects.o builtin-unpack-objects.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c new file mode 100644 index 0000000000..09d264d9f2 --- /dev/null +++ b/builtin-unpack-objects.c @@ -0,0 +1,310 @@ +#include "builtin.h" +#include "cache.h" +#include "object.h" +#include "delta.h" +#include "pack.h" +#include "blob.h" +#include "commit.h" +#include "tag.h" +#include "tree.h" + +#include + +static int dry_run, quiet; +static const char unpack_usage[] = "git-unpack-objects [-n] [-q] < pack-file"; + +/* We always read in 4kB chunks. */ +static unsigned char buffer[4096]; +static unsigned long offset, len, eof; +static SHA_CTX ctx; + +/* + * Make sure at least "min" bytes are available in the buffer, and + * return the pointer to the buffer. + */ +static void * fill(int min) +{ + if (min <= len) + return buffer + offset; + if (eof) + die("unable to fill input"); + if (min > sizeof(buffer)) + die("cannot fill %d bytes", min); + if (offset) { + SHA1_Update(&ctx, buffer, offset); + memcpy(buffer, buffer + offset, len); + offset = 0; + } + do { + int ret = xread(0, buffer + len, sizeof(buffer) - len); + if (ret <= 0) { + if (!ret) + die("early EOF"); + die("read error on input: %s", strerror(errno)); + } + len += ret; + } while (len < min); + return buffer; +} + +static void use(int bytes) +{ + if (bytes > len) + die("used more bytes than were available"); + len -= bytes; + offset += bytes; +} + +static void *get_data(unsigned long size) +{ + z_stream stream; + void *buf = xmalloc(size); + + memset(&stream, 0, sizeof(stream)); + + stream.next_out = buf; + stream.avail_out = size; + stream.next_in = fill(1); + stream.avail_in = len; + inflateInit(&stream); + + for (;;) { + int ret = inflate(&stream, 0); + use(len - stream.avail_in); + if (stream.total_out == size && ret == Z_STREAM_END) + break; + if (ret != Z_OK) + die("inflate returned %d\n", ret); + stream.next_in = fill(1); + stream.avail_in = len; + } + inflateEnd(&stream); + return buf; +} + +struct delta_info { + unsigned char base_sha1[20]; + unsigned long size; + void *delta; + struct delta_info *next; +}; + +static struct delta_info *delta_list; + +static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned long size) +{ + struct delta_info *info = xmalloc(sizeof(*info)); + + memcpy(info->base_sha1, base_sha1, 20); + info->size = size; + info->delta = delta; + info->next = delta_list; + delta_list = info; +} + +static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size); + +static void write_object(void *buf, unsigned long size, const char *type) +{ + unsigned char sha1[20]; + if (write_sha1_file(buf, size, type, sha1) < 0) + die("failed to write object"); + added_object(sha1, type, buf, size); +} + +static int resolve_delta(const char *type, + void *base, unsigned long base_size, + void *delta, unsigned long delta_size) +{ + void *result; + unsigned long result_size; + + result = patch_delta(base, base_size, + delta, delta_size, + &result_size); + if (!result) + die("failed to apply delta"); + free(delta); + write_object(result, result_size, type); + free(result); + return 0; +} + +static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size) +{ + struct delta_info **p = &delta_list; + struct delta_info *info; + + while ((info = *p) != NULL) { + if (!memcmp(info->base_sha1, sha1, 20)) { + *p = info->next; + p = &delta_list; + resolve_delta(type, data, size, info->delta, info->size); + free(info); + continue; + } + p = &info->next; + } +} + +static int unpack_non_delta_entry(enum object_type kind, unsigned long size) +{ + void *buf = get_data(size); + const char *type; + + switch (kind) { + case OBJ_COMMIT: type = commit_type; break; + case OBJ_TREE: type = tree_type; break; + case OBJ_BLOB: type = blob_type; break; + case OBJ_TAG: type = tag_type; break; + default: die("bad type %d", kind); + } + if (!dry_run) + write_object(buf, size, type); + free(buf); + return 0; +} + +static int unpack_delta_entry(unsigned long delta_size) +{ + void *delta_data, *base; + unsigned long base_size; + char type[20]; + unsigned char base_sha1[20]; + int result; + + memcpy(base_sha1, fill(20), 20); + use(20); + + delta_data = get_data(delta_size); + if (dry_run) { + free(delta_data); + return 0; + } + + if (!has_sha1_file(base_sha1)) { + add_delta_to_list(base_sha1, delta_data, delta_size); + return 0; + } + base = read_sha1_file(base_sha1, type, &base_size); + if (!base) + die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1)); + result = resolve_delta(type, base, base_size, delta_data, delta_size); + free(base); + return result; +} + +static void unpack_one(unsigned nr, unsigned total) +{ + unsigned shift; + unsigned char *pack, c; + unsigned long size; + enum object_type type; + + pack = fill(1); + c = *pack; + use(1); + type = (c >> 4) & 7; + size = (c & 15); + shift = 4; + while (c & 0x80) { + pack = fill(1); + c = *pack++; + use(1); + size += (c & 0x7f) << shift; + shift += 7; + } + if (!quiet) { + static unsigned long last_sec; + static unsigned last_percent; + struct timeval now; + unsigned percentage = (nr * 100) / total; + + gettimeofday(&now, NULL); + if (percentage != last_percent || now.tv_sec != last_sec) { + last_sec = now.tv_sec; + last_percent = percentage; + fprintf(stderr, "%4u%% (%u/%u) done\r", percentage, nr, total); + } + } + switch (type) { + case OBJ_COMMIT: + case OBJ_TREE: + case OBJ_BLOB: + case OBJ_TAG: + unpack_non_delta_entry(type, size); + return; + case OBJ_DELTA: + unpack_delta_entry(size); + return; + default: + die("bad object type %d", type); + } +} + +static void unpack_all(void) +{ + int i; + struct pack_header *hdr = fill(sizeof(struct pack_header)); + unsigned nr_objects = ntohl(hdr->hdr_entries); + + if (ntohl(hdr->hdr_signature) != PACK_SIGNATURE) + die("bad pack file"); + if (!pack_version_ok(hdr->hdr_version)) + die("unknown pack file version %d", ntohl(hdr->hdr_version)); + fprintf(stderr, "Unpacking %d objects\n", nr_objects); + + use(sizeof(struct pack_header)); + for (i = 0; i < nr_objects; i++) + unpack_one(i+1, nr_objects); + if (delta_list) + die("unresolved deltas left after unpacking"); +} + +int cmd_unpack_objects(int argc, const char **argv, const char *prefix) +{ + int i; + unsigned char sha1[20]; + + quiet = !isatty(2); + + for (i = 1 ; i < argc; i++) { + const char *arg = argv[i]; + + if (*arg == '-') { + if (!strcmp(arg, "-n")) { + dry_run = 1; + continue; + } + if (!strcmp(arg, "-q")) { + quiet = 1; + continue; + } + usage(unpack_usage); + } + + /* We don't take any non-flag arguments now.. Maybe some day */ + usage(unpack_usage); + } + SHA1_Init(&ctx); + unpack_all(); + SHA1_Update(&ctx, buffer, offset); + SHA1_Final(sha1, &ctx); + if (memcmp(fill(20), sha1, 20)) + die("final sha1 did not match"); + use(20); + + /* Write the last part of the buffer to stdout */ + while (len) { + int ret = xwrite(1, buffer + offset, len); + if (ret <= 0) + break; + len -= ret; + offset += ret; + } + + /* All done */ + if (!quiet) + fprintf(stderr, "\n"); + return 0; +} diff --git a/builtin.h b/builtin.h index bc57a04c05..af73c3c19e 100644 --- a/builtin.h +++ b/builtin.h @@ -51,6 +51,7 @@ extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_repo_config(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); +extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); diff --git a/git.c b/git.c index 5d58946f41..7c3a7f8fd1 100644 --- a/git.c +++ b/git.c @@ -267,6 +267,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "repo-config", cmd_repo_config }, { "name-rev", cmd_name_rev, NEEDS_PREFIX }, { "pack-objects", cmd_pack_objects, NEEDS_PREFIX }, + { "unpack-objects", cmd_unpack_objects, NEEDS_PREFIX }, }; int i; diff --git a/unpack-objects.c b/unpack-objects.c deleted file mode 100644 index 48c1ee7968..0000000000 --- a/unpack-objects.c +++ /dev/null @@ -1,311 +0,0 @@ -#include "cache.h" -#include "object.h" -#include "delta.h" -#include "pack.h" -#include "blob.h" -#include "commit.h" -#include "tag.h" -#include "tree.h" - -#include - -static int dry_run, quiet; -static const char unpack_usage[] = "git-unpack-objects [-n] [-q] < pack-file"; - -/* We always read in 4kB chunks. */ -static unsigned char buffer[4096]; -static unsigned long offset, len, eof; -static SHA_CTX ctx; - -/* - * Make sure at least "min" bytes are available in the buffer, and - * return the pointer to the buffer. - */ -static void * fill(int min) -{ - if (min <= len) - return buffer + offset; - if (eof) - die("unable to fill input"); - if (min > sizeof(buffer)) - die("cannot fill %d bytes", min); - if (offset) { - SHA1_Update(&ctx, buffer, offset); - memcpy(buffer, buffer + offset, len); - offset = 0; - } - do { - int ret = xread(0, buffer + len, sizeof(buffer) - len); - if (ret <= 0) { - if (!ret) - die("early EOF"); - die("read error on input: %s", strerror(errno)); - } - len += ret; - } while (len < min); - return buffer; -} - -static void use(int bytes) -{ - if (bytes > len) - die("used more bytes than were available"); - len -= bytes; - offset += bytes; -} - -static void *get_data(unsigned long size) -{ - z_stream stream; - void *buf = xmalloc(size); - - memset(&stream, 0, sizeof(stream)); - - stream.next_out = buf; - stream.avail_out = size; - stream.next_in = fill(1); - stream.avail_in = len; - inflateInit(&stream); - - for (;;) { - int ret = inflate(&stream, 0); - use(len - stream.avail_in); - if (stream.total_out == size && ret == Z_STREAM_END) - break; - if (ret != Z_OK) - die("inflate returned %d\n", ret); - stream.next_in = fill(1); - stream.avail_in = len; - } - inflateEnd(&stream); - return buf; -} - -struct delta_info { - unsigned char base_sha1[20]; - unsigned long size; - void *delta; - struct delta_info *next; -}; - -static struct delta_info *delta_list; - -static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned long size) -{ - struct delta_info *info = xmalloc(sizeof(*info)); - - memcpy(info->base_sha1, base_sha1, 20); - info->size = size; - info->delta = delta; - info->next = delta_list; - delta_list = info; -} - -static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size); - -static void write_object(void *buf, unsigned long size, const char *type) -{ - unsigned char sha1[20]; - if (write_sha1_file(buf, size, type, sha1) < 0) - die("failed to write object"); - added_object(sha1, type, buf, size); -} - -static int resolve_delta(const char *type, - void *base, unsigned long base_size, - void *delta, unsigned long delta_size) -{ - void *result; - unsigned long result_size; - - result = patch_delta(base, base_size, - delta, delta_size, - &result_size); - if (!result) - die("failed to apply delta"); - free(delta); - write_object(result, result_size, type); - free(result); - return 0; -} - -static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size) -{ - struct delta_info **p = &delta_list; - struct delta_info *info; - - while ((info = *p) != NULL) { - if (!memcmp(info->base_sha1, sha1, 20)) { - *p = info->next; - p = &delta_list; - resolve_delta(type, data, size, info->delta, info->size); - free(info); - continue; - } - p = &info->next; - } -} - -static int unpack_non_delta_entry(enum object_type kind, unsigned long size) -{ - void *buf = get_data(size); - const char *type; - - switch (kind) { - case OBJ_COMMIT: type = commit_type; break; - case OBJ_TREE: type = tree_type; break; - case OBJ_BLOB: type = blob_type; break; - case OBJ_TAG: type = tag_type; break; - default: die("bad type %d", kind); - } - if (!dry_run) - write_object(buf, size, type); - free(buf); - return 0; -} - -static int unpack_delta_entry(unsigned long delta_size) -{ - void *delta_data, *base; - unsigned long base_size; - char type[20]; - unsigned char base_sha1[20]; - int result; - - memcpy(base_sha1, fill(20), 20); - use(20); - - delta_data = get_data(delta_size); - if (dry_run) { - free(delta_data); - return 0; - } - - if (!has_sha1_file(base_sha1)) { - add_delta_to_list(base_sha1, delta_data, delta_size); - return 0; - } - base = read_sha1_file(base_sha1, type, &base_size); - if (!base) - die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1)); - result = resolve_delta(type, base, base_size, delta_data, delta_size); - free(base); - return result; -} - -static void unpack_one(unsigned nr, unsigned total) -{ - unsigned shift; - unsigned char *pack, c; - unsigned long size; - enum object_type type; - - pack = fill(1); - c = *pack; - use(1); - type = (c >> 4) & 7; - size = (c & 15); - shift = 4; - while (c & 0x80) { - pack = fill(1); - c = *pack++; - use(1); - size += (c & 0x7f) << shift; - shift += 7; - } - if (!quiet) { - static unsigned long last_sec; - static unsigned last_percent; - struct timeval now; - unsigned percentage = (nr * 100) / total; - - gettimeofday(&now, NULL); - if (percentage != last_percent || now.tv_sec != last_sec) { - last_sec = now.tv_sec; - last_percent = percentage; - fprintf(stderr, "%4u%% (%u/%u) done\r", percentage, nr, total); - } - } - switch (type) { - case OBJ_COMMIT: - case OBJ_TREE: - case OBJ_BLOB: - case OBJ_TAG: - unpack_non_delta_entry(type, size); - return; - case OBJ_DELTA: - unpack_delta_entry(size); - return; - default: - die("bad object type %d", type); - } -} - -static void unpack_all(void) -{ - int i; - struct pack_header *hdr = fill(sizeof(struct pack_header)); - unsigned nr_objects = ntohl(hdr->hdr_entries); - - if (ntohl(hdr->hdr_signature) != PACK_SIGNATURE) - die("bad pack file"); - if (!pack_version_ok(hdr->hdr_version)) - die("unknown pack file version %d", ntohl(hdr->hdr_version)); - fprintf(stderr, "Unpacking %d objects\n", nr_objects); - - use(sizeof(struct pack_header)); - for (i = 0; i < nr_objects; i++) - unpack_one(i+1, nr_objects); - if (delta_list) - die("unresolved deltas left after unpacking"); -} - -int main(int argc, char **argv) -{ - int i; - unsigned char sha1[20]; - - setup_git_directory(); - - quiet = !isatty(2); - - for (i = 1 ; i < argc; i++) { - const char *arg = argv[i]; - - if (*arg == '-') { - if (!strcmp(arg, "-n")) { - dry_run = 1; - continue; - } - if (!strcmp(arg, "-q")) { - quiet = 1; - continue; - } - usage(unpack_usage); - } - - /* We don't take any non-flag arguments now.. Maybe some day */ - usage(unpack_usage); - } - SHA1_Init(&ctx); - unpack_all(); - SHA1_Update(&ctx, buffer, offset); - SHA1_Final(sha1, &ctx); - if (memcmp(fill(20), sha1, 20)) - die("final sha1 did not match"); - use(20); - - /* Write the last part of the buffer to stdout */ - while (len) { - int ret = xwrite(1, buffer + offset, len); - if (ret <= 0) - break; - len -= ret; - offset += ret; - } - - /* All done */ - if (!quiet) - fprintf(stderr, "\n"); - return 0; -} -- cgit v1.2.3 From 640ce1052bbd6a8f1dd4d58beaa521d7592a0f02 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 3 Aug 2006 17:24:38 +0200 Subject: Make git-symbolic-ref a builtin Signed-off-by: Matthias Kestenholz Signed-off-by: Junio C Hamano --- Makefile | 6 +++--- builtin-symbolic-ref.c | 35 +++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 1 + symbolic-ref.c | 35 ----------------------------------- 5 files changed, 40 insertions(+), 38 deletions(-) create mode 100644 builtin-symbolic-ref.c delete mode 100644 symbolic-ref.c diff --git a/Makefile b/Makefile index b108a8aa7a..db59e00ede 100644 --- a/Makefile +++ b/Makefile @@ -184,7 +184,6 @@ PROGRAMS = \ git-ssh-upload$X git-unpack-file$X \ git-update-server-info$X \ git-upload-pack$X git-verify-pack$X \ - git-symbolic-ref$X \ git-pack-redundant$X git-var$X \ git-describe$X git-merge-tree$X git-blame$X git-imap-send$X @@ -199,7 +198,7 @@ BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \ git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \ git-fmt-merge-msg$X git-prune$X git-mv$X git-prune-packed$X \ git-repo-config$X git-name-rev$X git-pack-objects$X \ - git-unpack-objects$X + git-unpack-objects$X git-symbolic-ref$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -257,7 +256,8 @@ BUILTIN_OBJS = \ builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \ builtin-update-ref.o builtin-fmt-merge-msg.o builtin-prune.o \ builtin-mv.o builtin-prune-packed.o builtin-repo-config.o \ - builtin-name-rev.o builtin-pack-objects.o builtin-unpack-objects.o + builtin-name-rev.o builtin-pack-objects.o builtin-unpack-objects.o \ + builtin-symbolic-ref.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c new file mode 100644 index 0000000000..b4ec6f28ed --- /dev/null +++ b/builtin-symbolic-ref.c @@ -0,0 +1,35 @@ +#include "builtin.h" +#include "cache.h" + +static const char git_symbolic_ref_usage[] = +"git-symbolic-ref name [ref]"; + +static void check_symref(const char *HEAD) +{ + unsigned char sha1[20]; + const char *git_HEAD = strdup(git_path("%s", HEAD)); + const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0); + if (git_refs_heads_master) { + /* we want to strip the .git/ part */ + int pfxlen = strlen(git_HEAD) - strlen(HEAD); + puts(git_refs_heads_master + pfxlen); + } + else + die("No such ref: %s", HEAD); +} + +int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) +{ + git_config(git_default_config); + switch (argc) { + case 2: + check_symref(argv[1]); + break; + case 3: + create_symref(strdup(git_path("%s", argv[1])), argv[2]); + break; + default: + usage(git_symbolic_ref_usage); + } + return 0; +} diff --git a/builtin.h b/builtin.h index af73c3c19e..b767245c75 100644 --- a/builtin.h +++ b/builtin.h @@ -52,6 +52,7 @@ extern int cmd_repo_config(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); +extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); diff --git a/git.c b/git.c index 7c3a7f8fd1..e40e85935c 100644 --- a/git.c +++ b/git.c @@ -268,6 +268,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "name-rev", cmd_name_rev, NEEDS_PREFIX }, { "pack-objects", cmd_pack_objects, NEEDS_PREFIX }, { "unpack-objects", cmd_unpack_objects, NEEDS_PREFIX }, + { "symbolic-ref", cmd_symbolic_ref, NEEDS_PREFIX }, }; int i; diff --git a/symbolic-ref.c b/symbolic-ref.c deleted file mode 100644 index 193c87c174..0000000000 --- a/symbolic-ref.c +++ /dev/null @@ -1,35 +0,0 @@ -#include "cache.h" - -static const char git_symbolic_ref_usage[] = -"git-symbolic-ref name [ref]"; - -static void check_symref(const char *HEAD) -{ - unsigned char sha1[20]; - const char *git_HEAD = strdup(git_path("%s", HEAD)); - const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0); - if (git_refs_heads_master) { - /* we want to strip the .git/ part */ - int pfxlen = strlen(git_HEAD) - strlen(HEAD); - puts(git_refs_heads_master + pfxlen); - } - else - die("No such ref: %s", HEAD); -} - -int main(int argc, const char **argv) -{ - setup_git_directory(); - git_config(git_default_config); - switch (argc) { - case 2: - check_symref(argv[1]); - break; - case 3: - create_symref(strdup(git_path("%s", argv[1])), argv[2]); - break; - default: - usage(git_symbolic_ref_usage); - } - return 0; -} -- cgit v1.2.3 From e414156ab6e7869b29622ee3439e3c91361f5b0e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Aug 2006 01:23:19 -0700 Subject: Make git-checkout-index a builtin Signed-off-by: Junio C Hamano --- Makefile | 6 +- builtin-checkout-index.c | 309 ++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + checkout-index.c | 311 ----------------------------------------------- git.c | 1 + 5 files changed, 315 insertions(+), 313 deletions(-) create mode 100644 builtin-checkout-index.c delete mode 100644 checkout-index.c diff --git a/Makefile b/Makefile index db59e00ede..1a3605b123 100644 --- a/Makefile +++ b/Makefile @@ -173,7 +173,6 @@ SIMPLE_PROGRAMS = \ # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ - git-checkout-index$X \ git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ git-merge-base$X \ @@ -187,7 +186,9 @@ PROGRAMS = \ git-pack-redundant$X git-var$X \ git-describe$X git-merge-tree$X git-blame$X git-imap-send$X -BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \ +BUILT_INS = \ + git-checkout-index$X \ + git-log$X git-whatchanged$X git-show$X git-update-ref$X \ git-count-objects$X git-diff$X git-push$X git-mailsplit$X \ git-grep$X git-add$X git-rm$X git-rev-list$X git-stripspace$X \ git-check-ref-format$X git-rev-parse$X git-mailinfo$X \ @@ -245,6 +246,7 @@ LIB_OBJS = \ alloc.o merge-file.o path-list.o $(DIFF_OBJS) BUILTIN_OBJS = \ + builtin-checkout-index.o \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o \ builtin-rm.o builtin-init-db.o builtin-rev-parse.o \ diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c new file mode 100644 index 0000000000..29ea6fdc62 --- /dev/null +++ b/builtin-checkout-index.c @@ -0,0 +1,309 @@ +/* + * Check-out files from the "current cache directory" + * + * Copyright (C) 2005 Linus Torvalds + * + * Careful: order of argument flags does matter. For example, + * + * git-checkout-index -a -f file.c + * + * Will first check out all files listed in the cache (but not + * overwrite any old ones), and then force-checkout "file.c" a + * second time (ie that one _will_ overwrite any old contents + * with the same filename). + * + * Also, just doing "git-checkout-index" does nothing. You probably + * meant "git-checkout-index -a". And if you want to force it, you + * want "git-checkout-index -f -a". + * + * Intuitiveness is not the goal here. Repeatability is. The + * reason for the "no arguments means no work" thing is that + * from scripts you are supposed to be able to do things like + * + * find . -name '*.h' -print0 | xargs -0 git-checkout-index -f -- + * + * or: + * + * find . -name '*.h' -print0 | git-checkout-index -f -z --stdin + * + * which will force all existing *.h files to be replaced with + * their cached copies. If an empty command line implied "all", + * then this would force-refresh everything in the cache, which + * was not the point. + * + * Oh, and the "--" is just a good idea when you know the rest + * will be filenames. Just so that you wouldn't have a filename + * of "-a" causing problems (not possible in the above example, + * but get used to it in scripting!). + */ +#include "cache.h" +#include "strbuf.h" +#include "quote.h" +#include "cache-tree.h" + +#define CHECKOUT_ALL 4 +static int line_termination = '\n'; +static int checkout_stage; /* default to checkout stage0 */ +static int to_tempfile; +static char topath[4][MAXPATHLEN+1]; + +static struct checkout state; + +static void write_tempfile_record(const char *name, int prefix_length) +{ + int i; + + if (CHECKOUT_ALL == checkout_stage) { + for (i = 1; i < 4; i++) { + if (i > 1) + putchar(' '); + if (topath[i][0]) + fputs(topath[i], stdout); + else + putchar('.'); + } + } else + fputs(topath[checkout_stage], stdout); + + putchar('\t'); + write_name_quoted("", 0, name + prefix_length, + line_termination, stdout); + putchar(line_termination); + + for (i = 0; i < 4; i++) { + topath[i][0] = 0; + } +} + +static int checkout_file(const char *name, int prefix_length) +{ + int namelen = strlen(name); + int pos = cache_name_pos(name, namelen); + int has_same_name = 0; + int did_checkout = 0; + int errs = 0; + + if (pos < 0) + pos = -pos - 1; + + while (pos < active_nr) { + struct cache_entry *ce = active_cache[pos]; + if (ce_namelen(ce) != namelen || + memcmp(ce->name, name, namelen)) + break; + has_same_name = 1; + pos++; + if (ce_stage(ce) != checkout_stage + && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) + continue; + did_checkout = 1; + if (checkout_entry(ce, &state, + to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) + errs++; + } + + if (did_checkout) { + if (to_tempfile) + write_tempfile_record(name, prefix_length); + return errs > 0 ? -1 : 0; + } + + if (!state.quiet) { + fprintf(stderr, "git-checkout-index: %s ", name); + if (!has_same_name) + fprintf(stderr, "is not in the cache"); + else if (checkout_stage) + fprintf(stderr, "does not exist at stage %d", + checkout_stage); + else + fprintf(stderr, "is unmerged"); + fputc('\n', stderr); + } + return -1; +} + +static int checkout_all(const char *prefix, int prefix_length) +{ + int i, errs = 0; + struct cache_entry* last_ce = NULL; + + for (i = 0; i < active_nr ; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce) != checkout_stage + && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) + continue; + if (prefix && *prefix && + (ce_namelen(ce) <= prefix_length || + memcmp(prefix, ce->name, prefix_length))) + continue; + if (last_ce && to_tempfile) { + if (ce_namelen(last_ce) != ce_namelen(ce) + || memcmp(last_ce->name, ce->name, ce_namelen(ce))) + write_tempfile_record(last_ce->name, prefix_length); + } + if (checkout_entry(ce, &state, + to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) + errs++; + last_ce = ce; + } + if (last_ce && to_tempfile) + write_tempfile_record(last_ce->name, prefix_length); + if (errs) + /* we have already done our error reporting. + * exit with the same code as die(). + */ + exit(128); + return 0; +} + +static const char checkout_cache_usage[] = +"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]|all] [--prefix=] [--temp] [--] ..."; + +static struct lock_file lock_file; + +int cmd_checkout_index(int argc, const char **argv, const char *prefix) +{ + int i; + int newfd = -1; + int all = 0; + int read_from_stdin = 0; + int prefix_length; + + git_config(git_default_config); + state.base_dir = ""; + prefix_length = prefix ? strlen(prefix) : 0; + + if (read_cache() < 0) { + die("invalid cache"); + } + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-a") || !strcmp(arg, "--all")) { + all = 1; + continue; + } + if (!strcmp(arg, "-f") || !strcmp(arg, "--force")) { + state.force = 1; + continue; + } + if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) { + state.quiet = 1; + continue; + } + if (!strcmp(arg, "-n") || !strcmp(arg, "--no-create")) { + state.not_new = 1; + continue; + } + if (!strcmp(arg, "-u") || !strcmp(arg, "--index")) { + state.refresh_cache = 1; + if (newfd < 0) + newfd = hold_lock_file_for_update + (&lock_file, get_index_file()); + if (newfd < 0) + die("cannot open index.lock file."); + continue; + } + if (!strcmp(arg, "-z")) { + line_termination = 0; + continue; + } + if (!strcmp(arg, "--stdin")) { + if (i != argc - 1) + die("--stdin must be at the end"); + read_from_stdin = 1; + i++; /* do not consider arg as a file name */ + break; + } + if (!strcmp(arg, "--temp")) { + to_tempfile = 1; + continue; + } + if (!strncmp(arg, "--prefix=", 9)) { + state.base_dir = arg+9; + state.base_dir_len = strlen(state.base_dir); + continue; + } + if (!strncmp(arg, "--stage=", 8)) { + if (!strcmp(arg + 8, "all")) { + to_tempfile = 1; + checkout_stage = CHECKOUT_ALL; + } else { + int ch = arg[8]; + if ('1' <= ch && ch <= '3') + checkout_stage = arg[8] - '0'; + else + die("stage should be between 1 and 3 or all"); + } + continue; + } + if (arg[0] == '-') + usage(checkout_cache_usage); + break; + } + + if (state.base_dir_len || to_tempfile) { + /* when --prefix is specified we do not + * want to update cache. + */ + if (state.refresh_cache) { + close(newfd); newfd = -1; + rollback_lock_file(&lock_file); + } + state.refresh_cache = 0; + } + + /* Check out named files first */ + for ( ; i < argc; i++) { + const char *arg = argv[i]; + const char *p; + + if (all) + die("git-checkout-index: don't mix '--all' and explicit filenames"); + if (read_from_stdin) + die("git-checkout-index: don't mix '--stdin' and explicit filenames"); + p = prefix_path(prefix, prefix_length, arg); + checkout_file(p, prefix_length); + if (p < arg || p > arg + strlen(arg)) + free((char*)p); + } + + if (read_from_stdin) { + struct strbuf buf; + if (all) + die("git-checkout-index: don't mix '--all' and '--stdin'"); + strbuf_init(&buf); + while (1) { + char *path_name; + const char *p; + + read_line(&buf, stdin, line_termination); + if (buf.eof) + break; + if (line_termination && buf.buf[0] == '"') + path_name = unquote_c_style(buf.buf, NULL); + else + path_name = buf.buf; + p = prefix_path(prefix, prefix_length, path_name); + checkout_file(p, prefix_length); + if (p < path_name || p > path_name + strlen(path_name)) + free((char *)p); + if (path_name != buf.buf) + free(path_name); + } + } + + if (all) + checkout_all(prefix, prefix_length); + + if (0 <= newfd && + (write_cache(newfd, active_cache, active_nr) || + close(newfd) || commit_lock_file(&lock_file))) + die("Unable to write new index file"); + return 0; +} diff --git a/builtin.h b/builtin.h index b767245c75..3a24637b11 100644 --- a/builtin.h +++ b/builtin.h @@ -53,6 +53,7 @@ extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); +extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); diff --git a/checkout-index.c b/checkout-index.c deleted file mode 100644 index 61152f34b7..0000000000 --- a/checkout-index.c +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Check-out files from the "current cache directory" - * - * Copyright (C) 2005 Linus Torvalds - * - * Careful: order of argument flags does matter. For example, - * - * git-checkout-index -a -f file.c - * - * Will first check out all files listed in the cache (but not - * overwrite any old ones), and then force-checkout "file.c" a - * second time (ie that one _will_ overwrite any old contents - * with the same filename). - * - * Also, just doing "git-checkout-index" does nothing. You probably - * meant "git-checkout-index -a". And if you want to force it, you - * want "git-checkout-index -f -a". - * - * Intuitiveness is not the goal here. Repeatability is. The - * reason for the "no arguments means no work" thing is that - * from scripts you are supposed to be able to do things like - * - * find . -name '*.h' -print0 | xargs -0 git-checkout-index -f -- - * - * or: - * - * find . -name '*.h' -print0 | git-checkout-index -f -z --stdin - * - * which will force all existing *.h files to be replaced with - * their cached copies. If an empty command line implied "all", - * then this would force-refresh everything in the cache, which - * was not the point. - * - * Oh, and the "--" is just a good idea when you know the rest - * will be filenames. Just so that you wouldn't have a filename - * of "-a" causing problems (not possible in the above example, - * but get used to it in scripting!). - */ -#include "cache.h" -#include "strbuf.h" -#include "quote.h" -#include "cache-tree.h" - -#define CHECKOUT_ALL 4 -static const char *prefix; -static int prefix_length; -static int line_termination = '\n'; -static int checkout_stage; /* default to checkout stage0 */ -static int to_tempfile; -static char topath[4][MAXPATHLEN+1]; - -static struct checkout state; - -static void write_tempfile_record (const char *name) -{ - int i; - - if (CHECKOUT_ALL == checkout_stage) { - for (i = 1; i < 4; i++) { - if (i > 1) - putchar(' '); - if (topath[i][0]) - fputs(topath[i], stdout); - else - putchar('.'); - } - } else - fputs(topath[checkout_stage], stdout); - - putchar('\t'); - write_name_quoted("", 0, name + prefix_length, - line_termination, stdout); - putchar(line_termination); - - for (i = 0; i < 4; i++) { - topath[i][0] = 0; - } -} - -static int checkout_file(const char *name) -{ - int namelen = strlen(name); - int pos = cache_name_pos(name, namelen); - int has_same_name = 0; - int did_checkout = 0; - int errs = 0; - - if (pos < 0) - pos = -pos - 1; - - while (pos < active_nr) { - struct cache_entry *ce = active_cache[pos]; - if (ce_namelen(ce) != namelen || - memcmp(ce->name, name, namelen)) - break; - has_same_name = 1; - pos++; - if (ce_stage(ce) != checkout_stage - && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) - continue; - did_checkout = 1; - if (checkout_entry(ce, &state, - to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) - errs++; - } - - if (did_checkout) { - if (to_tempfile) - write_tempfile_record(name); - return errs > 0 ? -1 : 0; - } - - if (!state.quiet) { - fprintf(stderr, "git-checkout-index: %s ", name); - if (!has_same_name) - fprintf(stderr, "is not in the cache"); - else if (checkout_stage) - fprintf(stderr, "does not exist at stage %d", - checkout_stage); - else - fprintf(stderr, "is unmerged"); - fputc('\n', stderr); - } - return -1; -} - -static int checkout_all(void) -{ - int i, errs = 0; - struct cache_entry* last_ce = NULL; - - for (i = 0; i < active_nr ; i++) { - struct cache_entry *ce = active_cache[i]; - if (ce_stage(ce) != checkout_stage - && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) - continue; - if (prefix && *prefix && - (ce_namelen(ce) <= prefix_length || - memcmp(prefix, ce->name, prefix_length))) - continue; - if (last_ce && to_tempfile) { - if (ce_namelen(last_ce) != ce_namelen(ce) - || memcmp(last_ce->name, ce->name, ce_namelen(ce))) - write_tempfile_record(last_ce->name); - } - if (checkout_entry(ce, &state, - to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) - errs++; - last_ce = ce; - } - if (last_ce && to_tempfile) - write_tempfile_record(last_ce->name); - if (errs) - /* we have already done our error reporting. - * exit with the same code as die(). - */ - exit(128); - return 0; -} - -static const char checkout_cache_usage[] = -"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]|all] [--prefix=] [--temp] [--] ..."; - -static struct lock_file lock_file; - -int main(int argc, char **argv) -{ - int i; - int newfd = -1; - int all = 0; - int read_from_stdin = 0; - - state.base_dir = ""; - prefix = setup_git_directory(); - git_config(git_default_config); - prefix_length = prefix ? strlen(prefix) : 0; - - if (read_cache() < 0) { - die("invalid cache"); - } - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-a") || !strcmp(arg, "--all")) { - all = 1; - continue; - } - if (!strcmp(arg, "-f") || !strcmp(arg, "--force")) { - state.force = 1; - continue; - } - if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) { - state.quiet = 1; - continue; - } - if (!strcmp(arg, "-n") || !strcmp(arg, "--no-create")) { - state.not_new = 1; - continue; - } - if (!strcmp(arg, "-u") || !strcmp(arg, "--index")) { - state.refresh_cache = 1; - if (newfd < 0) - newfd = hold_lock_file_for_update - (&lock_file, get_index_file()); - if (newfd < 0) - die("cannot open index.lock file."); - continue; - } - if (!strcmp(arg, "-z")) { - line_termination = 0; - continue; - } - if (!strcmp(arg, "--stdin")) { - if (i != argc - 1) - die("--stdin must be at the end"); - read_from_stdin = 1; - i++; /* do not consider arg as a file name */ - break; - } - if (!strcmp(arg, "--temp")) { - to_tempfile = 1; - continue; - } - if (!strncmp(arg, "--prefix=", 9)) { - state.base_dir = arg+9; - state.base_dir_len = strlen(state.base_dir); - continue; - } - if (!strncmp(arg, "--stage=", 8)) { - if (!strcmp(arg + 8, "all")) { - to_tempfile = 1; - checkout_stage = CHECKOUT_ALL; - } else { - int ch = arg[8]; - if ('1' <= ch && ch <= '3') - checkout_stage = arg[8] - '0'; - else - die("stage should be between 1 and 3 or all"); - } - continue; - } - if (arg[0] == '-') - usage(checkout_cache_usage); - break; - } - - if (state.base_dir_len || to_tempfile) { - /* when --prefix is specified we do not - * want to update cache. - */ - if (state.refresh_cache) { - close(newfd); newfd = -1; - rollback_lock_file(&lock_file); - } - state.refresh_cache = 0; - } - - /* Check out named files first */ - for ( ; i < argc; i++) { - const char *arg = argv[i]; - const char *p; - - if (all) - die("git-checkout-index: don't mix '--all' and explicit filenames"); - if (read_from_stdin) - die("git-checkout-index: don't mix '--stdin' and explicit filenames"); - p = prefix_path(prefix, prefix_length, arg); - checkout_file(p); - if (p < arg || p > arg + strlen(arg)) - free((char*)p); - } - - if (read_from_stdin) { - struct strbuf buf; - if (all) - die("git-checkout-index: don't mix '--all' and '--stdin'"); - strbuf_init(&buf); - while (1) { - char *path_name; - const char *p; - - read_line(&buf, stdin, line_termination); - if (buf.eof) - break; - if (line_termination && buf.buf[0] == '"') - path_name = unquote_c_style(buf.buf, NULL); - else - path_name = buf.buf; - p = prefix_path(prefix, prefix_length, path_name); - checkout_file(p); - if (p < path_name || p > path_name + strlen(path_name)) - free((char *)p); - if (path_name != buf.buf) - free(path_name); - } - } - - if (all) - checkout_all(); - - if (0 <= newfd && - (write_cache(newfd, active_cache, active_nr) || - close(newfd) || commit_lock_file(&lock_file))) - die("Unable to write new index file"); - return 0; -} diff --git a/git.c b/git.c index e40e85935c..07b6d3e9dd 100644 --- a/git.c +++ b/git.c @@ -269,6 +269,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "pack-objects", cmd_pack_objects, NEEDS_PREFIX }, { "unpack-objects", cmd_unpack_objects, NEEDS_PREFIX }, { "symbolic-ref", cmd_symbolic_ref, NEEDS_PREFIX }, + { "checkout-index", cmd_checkout_index, NEEDS_PREFIX }, }; int i; -- cgit v1.2.3 From f754fa9c5480c4556ad268fb5014c4b96cc7b9dc Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Aug 2006 01:51:04 -0700 Subject: builtins: Makefile clean-up This cleans up the build procedure for built-in commands by: - generating mostly redundant definition of BUILT_INS from BUILTIN_OBJS in the Makefile, - renaming a few files to make the above possible, and - sorting the built-in command table in git.c. It might be a good idea to binary search (or perfect hash) the built-in command table, but that can be done later when somebody feels like. Signed-off-by: Junio C Hamano --- Makefile | 74 +++++++++------ builtin-count-objects.c | 125 ++++++++++++++++++++++++++ builtin-count.c | 125 -------------------------- builtin-help.c | 234 ------------------------------------------------ builtin.h | 80 ++++++++--------- git.c | 68 +++++++------- help.c | 234 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 475 insertions(+), 465 deletions(-) create mode 100644 builtin-count-objects.c delete mode 100644 builtin-count.c delete mode 100644 builtin-help.c create mode 100644 help.c diff --git a/Makefile b/Makefile index 1a3605b123..dc39f72f1e 100644 --- a/Makefile +++ b/Makefile @@ -187,19 +187,9 @@ PROGRAMS = \ git-describe$X git-merge-tree$X git-blame$X git-imap-send$X BUILT_INS = \ - git-checkout-index$X \ - git-log$X git-whatchanged$X git-show$X git-update-ref$X \ - git-count-objects$X git-diff$X git-push$X git-mailsplit$X \ - git-grep$X git-add$X git-rm$X git-rev-list$X git-stripspace$X \ - git-check-ref-format$X git-rev-parse$X git-mailinfo$X \ - git-init-db$X git-tar-tree$X git-upload-tar$X git-format-patch$X \ - git-ls-files$X git-ls-tree$X git-get-tar-commit-id$X \ - git-read-tree$X git-commit-tree$X git-write-tree$X \ - git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \ - git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \ - git-fmt-merge-msg$X git-prune$X git-mv$X git-prune-packed$X \ - git-repo-config$X git-name-rev$X git-pack-objects$X \ - git-unpack-objects$X git-symbolic-ref$X + git-format-patch$X git-show$X git-whatchanged$X \ + git-get-tar-commit-id$X \ + $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -228,7 +218,7 @@ LIB_H = \ blob.h cache.h commit.h csum-file.h delta.h \ diff.h object.h pack.h pkt-line.h quote.h refs.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ - tree-walk.h log-tree.h dir.h path-list.h + tree-walk.h log-tree.h dir.h path-list.h builtin.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -243,23 +233,49 @@ LIB_OBJS = \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ - alloc.o merge-file.o path-list.o $(DIFF_OBJS) + alloc.o merge-file.o path-list.o help.o $(DIFF_OBJS) BUILTIN_OBJS = \ + builtin-add.o \ + builtin-apply.o \ + builtin-cat-file.o \ builtin-checkout-index.o \ - builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ - builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o \ - builtin-rm.o builtin-init-db.o builtin-rev-parse.o \ - builtin-tar-tree.o builtin-upload-tar.o builtin-update-index.o \ - builtin-ls-files.o builtin-ls-tree.o builtin-write-tree.o \ - builtin-read-tree.o builtin-commit-tree.o builtin-mailinfo.o \ - builtin-apply.o builtin-show-branch.o builtin-diff-files.o \ - builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \ - builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \ - builtin-update-ref.o builtin-fmt-merge-msg.o builtin-prune.o \ - builtin-mv.o builtin-prune-packed.o builtin-repo-config.o \ - builtin-name-rev.o builtin-pack-objects.o builtin-unpack-objects.o \ - builtin-symbolic-ref.o + builtin-check-ref-format.o \ + builtin-commit-tree.o \ + builtin-count-objects.o \ + builtin-diff.o \ + builtin-diff-files.o \ + builtin-diff-index.o \ + builtin-diff-stages.o \ + builtin-diff-tree.o \ + builtin-fmt-merge-msg.o \ + builtin-grep.o \ + builtin-init-db.o \ + builtin-log.o \ + builtin-ls-files.o \ + builtin-ls-tree.o \ + builtin-mailinfo.o \ + builtin-mailsplit.o \ + builtin-mv.o \ + builtin-name-rev.o \ + builtin-pack-objects.o \ + builtin-prune.o \ + builtin-prune-packed.o \ + builtin-push.o \ + builtin-read-tree.o \ + builtin-repo-config.o \ + builtin-rev-list.o \ + builtin-rev-parse.o \ + builtin-rm.o \ + builtin-show-branch.o \ + builtin-stripspace.o \ + builtin-symbolic-ref.o \ + builtin-tar-tree.o \ + builtin-unpack-objects.o \ + builtin-update-index.o \ + builtin-update-ref.o \ + builtin-upload-tar.o \ + builtin-write-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -540,7 +556,7 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \ $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) -builtin-help.o: common-cmds.h +help.o: common-cmds.h $(BUILT_INS): git$X rm -f $@ && ln git$X $@ diff --git a/builtin-count-objects.c b/builtin-count-objects.c new file mode 100644 index 0000000000..1d3729aa99 --- /dev/null +++ b/builtin-count-objects.c @@ -0,0 +1,125 @@ +/* + * Builtin "git count-objects". + * + * Copyright (c) 2006 Junio C Hamano + */ + +#include "cache.h" +#include "builtin.h" + +static const char count_objects_usage[] = "git-count-objects [-v]"; + +static void count_objects(DIR *d, char *path, int len, int verbose, + unsigned long *loose, + unsigned long *loose_size, + unsigned long *packed_loose, + unsigned long *garbage) +{ + struct dirent *ent; + while ((ent = readdir(d)) != NULL) { + char hex[41]; + unsigned char sha1[20]; + const char *cp; + int bad = 0; + + if ((ent->d_name[0] == '.') && + (ent->d_name[1] == 0 || + ((ent->d_name[1] == '.') && (ent->d_name[2] == 0)))) + continue; + for (cp = ent->d_name; *cp; cp++) { + int ch = *cp; + if (('0' <= ch && ch <= '9') || + ('a' <= ch && ch <= 'f')) + continue; + bad = 1; + break; + } + if (cp - ent->d_name != 38) + bad = 1; + else { + struct stat st; + memcpy(path + len + 3, ent->d_name, 38); + path[len + 2] = '/'; + path[len + 41] = 0; + if (lstat(path, &st) || !S_ISREG(st.st_mode)) + bad = 1; + else + (*loose_size) += st.st_blocks; + } + if (bad) { + if (verbose) { + error("garbage found: %.*s/%s", + len + 2, path, ent->d_name); + (*garbage)++; + } + continue; + } + (*loose)++; + if (!verbose) + continue; + memcpy(hex, path+len, 2); + memcpy(hex+2, ent->d_name, 38); + hex[40] = 0; + if (get_sha1_hex(hex, sha1)) + die("internal error"); + if (has_sha1_pack(sha1)) + (*packed_loose)++; + } +} + +int cmd_count_objects(int ac, const char **av, const char *prefix) +{ + int i; + int verbose = 0; + const char *objdir = get_object_directory(); + int len = strlen(objdir); + char *path = xmalloc(len + 50); + unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0; + unsigned long loose_size = 0; + + for (i = 1; i < ac; i++) { + const char *arg = av[i]; + if (*arg != '-') + break; + else if (!strcmp(arg, "-v")) + verbose = 1; + else + usage(count_objects_usage); + } + + /* we do not take arguments other than flags for now */ + if (i < ac) + usage(count_objects_usage); + memcpy(path, objdir, len); + if (len && objdir[len-1] != '/') + path[len++] = '/'; + for (i = 0; i < 256; i++) { + DIR *d; + sprintf(path + len, "%02x", i); + d = opendir(path); + if (!d) + continue; + count_objects(d, path, len, verbose, + &loose, &loose_size, &packed_loose, &garbage); + closedir(d); + } + if (verbose) { + struct packed_git *p; + if (!packed_git) + prepare_packed_git(); + for (p = packed_git; p; p = p->next) { + if (!p->pack_local) + continue; + packed += num_packed_objects(p); + } + printf("count: %lu\n", loose); + printf("size: %lu\n", loose_size / 2); + printf("in-pack: %lu\n", packed); + printf("prune-packable: %lu\n", packed_loose); + printf("garbage: %lu\n", garbage); + } + else + printf("%lu objects, %lu kilobytes\n", + loose, loose_size / 2); + return 0; +} diff --git a/builtin-count.c b/builtin-count.c deleted file mode 100644 index 1d3729aa99..0000000000 --- a/builtin-count.c +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Builtin "git count-objects". - * - * Copyright (c) 2006 Junio C Hamano - */ - -#include "cache.h" -#include "builtin.h" - -static const char count_objects_usage[] = "git-count-objects [-v]"; - -static void count_objects(DIR *d, char *path, int len, int verbose, - unsigned long *loose, - unsigned long *loose_size, - unsigned long *packed_loose, - unsigned long *garbage) -{ - struct dirent *ent; - while ((ent = readdir(d)) != NULL) { - char hex[41]; - unsigned char sha1[20]; - const char *cp; - int bad = 0; - - if ((ent->d_name[0] == '.') && - (ent->d_name[1] == 0 || - ((ent->d_name[1] == '.') && (ent->d_name[2] == 0)))) - continue; - for (cp = ent->d_name; *cp; cp++) { - int ch = *cp; - if (('0' <= ch && ch <= '9') || - ('a' <= ch && ch <= 'f')) - continue; - bad = 1; - break; - } - if (cp - ent->d_name != 38) - bad = 1; - else { - struct stat st; - memcpy(path + len + 3, ent->d_name, 38); - path[len + 2] = '/'; - path[len + 41] = 0; - if (lstat(path, &st) || !S_ISREG(st.st_mode)) - bad = 1; - else - (*loose_size) += st.st_blocks; - } - if (bad) { - if (verbose) { - error("garbage found: %.*s/%s", - len + 2, path, ent->d_name); - (*garbage)++; - } - continue; - } - (*loose)++; - if (!verbose) - continue; - memcpy(hex, path+len, 2); - memcpy(hex+2, ent->d_name, 38); - hex[40] = 0; - if (get_sha1_hex(hex, sha1)) - die("internal error"); - if (has_sha1_pack(sha1)) - (*packed_loose)++; - } -} - -int cmd_count_objects(int ac, const char **av, const char *prefix) -{ - int i; - int verbose = 0; - const char *objdir = get_object_directory(); - int len = strlen(objdir); - char *path = xmalloc(len + 50); - unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0; - unsigned long loose_size = 0; - - for (i = 1; i < ac; i++) { - const char *arg = av[i]; - if (*arg != '-') - break; - else if (!strcmp(arg, "-v")) - verbose = 1; - else - usage(count_objects_usage); - } - - /* we do not take arguments other than flags for now */ - if (i < ac) - usage(count_objects_usage); - memcpy(path, objdir, len); - if (len && objdir[len-1] != '/') - path[len++] = '/'; - for (i = 0; i < 256; i++) { - DIR *d; - sprintf(path + len, "%02x", i); - d = opendir(path); - if (!d) - continue; - count_objects(d, path, len, verbose, - &loose, &loose_size, &packed_loose, &garbage); - closedir(d); - } - if (verbose) { - struct packed_git *p; - if (!packed_git) - prepare_packed_git(); - for (p = packed_git; p; p = p->next) { - if (!p->pack_local) - continue; - packed += num_packed_objects(p); - } - printf("count: %lu\n", loose); - printf("size: %lu\n", loose_size / 2); - printf("in-pack: %lu\n", packed); - printf("prune-packable: %lu\n", packed_loose); - printf("garbage: %lu\n", garbage); - } - else - printf("%lu objects, %lu kilobytes\n", - loose, loose_size / 2); - return 0; -} diff --git a/builtin-help.c b/builtin-help.c deleted file mode 100644 index fb731cc934..0000000000 --- a/builtin-help.c +++ /dev/null @@ -1,234 +0,0 @@ -/* - * builtin-help.c - * - * Builtin help-related commands (help, usage, version) - */ -#include -#include "cache.h" -#include "builtin.h" -#include "exec_cmd.h" -#include "common-cmds.h" - - -/* most GUI terminals set COLUMNS (although some don't export it) */ -static int term_columns(void) -{ - char *col_string = getenv("COLUMNS"); - int n_cols = 0; - - if (col_string && (n_cols = atoi(col_string)) > 0) - return n_cols; - -#ifdef TIOCGWINSZ - { - struct winsize ws; - if (!ioctl(1, TIOCGWINSZ, &ws)) { - if (ws.ws_col) - return ws.ws_col; - } - } -#endif - - return 80; -} - -static void oom(void) -{ - fprintf(stderr, "git: out of memory\n"); - exit(1); -} - -static inline void mput_char(char c, unsigned int num) -{ - while(num--) - putchar(c); -} - -static struct cmdname { - size_t len; - char name[1]; -} **cmdname; -static int cmdname_alloc, cmdname_cnt; - -static void add_cmdname(const char *name, int len) -{ - struct cmdname *ent; - if (cmdname_alloc <= cmdname_cnt) { - cmdname_alloc = cmdname_alloc + 200; - cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname)); - if (!cmdname) - oom(); - } - ent = malloc(sizeof(*ent) + len); - if (!ent) - oom(); - ent->len = len; - memcpy(ent->name, name, len); - ent->name[len] = 0; - cmdname[cmdname_cnt++] = ent; -} - -static int cmdname_compare(const void *a_, const void *b_) -{ - struct cmdname *a = *(struct cmdname **)a_; - struct cmdname *b = *(struct cmdname **)b_; - return strcmp(a->name, b->name); -} - -static void pretty_print_string_list(struct cmdname **cmdname, int longest) -{ - int cols = 1, rows; - int space = longest + 1; /* min 1 SP between words */ - int max_cols = term_columns() - 1; /* don't print *on* the edge */ - int i, j; - - if (space < max_cols) - cols = max_cols / space; - rows = (cmdname_cnt + cols - 1) / cols; - - qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare); - - for (i = 0; i < rows; i++) { - printf(" "); - - for (j = 0; j < cols; j++) { - int n = j * rows + i; - int size = space; - if (n >= cmdname_cnt) - break; - if (j == cols-1 || n + rows >= cmdname_cnt) - size = 1; - printf("%-*s", size, cmdname[n]->name); - } - putchar('\n'); - } -} - -static void list_commands(const char *exec_path, const char *pattern) -{ - unsigned int longest = 0; - char path[PATH_MAX]; - int dirlen; - DIR *dir = opendir(exec_path); - struct dirent *de; - - if (!dir) { - fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno)); - exit(1); - } - - dirlen = strlen(exec_path); - if (PATH_MAX - 20 < dirlen) { - fprintf(stderr, "git: insanely long exec-path '%s'\n", - exec_path); - exit(1); - } - - memcpy(path, exec_path, dirlen); - path[dirlen++] = '/'; - - while ((de = readdir(dir)) != NULL) { - struct stat st; - int entlen; - - if (strncmp(de->d_name, "git-", 4)) - continue; - strcpy(path+dirlen, de->d_name); - if (stat(path, &st) || /* stat, not lstat */ - !S_ISREG(st.st_mode) || - !(st.st_mode & S_IXUSR)) - continue; - - entlen = strlen(de->d_name); - if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe")) - entlen -= 4; - - if (longest < entlen) - longest = entlen; - - add_cmdname(de->d_name + 4, entlen-4); - } - closedir(dir); - - printf("git commands available in '%s'\n", exec_path); - printf("----------------------------"); - mput_char('-', strlen(exec_path)); - putchar('\n'); - pretty_print_string_list(cmdname, longest - 4); - putchar('\n'); -} - -static void list_common_cmds_help(void) -{ - int i, longest = 0; - - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - if (longest < strlen(common_cmds[i].name)) - longest = strlen(common_cmds[i].name); - } - - puts("The most commonly used git commands are:"); - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - printf(" %s", common_cmds[i].name); - mput_char(' ', longest - strlen(common_cmds[i].name) + 4); - puts(common_cmds[i].help); - } - puts("(use 'git help -a' to get a list of all installed git commands)"); -} - -static void show_man_page(const char *git_cmd) -{ - const char *page; - - if (!strncmp(git_cmd, "git", 3)) - page = git_cmd; - else { - int page_len = strlen(git_cmd) + 4; - char *p = malloc(page_len + 1); - strcpy(p, "git-"); - strcpy(p + 4, git_cmd); - p[page_len] = 0; - page = p; - } - - execlp("man", "man", page, NULL); -} - -void help_unknown_cmd(const char *cmd) -{ - printf("git: '%s' is not a git-command\n\n", cmd); - list_common_cmds_help(); - exit(1); -} - -int cmd_version(int argc, const char **argv, const char *prefix) -{ - printf("git version %s\n", git_version_string); - return 0; -} - -int cmd_help(int argc, const char **argv, const char *prefix) -{ - const char *help_cmd = argc > 1 ? argv[1] : NULL; - const char *exec_path = git_exec_path(); - - if (!help_cmd) { - printf("usage: %s\n\n", git_usage_string); - list_common_cmds_help(); - exit(1); - } - - else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) { - printf("usage: %s\n\n", git_usage_string); - if(exec_path) - list_commands(exec_path, "git-*"); - exit(1); - } - - else - show_man_page(help_cmd); - - return 0; -} - - diff --git a/builtin.h b/builtin.h index 3a24637b11..c0bdb051bd 100644 --- a/builtin.h +++ b/builtin.h @@ -8,62 +8,56 @@ extern const char git_version_string[]; extern const char git_usage_string[]; extern void help_unknown_cmd(const char *cmd); +extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const char *msg, const char *patch); +extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip); +extern void stripspace(FILE *in, FILE *out); +extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); -extern int cmd_help(int argc, const char **argv, const char *prefix); -extern int cmd_version(int argc, const char **argv, const char *prefix); - -extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); -extern int cmd_show(int argc, const char **argv, const char *prefix); -extern int cmd_log(int argc, const char **argv, const char *prefix); -extern int cmd_diff(int argc, const char **argv, const char *prefix); -extern int cmd_format_patch(int argc, const char **argv, const char *prefix); -extern int cmd_count_objects(int argc, const char **argv, const char *prefix); - -extern int cmd_prune(int argc, const char **argv, const char *prefix); -extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); - -extern int cmd_push(int argc, const char **argv, const char *prefix); -extern int cmd_grep(int argc, const char **argv, const char *prefix); -extern int cmd_rm(int argc, const char **argv, const char *prefix); extern int cmd_add(int argc, const char **argv, const char *prefix); -extern int cmd_rev_list(int argc, const char **argv, const char *prefix); +extern int cmd_apply(int argc, const char **argv, const char *prefix); +extern int cmd_cat_file(int argc, const char **argv, const char *prefix); +extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); -extern int cmd_init_db(int argc, const char **argv, const char *prefix); -extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); -extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); -extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); -extern int cmd_ls_files(int argc, const char **argv, const char *prefix); -extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); -extern int cmd_read_tree(int argc, const char **argv, const char *prefix); extern int cmd_commit_tree(int argc, const char **argv, const char *prefix); -extern int cmd_apply(int argc, const char **argv, const char *prefix); -extern int cmd_show_branch(int argc, const char **argv, const char *prefix); +extern int cmd_count_objects(int argc, const char **argv, const char *prefix); extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); +extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_stages(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); -extern int cmd_cat_file(int argc, const char **argv, const char *prefix); -extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); -extern int cmd_update_index(int argc, const char **argv, const char *prefix); -extern int cmd_update_ref(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_format_patch(int argc, const char **argv, const char *prefix); +extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); +extern int cmd_grep(int argc, const char **argv, const char *prefix); +extern int cmd_help(int argc, const char **argv, const char *prefix); +extern int cmd_init_db(int argc, const char **argv, const char *prefix); +extern int cmd_log(int argc, const char **argv, const char *prefix); +extern int cmd_ls_files(int argc, const char **argv, const char *prefix); +extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); +extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); +extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); -extern int cmd_repo_config(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); -extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); +extern int cmd_prune(int argc, const char **argv, const char *prefix); +extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); +extern int cmd_push(int argc, const char **argv, const char *prefix); +extern int cmd_read_tree(int argc, const char **argv, const char *prefix); +extern int cmd_repo_config(int argc, const char **argv, const char *prefix); +extern int cmd_rev_list(int argc, const char **argv, const char *prefix); +extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); +extern int cmd_rm(int argc, const char **argv, const char *prefix); +extern int cmd_show_branch(int argc, const char **argv, const char *prefix); +extern int cmd_show(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); -extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); - +extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); +extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); +extern int cmd_update_index(int argc, const char **argv, const char *prefix); +extern int cmd_update_ref(int argc, const char **argv, const char *prefix); +extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); +extern int cmd_version(int argc, const char **argv, const char *prefix); +extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); -extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); - -extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); -extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip); -extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); -extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const char *msg, const char *patch); - -extern int cmd_stripspace(int argc, const char **argv, const char *prefix); -extern void stripspace(FILE *in, FILE *out); #endif diff --git a/git.c b/git.c index 07b6d3e9dd..754db1a2ee 100644 --- a/git.c +++ b/git.c @@ -224,52 +224,52 @@ static void handle_internal_command(int argc, const char **argv, char **envp) int (*fn)(int, const char **, const char *); int option; } commands[] = { - { "version", cmd_version }, - { "help", cmd_help }, - { "log", cmd_log, NEEDS_PREFIX | USE_PAGER }, - { "whatchanged", cmd_whatchanged, NEEDS_PREFIX | USE_PAGER }, - { "show", cmd_show, NEEDS_PREFIX | USE_PAGER }, - { "push", cmd_push }, - { "format-patch", cmd_format_patch, NEEDS_PREFIX }, - { "count-objects", cmd_count_objects }, - { "diff", cmd_diff, NEEDS_PREFIX }, - { "grep", cmd_grep, NEEDS_PREFIX }, - { "rm", cmd_rm, NEEDS_PREFIX }, { "add", cmd_add, NEEDS_PREFIX }, - { "rev-list", cmd_rev_list, NEEDS_PREFIX }, - { "init-db", cmd_init_db }, - { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "upload-tar", cmd_upload_tar }, + { "apply", cmd_apply }, + { "cat-file", cmd_cat_file, NEEDS_PREFIX }, + { "checkout-index", cmd_checkout_index, NEEDS_PREFIX }, { "check-ref-format", cmd_check_ref_format }, - { "ls-files", cmd_ls_files, NEEDS_PREFIX }, - { "ls-tree", cmd_ls_tree, NEEDS_PREFIX }, - { "tar-tree", cmd_tar_tree, NEEDS_PREFIX }, - { "read-tree", cmd_read_tree, NEEDS_PREFIX }, { "commit-tree", cmd_commit_tree, NEEDS_PREFIX }, - { "apply", cmd_apply }, - { "show-branch", cmd_show_branch, NEEDS_PREFIX }, + { "count-objects", cmd_count_objects }, + { "diff", cmd_diff, NEEDS_PREFIX }, { "diff-files", cmd_diff_files, NEEDS_PREFIX }, { "diff-index", cmd_diff_index, NEEDS_PREFIX }, { "diff-stages", cmd_diff_stages, NEEDS_PREFIX }, { "diff-tree", cmd_diff_tree, NEEDS_PREFIX }, - { "cat-file", cmd_cat_file, NEEDS_PREFIX }, - { "rev-parse", cmd_rev_parse, NEEDS_PREFIX }, - { "write-tree", cmd_write_tree, NEEDS_PREFIX }, - { "mailsplit", cmd_mailsplit }, - { "mailinfo", cmd_mailinfo }, - { "stripspace", cmd_stripspace }, - { "update-index", cmd_update_index, NEEDS_PREFIX }, - { "update-ref", cmd_update_ref, NEEDS_PREFIX }, { "fmt-merge-msg", cmd_fmt_merge_msg, NEEDS_PREFIX }, - { "prune", cmd_prune, NEEDS_PREFIX }, + { "format-patch", cmd_format_patch, NEEDS_PREFIX }, + { "get-tar-commit-id", cmd_get_tar_commit_id }, + { "grep", cmd_grep, NEEDS_PREFIX }, + { "help", cmd_help }, + { "init-db", cmd_init_db }, + { "log", cmd_log, NEEDS_PREFIX | USE_PAGER }, + { "ls-files", cmd_ls_files, NEEDS_PREFIX }, + { "ls-tree", cmd_ls_tree, NEEDS_PREFIX }, + { "mailinfo", cmd_mailinfo }, + { "mailsplit", cmd_mailsplit }, { "mv", cmd_mv, NEEDS_PREFIX }, - { "prune-packed", cmd_prune_packed, NEEDS_PREFIX }, - { "repo-config", cmd_repo_config }, { "name-rev", cmd_name_rev, NEEDS_PREFIX }, { "pack-objects", cmd_pack_objects, NEEDS_PREFIX }, - { "unpack-objects", cmd_unpack_objects, NEEDS_PREFIX }, + { "prune", cmd_prune, NEEDS_PREFIX }, + { "prune-packed", cmd_prune_packed, NEEDS_PREFIX }, + { "push", cmd_push }, + { "read-tree", cmd_read_tree, NEEDS_PREFIX }, + { "repo-config", cmd_repo_config }, + { "rev-list", cmd_rev_list, NEEDS_PREFIX }, + { "rev-parse", cmd_rev_parse, NEEDS_PREFIX }, + { "rm", cmd_rm, NEEDS_PREFIX }, + { "show-branch", cmd_show_branch, NEEDS_PREFIX }, + { "show", cmd_show, NEEDS_PREFIX | USE_PAGER }, + { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, NEEDS_PREFIX }, - { "checkout-index", cmd_checkout_index, NEEDS_PREFIX }, + { "tar-tree", cmd_tar_tree, NEEDS_PREFIX }, + { "unpack-objects", cmd_unpack_objects, NEEDS_PREFIX }, + { "update-index", cmd_update_index, NEEDS_PREFIX }, + { "update-ref", cmd_update_ref, NEEDS_PREFIX }, + { "upload-tar", cmd_upload_tar }, + { "version", cmd_version }, + { "whatchanged", cmd_whatchanged, NEEDS_PREFIX | USE_PAGER }, + { "write-tree", cmd_write_tree, NEEDS_PREFIX }, }; int i; diff --git a/help.c b/help.c new file mode 100644 index 0000000000..fb731cc934 --- /dev/null +++ b/help.c @@ -0,0 +1,234 @@ +/* + * builtin-help.c + * + * Builtin help-related commands (help, usage, version) + */ +#include +#include "cache.h" +#include "builtin.h" +#include "exec_cmd.h" +#include "common-cmds.h" + + +/* most GUI terminals set COLUMNS (although some don't export it) */ +static int term_columns(void) +{ + char *col_string = getenv("COLUMNS"); + int n_cols = 0; + + if (col_string && (n_cols = atoi(col_string)) > 0) + return n_cols; + +#ifdef TIOCGWINSZ + { + struct winsize ws; + if (!ioctl(1, TIOCGWINSZ, &ws)) { + if (ws.ws_col) + return ws.ws_col; + } + } +#endif + + return 80; +} + +static void oom(void) +{ + fprintf(stderr, "git: out of memory\n"); + exit(1); +} + +static inline void mput_char(char c, unsigned int num) +{ + while(num--) + putchar(c); +} + +static struct cmdname { + size_t len; + char name[1]; +} **cmdname; +static int cmdname_alloc, cmdname_cnt; + +static void add_cmdname(const char *name, int len) +{ + struct cmdname *ent; + if (cmdname_alloc <= cmdname_cnt) { + cmdname_alloc = cmdname_alloc + 200; + cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname)); + if (!cmdname) + oom(); + } + ent = malloc(sizeof(*ent) + len); + if (!ent) + oom(); + ent->len = len; + memcpy(ent->name, name, len); + ent->name[len] = 0; + cmdname[cmdname_cnt++] = ent; +} + +static int cmdname_compare(const void *a_, const void *b_) +{ + struct cmdname *a = *(struct cmdname **)a_; + struct cmdname *b = *(struct cmdname **)b_; + return strcmp(a->name, b->name); +} + +static void pretty_print_string_list(struct cmdname **cmdname, int longest) +{ + int cols = 1, rows; + int space = longest + 1; /* min 1 SP between words */ + int max_cols = term_columns() - 1; /* don't print *on* the edge */ + int i, j; + + if (space < max_cols) + cols = max_cols / space; + rows = (cmdname_cnt + cols - 1) / cols; + + qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare); + + for (i = 0; i < rows; i++) { + printf(" "); + + for (j = 0; j < cols; j++) { + int n = j * rows + i; + int size = space; + if (n >= cmdname_cnt) + break; + if (j == cols-1 || n + rows >= cmdname_cnt) + size = 1; + printf("%-*s", size, cmdname[n]->name); + } + putchar('\n'); + } +} + +static void list_commands(const char *exec_path, const char *pattern) +{ + unsigned int longest = 0; + char path[PATH_MAX]; + int dirlen; + DIR *dir = opendir(exec_path); + struct dirent *de; + + if (!dir) { + fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno)); + exit(1); + } + + dirlen = strlen(exec_path); + if (PATH_MAX - 20 < dirlen) { + fprintf(stderr, "git: insanely long exec-path '%s'\n", + exec_path); + exit(1); + } + + memcpy(path, exec_path, dirlen); + path[dirlen++] = '/'; + + while ((de = readdir(dir)) != NULL) { + struct stat st; + int entlen; + + if (strncmp(de->d_name, "git-", 4)) + continue; + strcpy(path+dirlen, de->d_name); + if (stat(path, &st) || /* stat, not lstat */ + !S_ISREG(st.st_mode) || + !(st.st_mode & S_IXUSR)) + continue; + + entlen = strlen(de->d_name); + if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe")) + entlen -= 4; + + if (longest < entlen) + longest = entlen; + + add_cmdname(de->d_name + 4, entlen-4); + } + closedir(dir); + + printf("git commands available in '%s'\n", exec_path); + printf("----------------------------"); + mput_char('-', strlen(exec_path)); + putchar('\n'); + pretty_print_string_list(cmdname, longest - 4); + putchar('\n'); +} + +static void list_common_cmds_help(void) +{ + int i, longest = 0; + + for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { + if (longest < strlen(common_cmds[i].name)) + longest = strlen(common_cmds[i].name); + } + + puts("The most commonly used git commands are:"); + for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { + printf(" %s", common_cmds[i].name); + mput_char(' ', longest - strlen(common_cmds[i].name) + 4); + puts(common_cmds[i].help); + } + puts("(use 'git help -a' to get a list of all installed git commands)"); +} + +static void show_man_page(const char *git_cmd) +{ + const char *page; + + if (!strncmp(git_cmd, "git", 3)) + page = git_cmd; + else { + int page_len = strlen(git_cmd) + 4; + char *p = malloc(page_len + 1); + strcpy(p, "git-"); + strcpy(p + 4, git_cmd); + p[page_len] = 0; + page = p; + } + + execlp("man", "man", page, NULL); +} + +void help_unknown_cmd(const char *cmd) +{ + printf("git: '%s' is not a git-command\n\n", cmd); + list_common_cmds_help(); + exit(1); +} + +int cmd_version(int argc, const char **argv, const char *prefix) +{ + printf("git version %s\n", git_version_string); + return 0; +} + +int cmd_help(int argc, const char **argv, const char *prefix) +{ + const char *help_cmd = argc > 1 ? argv[1] : NULL; + const char *exec_path = git_exec_path(); + + if (!help_cmd) { + printf("usage: %s\n\n", git_usage_string); + list_common_cmds_help(); + exit(1); + } + + else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) { + printf("usage: %s\n\n", git_usage_string); + if(exec_path) + list_commands(exec_path, "git-*"); + exit(1); + } + + else + show_man_page(help_cmd); + + return 0; +} + + -- cgit v1.2.3 From efffea033457eedb90ad63596687564f797f12de Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Aug 2006 02:04:00 -0700 Subject: git.c: Rename NEEDS_PREFIX to RUN_SETUP As Matthias Kestenholz noted, the flag does not quite mean "needs prefix" -- it is more like "run setup_git_directory() before running this command", so rename it to avoid future confusion. While we are at it, rewrite the definition of options to make it obvious that we are talking about flag bits by using standard (1< --- git.c | 72 +++++++++++++++++++++++++++++++++---------------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/git.c b/git.c index 754db1a2ee..96e596b1a3 100644 --- a/git.c +++ b/git.c @@ -213,8 +213,8 @@ static int handle_alias(int *argcp, const char ***argv) const char git_version_string[] = GIT_VERSION; -#define NEEDS_PREFIX 1 -#define USE_PAGER 2 +#define RUN_SETUP (1<<0) +#define USE_PAGER (1<<1) static void handle_internal_command(int argc, const char **argv, char **envp) { @@ -224,52 +224,52 @@ static void handle_internal_command(int argc, const char **argv, char **envp) int (*fn)(int, const char **, const char *); int option; } commands[] = { - { "add", cmd_add, NEEDS_PREFIX }, + { "add", cmd_add, RUN_SETUP }, { "apply", cmd_apply }, - { "cat-file", cmd_cat_file, NEEDS_PREFIX }, - { "checkout-index", cmd_checkout_index, NEEDS_PREFIX }, + { "cat-file", cmd_cat_file, RUN_SETUP }, + { "checkout-index", cmd_checkout_index, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, - { "commit-tree", cmd_commit_tree, NEEDS_PREFIX }, + { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "count-objects", cmd_count_objects }, - { "diff", cmd_diff, NEEDS_PREFIX }, - { "diff-files", cmd_diff_files, NEEDS_PREFIX }, - { "diff-index", cmd_diff_index, NEEDS_PREFIX }, - { "diff-stages", cmd_diff_stages, NEEDS_PREFIX }, - { "diff-tree", cmd_diff_tree, NEEDS_PREFIX }, - { "fmt-merge-msg", cmd_fmt_merge_msg, NEEDS_PREFIX }, - { "format-patch", cmd_format_patch, NEEDS_PREFIX }, + { "diff", cmd_diff, RUN_SETUP }, + { "diff-files", cmd_diff_files, RUN_SETUP }, + { "diff-index", cmd_diff_index, RUN_SETUP }, + { "diff-stages", cmd_diff_stages, RUN_SETUP }, + { "diff-tree", cmd_diff_tree, RUN_SETUP }, + { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP }, + { "format-patch", cmd_format_patch, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "grep", cmd_grep, NEEDS_PREFIX }, + { "grep", cmd_grep, RUN_SETUP }, { "help", cmd_help }, { "init-db", cmd_init_db }, - { "log", cmd_log, NEEDS_PREFIX | USE_PAGER }, - { "ls-files", cmd_ls_files, NEEDS_PREFIX }, - { "ls-tree", cmd_ls_tree, NEEDS_PREFIX }, + { "log", cmd_log, RUN_SETUP | USE_PAGER }, + { "ls-files", cmd_ls_files, RUN_SETUP }, + { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo }, { "mailsplit", cmd_mailsplit }, - { "mv", cmd_mv, NEEDS_PREFIX }, - { "name-rev", cmd_name_rev, NEEDS_PREFIX }, - { "pack-objects", cmd_pack_objects, NEEDS_PREFIX }, - { "prune", cmd_prune, NEEDS_PREFIX }, - { "prune-packed", cmd_prune_packed, NEEDS_PREFIX }, + { "mv", cmd_mv, RUN_SETUP }, + { "name-rev", cmd_name_rev, RUN_SETUP }, + { "pack-objects", cmd_pack_objects, RUN_SETUP }, + { "prune", cmd_prune, RUN_SETUP }, + { "prune-packed", cmd_prune_packed, RUN_SETUP }, { "push", cmd_push }, - { "read-tree", cmd_read_tree, NEEDS_PREFIX }, + { "read-tree", cmd_read_tree, RUN_SETUP }, { "repo-config", cmd_repo_config }, - { "rev-list", cmd_rev_list, NEEDS_PREFIX }, - { "rev-parse", cmd_rev_parse, NEEDS_PREFIX }, - { "rm", cmd_rm, NEEDS_PREFIX }, - { "show-branch", cmd_show_branch, NEEDS_PREFIX }, - { "show", cmd_show, NEEDS_PREFIX | USE_PAGER }, + { "rev-list", cmd_rev_list, RUN_SETUP }, + { "rev-parse", cmd_rev_parse, RUN_SETUP }, + { "rm", cmd_rm, RUN_SETUP }, + { "show-branch", cmd_show_branch, RUN_SETUP }, + { "show", cmd_show, RUN_SETUP | USE_PAGER }, { "stripspace", cmd_stripspace }, - { "symbolic-ref", cmd_symbolic_ref, NEEDS_PREFIX }, - { "tar-tree", cmd_tar_tree, NEEDS_PREFIX }, - { "unpack-objects", cmd_unpack_objects, NEEDS_PREFIX }, - { "update-index", cmd_update_index, NEEDS_PREFIX }, - { "update-ref", cmd_update_ref, NEEDS_PREFIX }, + { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, + { "tar-tree", cmd_tar_tree, RUN_SETUP }, + { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, + { "update-index", cmd_update_index, RUN_SETUP }, + { "update-ref", cmd_update_ref, RUN_SETUP }, { "upload-tar", cmd_upload_tar }, { "version", cmd_version }, - { "whatchanged", cmd_whatchanged, NEEDS_PREFIX | USE_PAGER }, - { "write-tree", cmd_write_tree, NEEDS_PREFIX }, + { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, + { "write-tree", cmd_write_tree, RUN_SETUP }, }; int i; @@ -286,7 +286,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) continue; prefix = NULL; - if (p->option & NEEDS_PREFIX) + if (p->option & RUN_SETUP) prefix = setup_git_directory(); if (p->option & USE_PAGER) setup_pager(); -- cgit v1.2.3 From 656517b9ef5cf443f72110dcd56b15825bc7f1ef Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 4 Aug 2006 17:55:57 +0200 Subject: autoconf: Check for ll hh j z t size specifiers introduced by C99 Add custom test for checking whether formatted IO functions (printf/scanf et.al.) support 'size specifiers' introduced by C99, namely ll, hh, j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- configure.ac | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/configure.ac b/configure.ac index 0a54b44939..a4becf8fa5 100644 --- a/configure.ac +++ b/configure.ac @@ -189,6 +189,27 @@ AC_CHECK_MEMBER(struct dirent.d_type,[], AC_CHECK_TYPE(struct sockaddr_storage,[], [GIT_CONF_APPEND_LINE(NO_SOCKADDR_STORAGE=YesPlease)], [#include ]) +# +# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) +# do not support the 'size specifiers' introduced by C99, namely ll, hh, +# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). +# some C compilers supported these specifiers prior to C99 as an extension. +AC_CACHE_CHECK(whether IO functions support %ll %hh %j %z %t size specifiers, + ac_cv_c_c99_format, +[# Actually git uses only %z (%zu) in alloc.c, and %t (%td) in mktag.c +AC_RUN_IFELSE( + [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT], + [[char buf[64]; + if (sprintf(buf, "%lld%hhd%jd%zd%td", (long long int)1, (char)2, (intmax_t)3, (size_t)4, (ptrdiff_t)5) != 5) + exit(1); + else if (strcmp(buf, "12345")) + exit(2);]])], + [ac_cv_c_c99_format=yes], + [ac_cv_c_c99_format=no]) +]) +if test $ac_cv_c_c99_format = no; then + GIT_CONF_APPEND_LINE(NO_C99_FORMAT=YesPlease) +fi ## Checks for library functions. -- cgit v1.2.3 From 465e649d0c62847fd5cca212766e1a01183baeef Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 4 Aug 2006 17:55:58 +0200 Subject: autoconf: Typo cleanup, reordering etc. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- Makefile | 2 +- configure.ac | 43 +++++++++++++++++++++++-------------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index 700c77f564..ae4c0f2d90 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ all: # Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) # do not support the 'size specifiers' introduced by C99, namely ll, hh, # j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). -# some c compilers supported these specifiers prior to C99 as an extension. +# some C compilers supported these specifiers prior to C99 as an extension. # # Define NO_STRCASESTR if you don't have strcasestr. # diff --git a/configure.ac b/configure.ac index a4becf8fa5..cc6b3cd5fb 100644 --- a/configure.ac +++ b/configure.ac @@ -51,7 +51,7 @@ fi; \ ## Site configuration ## --with-PACKAGE[=ARG] and --without-PACKAGE # -# Define NO_SVN_TESTS if you want to skip time-consuming SVN interopability +# Define NO_SVN_TESTS if you want to skip time-consuming SVN interoperability # tests. These tests take up a significant amount of the total test time # but are not needed unless you plan to talk to SVN repos. # @@ -81,7 +81,24 @@ fi; \ # # Define NO_MMAP if you want to avoid mmap. # -# Define NO_PYTHON if you want to loose all benefits of the recursive merge. +# Define SHELL_PATH to provide path to shell. +GIT_ARG_SET_PATH(shell) +# +# Define PERL_PATH to provide path to Perl. +GIT_ARG_SET_PATH(perl) +# +# Define NO_PYTHON if you want to lose all benefits of the recursive merge. +# Define PYTHON_PATH to provide path to Python. +AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python]) +AS_HELP_STRING([--no-python], [don't use python scripts])], + [if test "$withval" = "no"; then \ + NO_PYTHON=YesPlease; \ + elif test "$withval" != "yes"; then \ + PYTHON_PATH=$withval; \ + fi; \ + ]) +AC_SUBST(NO_PYTHON) +AC_SUBST(PYTHON_PATH) # ## --enable-FEATURE[=ARG] and --disable-FEATURE # Define COLLISION_CHECK below if you believe that SHA1's @@ -101,27 +118,13 @@ fi; \ ## Checks for programs. AC_MSG_NOTICE([CHECKS for programs]) # -GIT_ARG_SET_PATH(shell) -GIT_ARG_SET_PATH(perl) -AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python]) -AS_HELP_STRING([--no-python], [don't use python scripts])], - [if test "$withval" = "no"; then \ - NO_PYTHON=YesPlease; \ - elif test "$withval" != "yes"; then \ - PYTHON_PATH=$withval; \ - fi; \ - ]) -AC_SUBST(NO_PYTHON) -AC_SUBST(PYTHON_PATH) - - -# -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. -# Define PYTHON_PATH to provide path to Python. AC_PROG_CC #AC_PROG_INSTALL # needs install-sh or install.sh in sources AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) +# +# Define NO_PYTHON if you want to lose all benefits of the recursive merge. +# Define PYTHON_PATH to provide path to Python. if test -z "$NO_PYTHON"; then AC_PATH_PROGS(PYTHON_PATH, [python2.4 python2.3 python2 python]) if test -n "$PYTHON_PATH"; then @@ -194,7 +197,7 @@ AC_CHECK_TYPE(struct sockaddr_storage,[], # do not support the 'size specifiers' introduced by C99, namely ll, hh, # j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). # some C compilers supported these specifiers prior to C99 as an extension. -AC_CACHE_CHECK(whether IO functions support %ll %hh %j %z %t size specifiers, +AC_CACHE_CHECK(whether formatted IO functions support C99 size specifiers, ac_cv_c_c99_format, [# Actually git uses only %z (%zu) in alloc.c, and %t (%td) in mktag.c AC_RUN_IFELSE( -- cgit v1.2.3 From 34d4a67f47bb8954475077e91d513f445713c534 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 4 Aug 2006 17:55:59 +0200 Subject: Copy description of new build configuration variables to configure.ac Copy description of new build configuration variables from the commentary in the top Makefile, namely NO_FINK and NO_DARWIN_PORTS configuration variables, putting them in site configuration section. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- configure.ac | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/configure.ac b/configure.ac index cc6b3cd5fb..9ce00e9522 100644 --- a/configure.ac +++ b/configure.ac @@ -79,6 +79,18 @@ fi; \ # Define NO_EXPAT if you do not have expat installed. git-http-push is # not built, and you cannot push using http:// and https:// transports. # +# Define NO_FINK if you are building on Darwin/Mac OS X, have Fink +# installed in /sw, but don't want GIT to link against any libraries +# installed there. If defined you may specify your own (or Fink's) +# include directories and library directories by defining CFLAGS +# and LDFLAGS appropriately. +# +# Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X, +# have DarwinPorts installed in /opt/local, but don't want GIT to +# link against any libraries installed there. If defined you may +# specify your own (or DarwinPort's) include directories and +# library directories by defining CFLAGS and LDFLAGS appropriately. +# # Define NO_MMAP if you want to avoid mmap. # # Define SHELL_PATH to provide path to shell. -- cgit v1.2.3 From 3068f6c47d8ece84c7daba243d3f60492bd0611e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Aug 2006 16:33:18 -0700 Subject: autoconf: fix NEEDS_SSL_WITH_CRYPTO NEEDS_SSL_WITH_CRYPTO means you cannot just say "-lcrypto" to use SHA1 stuff, but need to say "-lcrypto -lssl". Signed-off-by: Junio C Hamano --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 9ce00e9522..fea18b69b4 100644 --- a/configure.ac +++ b/configure.ac @@ -154,8 +154,8 @@ AC_MSG_NOTICE([CHECKS for libraries]) # # Define NO_OPENSSL environment variable if you do not have OpenSSL. # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). -AC_CHECK_LIB([ssl], [SHA1_Init],[], -[AC_CHECK_LIB([crypto], [SHA1_INIT], +AC_CHECK_LIB([crypto], [SHA1_Init],[], +[AC_CHECK_LIB([ssl], [SHA1_Init], [GIT_CONF_APPEND_LINE(NEEDS_SSL_WITH_CRYPTO=YesPlease)], [GIT_CONF_APPEND_LINE(NO_OPENSSL=YesPlease)])]) # -- cgit v1.2.3 From f7b5e8d03a97e7d4a6e543d5f9972d1285947908 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 4 Aug 2006 23:28:11 +0200 Subject: autoconf: Set NEEDS_LIBICONV unconditionally if there is no iconv in libc Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- configure.ac | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index fea18b69b4..33ddee2120 100644 --- a/configure.ac +++ b/configure.ac @@ -172,8 +172,7 @@ AC_CHECK_LIB([expat], [XML_ParserCreate],[], # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). AC_CHECK_LIB([c], [iconv],[], -[AC_CHECK_LIB([iconv],[iconv], - [GIT_CONF_APPEND_LINE(NEEDS_LIBICONV=YesPlease)],[])]) +[GIT_CONF_APPEND_LINE(NEEDS_LIBICONV=YesPlease)]) # # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, # Patrick Mauritz). -- cgit v1.2.3 From 8c6ab35efe635b2f70cdd471903cd80886ec6eec Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Aug 2006 16:46:16 -0700 Subject: autoconf: NO_IPV6 We would need both "struct addrinfo" and getaddrinfo() available. Check them and set NO_IPV6 otherwise. Signed-off-by: Junio C Hamano --- configure.ac | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 33ddee2120..74c271098c 100644 --- a/configure.ac +++ b/configure.ac @@ -203,6 +203,14 @@ AC_CHECK_MEMBER(struct dirent.d_type,[], AC_CHECK_TYPE(struct sockaddr_storage,[], [GIT_CONF_APPEND_LINE(NO_SOCKADDR_STORAGE=YesPlease)], [#include ]) +# Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). +AC_CHECK_TYPE([struct addrinfo],[ +AC_CHECK_FUNC([getaddrinfo],[], +[GIT_CONF_APPEND_LINE(NO_IPV6=YesPlease)])],[],[ +#include +#include +#include +]) # # Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) # do not support the 'size specifiers' introduced by C99, namely ll, hh, @@ -244,8 +252,6 @@ AC_CHECK_FUNC(setenv,[], # # Define NO_MMAP if you want to avoid mmap. # -# Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). -# # Define NO_ICONV if your libc does not properly support iconv. -- cgit v1.2.3 From f9f02d012927cba2264b948d16008ede025f7c71 Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Fri, 4 Aug 2006 15:14:27 -0700 Subject: gitweb: git_tree displays blame based on repository config git_tree() will now conditionally display "blame" depending on how "gitweb.blame" variable is configured using "git-repo-config". Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 58eb5b190f..b268b6344d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1757,6 +1757,7 @@ sub git_tree { git_header_html(); my $base_key = ""; my $base = ""; + my $have_blame = git_get_project_config_bool ('blame'); if (defined $hash_base && (my %co = git_read_commit($hash_base))) { $base_key = ";hb=$hash_base"; git_page_nav('tree','', $hash_base); @@ -1792,9 +1793,11 @@ sub git_tree { $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name"), -class => "list"}, esc_html($t_name)) . "\n" . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob") . -# " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob"); + if ($have_blame) { + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame"); + } + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") . " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$t_hash;f=$base$t_name")}, "raw") . "\n"; } elsif ($t_type eq "tree") { -- cgit v1.2.3 From 61ffbcb988046dfaf8266569f78a31483be026f6 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Aug 2006 17:50:41 +0200 Subject: http-push: avoid fork() by calling merge_bases() directly Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- http-push.c | 49 +++++++------------------------------------------ 1 file changed, 7 insertions(+), 42 deletions(-) diff --git a/http-push.c b/http-push.c index 4021e7d927..dc82657830 100644 --- a/http-push.c +++ b/http-push.c @@ -2182,49 +2182,14 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1) static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1) { - int pipe_fd[2]; - pid_t merge_base_pid; - char line[PATH_MAX + 20]; - unsigned char merge_sha1[20]; - int verified = 0; - - if (pipe(pipe_fd) < 0) - die("Verify merge base: pipe failed"); - - merge_base_pid = fork(); - if (!merge_base_pid) { - static const char *args[] = { - "merge-base", - "-a", - NULL, - NULL, - NULL - }; - args[2] = strdup(sha1_to_hex(head_sha1)); - args[3] = sha1_to_hex(branch_sha1); - - dup2(pipe_fd[1], 1); - close(pipe_fd[0]); - close(pipe_fd[1]); - execv_git_cmd(args); - die("merge-base setup failed"); - } - if (merge_base_pid < 0) - die("merge-base fork failed"); - - dup2(pipe_fd[0], 0); - close(pipe_fd[0]); - close(pipe_fd[1]); - while (fgets(line, sizeof(line), stdin) != NULL) { - if (get_sha1_hex(line, merge_sha1)) - die("expected sha1, got garbage:\n %s", line); - if (!memcmp(branch_sha1, merge_sha1, 20)) { - verified = 1; - break; - } - } + struct commit *head = lookup_commit(head_sha1); + struct commit *branch = lookup_commit(branch_sha1); + struct commit_list *merge_bases = get_merge_bases(head, branch, 1); - return verified; + if (merge_bases && !merge_bases->next && merge_bases->item == branch) + return 1; + + return 0; } static int delete_remote_branch(char *pattern, int force) -- cgit v1.2.3 From 154b4d78cf35025268ed74c45f182b25ebaf4acc Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 5 Aug 2006 12:55:20 +0200 Subject: gitweb: Separate input validation and dispatch, add comment about opml action Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b268b6344d..7f4387fde6 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -71,6 +71,7 @@ if (! -d $git_temp) { mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp"); } +# ====================================================================== # input validation and dispatch our $action = $cgi->param('a'); if (defined $action) { @@ -78,6 +79,7 @@ if (defined $action) { undef $action; die_error(undef, "Invalid action parameter."); } + # action which does not check rest of parameters if ($action eq "opml") { git_opml(); exit; -- cgit v1.2.3 From cfd826693688245c6d8f48a5fdc1f6780ecca65b Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 5 Aug 2006 12:56:04 +0200 Subject: gitweb: die_error first (optional) parameter is HTTP status Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7f4387fde6..040024ccf7 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -68,7 +68,7 @@ our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; $projects_list ||= $projectroot; if (! -d $git_temp) { - mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp"); + mkdir($git_temp, 0700) || die_error(undef, "Couldn't mkdir $git_temp"); } # ====================================================================== @@ -1658,7 +1658,7 @@ sub git_blob_plain { } my $type = shift; open my $fd, "-|", $GIT, "cat-file", "blob", $hash - or die_error("Couldn't cat $file_name, $hash"); + or die_error(undef, "Couldn't cat $file_name, $hash"); $type ||= git_blob_plain_mimetype($fd, $file_name); -- cgit v1.2.3 From 623e4aeb42c313360d2c1edba7f844908882c2a1 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 5 Aug 2006 12:56:42 +0200 Subject: gitweb: Use undef for die_error to use default first (status) parameter value Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 040024ccf7..c11c2f2b70 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2323,7 +2323,7 @@ sub git_history { sub git_search { if (!defined $searchtext) { - die_error("", "Text field empty."); + die_error(undef, "Text field empty."); } if (!defined $hash) { $hash = git_read_head($project); -- cgit v1.2.3 From dbd954a8969a31741a676e9e7cc2dfd00f8f4e93 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 5 Aug 2006 12:58:06 +0200 Subject: gitweb: Don't undefine query parameter related variables before die_error Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c11c2f2b70..89ceb9765d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -76,7 +76,6 @@ if (! -d $git_temp) { our $action = $cgi->param('a'); if (defined $action) { if ($action =~ m/[^0-9a-zA-Z\.\-_]/) { - undef $action; die_error(undef, "Invalid action parameter."); } # action which does not check rest of parameters @@ -89,16 +88,13 @@ if (defined $action) { our $project = ($cgi->param('p') || $ENV{'PATH_INFO'}); if (defined $project) { $project =~ s|^/||; $project =~ s|/$||; - $project = validate_input($project); - if (!defined($project)) { + if (!validate_input($project)) { die_error(undef, "Invalid project parameter."); } if (!(-d "$projectroot/$project")) { - undef $project; die_error(undef, "No such directory."); } if (!(-e "$projectroot/$project/HEAD")) { - undef $project; die_error(undef, "No such project."); } $rss_link = "param('f'); if (defined $file_name) { - $file_name = validate_input($file_name); - if (!defined($file_name)) { + if (!validate_input($file_name)) { die_error(undef, "Invalid file parameter."); } } our $hash = $cgi->param('h'); if (defined $hash) { - $hash = validate_input($hash); - if (!defined($hash)) { + if (!validate_input($hash)) { die_error(undef, "Invalid hash parameter."); } } our $hash_parent = $cgi->param('hp'); if (defined $hash_parent) { - $hash_parent = validate_input($hash_parent); - if (!defined($hash_parent)) { + if (!validate_input($hash_parent)) { die_error(undef, "Invalid hash parent parameter."); } } our $hash_base = $cgi->param('hb'); if (defined $hash_base) { - $hash_base = validate_input($hash_base); - if (!defined($hash_base)) { + if (!validate_input($hash_base)) { die_error(undef, "Invalid hash base parameter."); } } @@ -144,7 +136,6 @@ if (defined $hash_base) { our $page = $cgi->param('pg'); if (defined $page) { if ($page =~ m/[^0-9]$/) { - undef $page; die_error(undef, "Invalid page parameter."); } } @@ -152,7 +143,6 @@ if (defined $page) { our $searchtext = $cgi->param('s'); if (defined $searchtext) { if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) { - undef $searchtext; die_error(undef, "Invalid search parameter."); } $searchtext = quotemeta $searchtext; @@ -182,7 +172,6 @@ my %actions = ( $action = 'summary' if (!defined($action)); if (!defined($actions{$action})) { - undef $action; die_error(undef, "Unknown action."); } $actions{$action}->(); -- cgit v1.2.3 From e484a2d6ad26ff7a6ebe84085dda00591476014e Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 5 Aug 2006 13:12:51 +0200 Subject: gitweb: Cleanup and uniquify error messages Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 89ceb9765d..16e838fc6d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1464,7 +1464,7 @@ sub git_blame2 { die_error(undef, "Permission denied.") if (!git_get_project_config_bool ('blame')); die_error('404 Not Found', "File name not defined") if (!$file_name); $hash_base ||= git_read_head($project); - die_error(undef, "Reading commit failed") unless ($hash_base); + die_error(undef, "Couldn't find base commit.") unless ($hash_base); my %co = git_read_commit($hash_base) or die_error(undef, "Reading commit failed"); if (!defined $hash) { @@ -1473,7 +1473,7 @@ sub git_blame2 { } $ftype = git_get_type($hash); if ($ftype !~ "blob") { - die_error("400 Bad Request", "object is not a blob"); + die_error("400 Bad Request", "Object is not a blob"); } open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base) or die_error(undef, "Open git-blame failed."); @@ -1520,9 +1520,9 @@ sub git_blame2 { sub git_blame { my $fd; die_error('403 Permission denied', "Permission denied.") if (!git_get_project_config_bool ('blame')); - die_error('404 Not Found', "What file will it be, master?") if (!$file_name); + die_error('404 Not Found', "File name not defined.") if (!$file_name); $hash_base ||= git_read_head($project); - die_error(undef, "Reading commit failed.") unless ($hash_base); + die_error(undef, "Couldn't find base commit.") unless ($hash_base); my %co = git_read_commit($hash_base) or die_error(undef, "Reading commit failed."); if (!defined $hash) { @@ -2116,7 +2116,7 @@ sub git_commitdiff { open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash or die_error(undef, "Open git-diff-tree failed."); my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading diff-tree failed."); + close $fd or die_error(undef, "Reading git-diff-tree failed."); # non-textual hash id's can be cached my $expires; @@ -2487,7 +2487,7 @@ sub git_rss { open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) or die_error(undef, "Open git-rev-list failed."); my @revlist = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading rev-list failed."); + close $fd or die_error(undef, "Reading git-rev-list failed."); print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); print "\n". "\n"; -- cgit v1.2.3 From cac4bd94fb6d5f3066614051827669476a9f0b56 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 5 Aug 2006 13:13:53 +0200 Subject: gitweb: No periods for error messages Signed-off-by: Jakub Narebski Acked-by: Luben Tuikov Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 92 +++++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 16e838fc6d..6284901457 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -76,7 +76,7 @@ if (! -d $git_temp) { our $action = $cgi->param('a'); if (defined $action) { if ($action =~ m/[^0-9a-zA-Z\.\-_]/) { - die_error(undef, "Invalid action parameter."); + die_error(undef, "Invalid action parameter"); } # action which does not check rest of parameters if ($action eq "opml") { @@ -89,13 +89,13 @@ our $project = ($cgi->param('p') || $ENV{'PATH_INFO'}); if (defined $project) { $project =~ s|^/||; $project =~ s|/$||; if (!validate_input($project)) { - die_error(undef, "Invalid project parameter."); + die_error(undef, "Invalid project parameter"); } if (!(-d "$projectroot/$project")) { - die_error(undef, "No such directory."); + die_error(undef, "No such directory"); } if (!(-e "$projectroot/$project/HEAD")) { - die_error(undef, "No such project."); + die_error(undef, "No such project"); } $rss_link = ""; @@ -108,42 +108,42 @@ if (defined $project) { our $file_name = $cgi->param('f'); if (defined $file_name) { if (!validate_input($file_name)) { - die_error(undef, "Invalid file parameter."); + die_error(undef, "Invalid file parameter"); } } our $hash = $cgi->param('h'); if (defined $hash) { if (!validate_input($hash)) { - die_error(undef, "Invalid hash parameter."); + die_error(undef, "Invalid hash parameter"); } } our $hash_parent = $cgi->param('hp'); if (defined $hash_parent) { if (!validate_input($hash_parent)) { - die_error(undef, "Invalid hash parent parameter."); + die_error(undef, "Invalid hash parent parameter"); } } our $hash_base = $cgi->param('hb'); if (defined $hash_base) { if (!validate_input($hash_base)) { - die_error(undef, "Invalid hash base parameter."); + die_error(undef, "Invalid hash base parameter"); } } our $page = $cgi->param('pg'); if (defined $page) { if ($page =~ m/[^0-9]$/) { - die_error(undef, "Invalid page parameter."); + die_error(undef, "Invalid page parameter"); } } our $searchtext = $cgi->param('s'); if (defined $searchtext) { if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) { - die_error(undef, "Invalid search parameter."); + die_error(undef, "Invalid search parameter"); } $searchtext = quotemeta $searchtext; } @@ -172,7 +172,7 @@ my %actions = ( $action = 'summary' if (!defined($action)); if (!defined($actions{$action})) { - die_error(undef, "Unknown action."); + die_error(undef, "Unknown action"); } $actions{$action}->(); exit; @@ -418,7 +418,7 @@ sub git_get_hash_by_path { my $tree = $base; open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path - or die_error(undef, "Open git-ls-tree failed."); + or die_error(undef, "Open git-ls-tree failed"); my $line = <$fd>; close $fd or return undef; @@ -1265,13 +1265,13 @@ sub git_diff_print { sub git_project_list { my $order = $cgi->param('o'); if (defined $order && $order !~ m/project|descr|owner|age/) { - die_error(undef, "Invalid order parameter '$order'."); + die_error(undef, "Invalid order parameter '$order'"); } my @list = git_read_projects(); my @projects; if (!@list) { - die_error(undef, "No projects found."); + die_error(undef, "No projects found"); } foreach my $pr (@list) { my $head = git_read_head($pr->{'path'}); @@ -1405,7 +1405,7 @@ sub git_summary { "\n"; open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) - or die_error(undef, "Open git-rev-list failed."); + or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; git_header_div('shortlog'); @@ -1461,10 +1461,10 @@ sub git_tag { sub git_blame2 { my $fd; my $ftype; - die_error(undef, "Permission denied.") if (!git_get_project_config_bool ('blame')); + die_error(undef, "Permission denied") if (!git_get_project_config_bool ('blame')); die_error('404 Not Found', "File name not defined") if (!$file_name); $hash_base ||= git_read_head($project); - die_error(undef, "Couldn't find base commit.") unless ($hash_base); + die_error(undef, "Couldn't find base commit") unless ($hash_base); my %co = git_read_commit($hash_base) or die_error(undef, "Reading commit failed"); if (!defined $hash) { @@ -1476,7 +1476,7 @@ sub git_blame2 { die_error("400 Bad Request", "Object is not a blob"); } open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base) - or die_error(undef, "Open git-blame failed."); + or die_error(undef, "Open git-blame failed"); git_header_html(); my $formats_nav = $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . @@ -1519,18 +1519,18 @@ sub git_blame2 { sub git_blame { my $fd; - die_error('403 Permission denied', "Permission denied.") if (!git_get_project_config_bool ('blame')); - die_error('404 Not Found', "File name not defined.") if (!$file_name); + die_error('403 Permission denied', "Permission denied") if (!git_get_project_config_bool ('blame')); + die_error('404 Not Found', "File name not defined") if (!$file_name); $hash_base ||= git_read_head($project); - die_error(undef, "Couldn't find base commit.") unless ($hash_base); + die_error(undef, "Couldn't find base commit") unless ($hash_base); my %co = git_read_commit($hash_base) - or die_error(undef, "Reading commit failed."); + or die_error(undef, "Reading commit failed"); if (!defined $hash) { $hash = git_get_hash_by_path($hash_base, $file_name, "blob") - or die_error(undef, "Error lookup file."); + or die_error(undef, "Error lookup file"); } open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base) - or die_error(undef, "Open git-annotate failed."); + or die_error(undef, "Open git-annotate failed"); git_header_html(); my $formats_nav = $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . @@ -1640,9 +1640,9 @@ sub git_blob_plain { if (defined $file_name) { my $base = $hash_base || git_read_head($project); $hash = git_get_hash_by_path($base, $file_name, "blob") - or die_error(undef, "Error lookup file."); + or die_error(undef, "Error lookup file"); } else { - die_error(undef, "No file name defined."); + die_error(undef, "No file name defined"); } } my $type = shift; @@ -1673,14 +1673,14 @@ sub git_blob { if (defined $file_name) { my $base = $hash_base || git_read_head($project); $hash = git_get_hash_by_path($base, $file_name, "blob") - or die_error(undef, "Error lookup file."); + or die_error(undef, "Error lookup file"); } else { - die_error(undef, "No file name defined."); + die_error(undef, "No file name defined"); } } my $have_blame = git_get_project_config_bool ('blame'); open my $fd, "-|", $GIT, "cat-file", "blob", $hash - or die_error(undef, "Couldn't cat $file_name, $hash."); + or die_error(undef, "Couldn't cat $file_name, $hash"); my $mimetype = git_blob_plain_mimetype($fd, $file_name); if ($mimetype !~ m/^text\//) { close $fd; @@ -1738,9 +1738,9 @@ sub git_tree { } $/ = "\0"; open my $fd, "-|", $GIT, "ls-tree", '-z', $hash - or die_error(undef, "Open git-ls-tree failed."); + or die_error(undef, "Open git-ls-tree failed"); my @entries = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading tree failed."); + close $fd or die_error(undef, "Reading tree failed"); $/ = "\n"; my $refs = read_info_ref(); @@ -1819,7 +1819,7 @@ sub git_log { my $limit = sprintf("--max-count=%i", (100 * ($page+1))); open my $fd, "-|", $GIT, "rev-list", $limit, $hash - or die_error(undef, "Open git-rev-list failed."); + or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; @@ -1880,7 +1880,7 @@ sub git_log { sub git_commit { my %co = git_read_commit($hash); if (!%co) { - die_error(undef, "Unknown commit object."); + die_error(undef, "Unknown commit object"); } my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); @@ -1890,9 +1890,9 @@ sub git_commit { $parent = "--root"; } open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash - or die_error(undef, "Open git-diff-tree failed."); + or die_error(undef, "Open git-diff-tree failed"); my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading git-diff-tree failed."); + close $fd or die_error(undef, "Reading git-diff-tree failed"); # non-textual hash id's can be cached my $expires; @@ -2108,15 +2108,15 @@ sub git_commitdiff { mkdir($git_temp, 0700); my %co = git_read_commit($hash); if (!%co) { - die_error(undef, "Unknown commit object."); + die_error(undef, "Unknown commit object"); } if (!defined $hash_parent) { $hash_parent = $co{'parent'}; } open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash - or die_error(undef, "Open git-diff-tree failed."); + or die_error(undef, "Open git-diff-tree failed"); my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading git-diff-tree failed."); + close $fd or die_error(undef, "Reading git-diff-tree failed"); # non-textual hash id's can be cached my $expires; @@ -2194,9 +2194,9 @@ sub git_commitdiff { sub git_commitdiff_plain { mkdir($git_temp, 0700); open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash - or die_error(undef, "Open git-diff-tree failed."); + or die_error(undef, "Open git-diff-tree failed"); my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading diff-tree failed."); + close $fd or die_error(undef, "Reading diff-tree failed"); # try to figure out the next tag after this commit my $tagname; @@ -2254,7 +2254,7 @@ sub git_history { my $ftype; my %co = git_read_commit($hash_base); if (!%co) { - die_error(undef, "Unknown commit object."); + die_error(undef, "Unknown commit object"); } my $refs = read_info_ref(); git_header_html(); @@ -2312,14 +2312,14 @@ sub git_history { sub git_search { if (!defined $searchtext) { - die_error(undef, "Text field empty."); + die_error(undef, "Text field empty"); } if (!defined $hash) { $hash = git_read_head($project); } my %co = git_read_commit($hash); if (!%co) { - die_error(undef, "Unknown commit object."); + die_error(undef, "Unknown commit object"); } # pickaxe may take all resources of your box and run for several minutes # with every query - so decide by yourself how public you make this feature :) @@ -2457,7 +2457,7 @@ sub git_shortlog { my $limit = sprintf("--max-count=%i", (100 * ($page+1))); open my $fd, "-|", $GIT, "rev-list", $limit, $hash - or die_error(undef, "Open git-rev-list failed."); + or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; @@ -2485,9 +2485,9 @@ sub git_shortlog { sub git_rss { # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) - or die_error(undef, "Open git-rev-list failed."); + or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading git-rev-list failed."); + close $fd or die_error(undef, "Reading git-rev-list failed"); print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); print "\n". "\n"; -- cgit v1.2.3 From e2860ead31579a15ee94831f2b9b55e43caa2cac Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 5 Aug 2006 13:15:24 +0200 Subject: gitweb: No error messages with unescaped/unprotected user input Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6284901457..2e2629ca53 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1265,7 +1265,7 @@ sub git_diff_print { sub git_project_list { my $order = $cgi->param('o'); if (defined $order && $order !~ m/project|descr|owner|age/) { - die_error(undef, "Invalid order parameter '$order'"); + die_error(undef, "Unknown order parameter"); } my @list = git_read_projects(); -- cgit v1.2.3 From 668e34d7cc1ab7d6135f0004ee7bea2ffdca0de0 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 5 Aug 2006 13:16:03 +0200 Subject: gitweb: PATH_INFO=/ means no project Prepared for refactoring input validation. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 2e2629ca53..fdba15e2b2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -86,8 +86,8 @@ if (defined $action) { } our $project = ($cgi->param('p') || $ENV{'PATH_INFO'}); -if (defined $project) { - $project =~ s|^/||; $project =~ s|/$||; +$project =~ s|^/||; $project =~ s|/$||; +if (defined $project && $project) { if (!validate_input($project)) { die_error(undef, "Invalid project parameter"); } -- cgit v1.2.3 From 10161355ba3b121c823649ef1acca526d73363f6 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 5 Aug 2006 13:18:58 +0200 Subject: gitweb: Inline $rss_link Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index fdba15e2b2..1b5fec924f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -21,7 +21,6 @@ our $cgi = new CGI; our $version = "++GIT_VERSION++"; our $my_url = $cgi->url(); our $my_uri = $cgi->url(-absolute => 1); -our $rss_link = ""; # core git executable to use # this can just be "git" if your webserver has a sensible PATH @@ -97,8 +96,6 @@ if (defined $project && $project) { if (!(-e "$projectroot/$project/HEAD")) { die_error(undef, "No such project"); } - $rss_link = ""; $ENV{'GIT_DIR'} = "$projectroot/$project"; } else { git_project_list(); @@ -862,11 +859,13 @@ sub git_header_html { $title -$rss_link - - EOF - print "
\n" . + print "\n" . + "\n"; + + print "\n" . + "
\n" . "" . "\"git\"" . "\n"; -- cgit v1.2.3 From f16db173a4680aebc2f14c103a1e125c3f4d4531 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 6 Aug 2006 02:08:31 +0200 Subject: gitweb: Refactor untabifying - converting tabs to spaces Add untabify subroutine and use it. It also fixes git_diff_print which used to get the tabstop wrong. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1b5fec924f..d0672cde3e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -221,6 +221,20 @@ sub unquote { return $str; } +# escape tabs (convert tabs to spaces) +sub untabify { + my $line = shift; + + while ((my $pos = index($line, "\t")) != -1) { + if (my $count = (8 - ($pos % 8))) { + my $spaces = ' ' x $count; + $line =~ s/\t/$spaces/; + } + } + + return $line; +} + ## ---------------------------------------------------------------------- ## HTML aware string manipulation @@ -1237,12 +1251,7 @@ sub git_diff_print { # skip errors next; } - while ((my $pos = index($line, "\t")) != -1) { - if (my $count = (8 - (($pos-1) % 8))) { - my $spaces = ' ' x $count; - $line =~ s/\t/$spaces/; - } - } + $line = untabify($line); print "
" . esc_html($line) . "
\n"; } } @@ -1582,13 +1591,8 @@ HTML $age_class = age_class($age); $author = esc_html ($author); $author =~ s/ / /g; - # escape tabs - while ((my $pos = index($data, "\t")) != -1) { - if (my $count = (8 - ($pos % 8))) { - my $spaces = ' ' x $count; - $data =~ s/\t/$spaces/; - } - } + + $data = untabify($data); $data = esc_html ($data); print <) { chomp $line; $nr++; - while ((my $pos = index($line, "\t")) != -1) { - if (my $count = (8 - ($pos % 8))) { - my $spaces = ' ' x $count; - $line =~ s/\t/$spaces/; - } - } + $line = untabify($line); printf "
%4i %s
\n", $nr, $nr, $nr, esc_html($line); } close $fd or print "Reading blob failed.\n"; -- cgit v1.2.3 From 403ccc4f36101ca5084e659a8c3c9911ccc91de8 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sun, 6 Aug 2006 12:47:33 +0200 Subject: Add gitweb.cgi to .gitignore Signed-off-by: Matthias Kestenholz Signed-off-by: Junio C Hamano --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index fb0fa3f16a..20df1f8b2f 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,7 @@ git-verify-tag git-whatchanged git-write-tree git-core-*/?* +gitweb/gitweb.cgi test-date test-delta test-dump-cache-tree -- cgit v1.2.3 From bd943f4757ab1d62d862ea12b4cf8b6b495e115f Mon Sep 17 00:00:00 2001 From: Matthias Lederhofer Date: Sun, 6 Aug 2006 15:55:02 +0200 Subject: gitweb: check if HTTP_ACCEPT is really set Signed-off-by: Matthias Lederhofer Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index d0672cde3e..5e72b4f617 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -856,7 +856,7 @@ sub git_header_html { # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. # we have to do this because MSIE sometimes globs '*/*', pretending to # support xhtml+xml but choking when it gets what it asked for. - if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) { + if (defined $cgi->http('HTTP_ACCEPT') && $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) { $content_type = 'application/xhtml+xml'; } else { $content_type = 'text/html'; -- cgit v1.2.3 From b5ff2cf9a6a4246a5443e904acf3165e89654b1e Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 6 Aug 2006 16:14:25 +0200 Subject: gitweb: fix commitdiff for root commits After changing all "-|" open invocations to list form, commitdiff for initial commit (without parent) got broken; it returned incorrectly empty patch earlier. Use '--root' option to git-diff-tree for initial (root) commit. No checking for empty $hash_parent in git_commitdiff_plain -- we rely on gitweb to give correct parameters for commitdiff_plain action. Noticed by Matthias Lederhofer (matled). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 5e72b4f617..9be35aebd1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2109,7 +2109,7 @@ sub git_commitdiff { die_error(undef, "Unknown commit object"); } if (!defined $hash_parent) { - $hash_parent = $co{'parent'}; + $hash_parent = $co{'parent'} || '--root'; } open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash or die_error(undef, "Open git-diff-tree failed"); -- cgit v1.2.3 From e349d21ab47ae4a3753749c4579cce150c71c768 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 6 Aug 2006 17:59:52 +0200 Subject: gitweb: Skip nonmatching lines in difftree output, consistently This fixes error for commitdiff on root commit (without parents). Noticed-by: Matthias Lederhofer (matled) Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 9be35aebd1..b8e266e0d9 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1983,7 +1983,7 @@ sub git_commit { foreach my $line (@difftree) { # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { + if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) { next; } my $from_mode = $1; @@ -2156,7 +2156,9 @@ sub git_commitdiff { foreach my $line (@difftree) { # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/; + if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { + next; + } my $from_mode = $1; my $to_mode = $2; my $from_id = $3; @@ -2230,7 +2232,9 @@ sub git_commitdiff_plain { print "---\n\n"; foreach my $line (@difftree) { - $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/; + if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { + next; + } my $from_id = $3; my $to_id = $4; my $status = $5; -- cgit v1.2.3 From 1568515d5b8350a6087c0976c841387d71075c01 Mon Sep 17 00:00:00 2001 From: Matthias Lederhofer Date: Sun, 6 Aug 2006 19:24:47 +0200 Subject: gitweb: fix commitdiff_plain for root commits Signed-off-by: Matthias Lederhofer Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b8e266e0d9..08de2ce77e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2193,6 +2193,13 @@ sub git_commitdiff { sub git_commitdiff_plain { mkdir($git_temp, 0700); + my %co = git_read_commit($hash); + if (!%co) { + die_error(undef, "Unknown commit object"); + } + if (!defined $hash_parent) { + $hash_parent = $co{'parent'} || '--root'; + } open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash or die_error(undef, "Open git-diff-tree failed"); my @difftree = map { chomp; $_ } <$fd>; @@ -2214,7 +2221,6 @@ sub git_commitdiff_plain { } print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\""); - my %co = git_read_commit($hash); my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); my $comment = $co{'comment'}; print "From: $co{'author'}\n" . -- cgit v1.2.3 From dd04c428cfea1d9dd7684bbd9e2919fbad051b26 Mon Sep 17 00:00:00 2001 From: Matthias Lederhofer Date: Sun, 6 Aug 2006 13:25:41 +0200 Subject: gitweb: fix $project usage There were some places where $project was used even if it was not defined. Signed-off-by: Matthias Lederhofer Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 08de2ce77e..b3bfc6bd9e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -85,7 +85,10 @@ if (defined $action) { } our $project = ($cgi->param('p') || $ENV{'PATH_INFO'}); -$project =~ s|^/||; $project =~ s|/$||; +if (defined $project) { + $project =~ s|^/||; + $project =~ s|/$||; +} if (defined $project && $project) { if (!validate_input($project)) { die_error(undef, "Invalid project parameter"); @@ -874,11 +877,15 @@ sub git_header_html { $title EOF - print "\n" . - "\n"; + if (defined $project) { + printf(''."\n", + esc_param($project), + esc_param("$my_uri?p=$project;a=rss")); + } - print "\n" . + print "\n" . + "\n" . "
\n" . "" . "\"git\"" . -- cgit v1.2.3 From d5c31a1ccfa1195cff160bbad995c68037180570 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 4 Aug 2006 23:43:59 +0200 Subject: autoconf: Unset NO_STH and NEED_STH when it is detected not needed When configure detects some NO_XXX or NEEDS_XXX is unneeded, unset this variable (actually set it to empty string). This allow autodetection to override the default set in Makefile. [jc: while at it fixed a thinko in IPv6 detection.] Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- configure.ac | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/configure.ac b/configure.ac index 74c271098c..3c56cc51ac 100644 --- a/configure.ac +++ b/configure.ac @@ -154,31 +154,36 @@ AC_MSG_NOTICE([CHECKS for libraries]) # # Define NO_OPENSSL environment variable if you do not have OpenSSL. # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). -AC_CHECK_LIB([crypto], [SHA1_Init],[], +AC_CHECK_LIB([crypto], [SHA1_Init], +[GIT_CONF_APPEND_LINE(NEEDS_SSL_WITH_CRYPTO=)], [AC_CHECK_LIB([ssl], [SHA1_Init], - [GIT_CONF_APPEND_LINE(NEEDS_SSL_WITH_CRYPTO=YesPlease)], + [GIT_CONF_APPEND_LINE(NEEDS_SSL_WITH_CRYPTO=YesPlease) + GIT_CONF_APPEND_LINE(NEEDS_SSL_WITH_CRYPTO=)], [GIT_CONF_APPEND_LINE(NO_OPENSSL=YesPlease)])]) # # Define NO_CURL if you do not have curl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. -AC_CHECK_LIB([curl], [curl_global_init],[], +AC_CHECK_LIB([curl], [curl_global_init], +[GIT_CONF_APPEND_LINE(NO_CURL=)], [GIT_CONF_APPEND_LINE(NO_CURL=YesPlease)]) # # Define NO_EXPAT if you do not have expat installed. git-http-push is # not built, and you cannot push using http:// and https:// transports. -AC_CHECK_LIB([expat], [XML_ParserCreate],[], +AC_CHECK_LIB([expat], [XML_ParserCreate], +[GIT_CONF_APPEND_LINE(NO_EXPAT=)], [GIT_CONF_APPEND_LINE(NO_EXPAT=YesPlease)]) # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). -AC_CHECK_LIB([c], [iconv],[], +AC_CHECK_LIB([c], [iconv], +[GIT_CONF_APPEND_LINE(NEEDS_LIBICONV=)], [GIT_CONF_APPEND_LINE(NEEDS_LIBICONV=YesPlease)]) # # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, # Patrick Mauritz). -AC_CHECK_LIB([c], [socket],[], -[AC_CHECK_LIB([socket],[socket], - [GIT_CONF_APPEND_LINE(NEEDS_SOCKET=YesPlease)],[])]) +AC_CHECK_LIB([c], [socket], +[GIT_CONF_APPEND_LINE(NEEDS_SOCKET=)], +[GIT_CONF_APPEND_LINE(NEEDS_SOCKET=YesPlease)]) ## Checks for header files. @@ -188,25 +193,30 @@ AC_CHECK_LIB([c], [socket],[], AC_MSG_NOTICE([CHECKS for typedefs, structures, and compiler characteristics]) # # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent. -AC_CHECK_MEMBER(struct dirent.d_ino,[], +AC_CHECK_MEMBER(struct dirent.d_ino, +[GIT_CONF_APPEND_LINE(NO_D_INO_IN_DIRENT=)], [GIT_CONF_APPEND_LINE(NO_D_INO_IN_DIRENT=YesPlease)], [#include ]) # # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks # d_type in struct dirent (latest Cygwin -- will be fixed soonish). -AC_CHECK_MEMBER(struct dirent.d_type,[], +AC_CHECK_MEMBER(struct dirent.d_type, +[GIT_CONF_APPEND_LINE(NO_D_TYPE_IN_DIRENT=)], [GIT_CONF_APPEND_LINE(NO_D_TYPE_IN_DIRENT=YesPlease)], [#include ]) # # Define NO_SOCKADDR_STORAGE if your platform does not have struct # sockaddr_storage. -AC_CHECK_TYPE(struct sockaddr_storage,[], +AC_CHECK_TYPE(struct sockaddr_storage, +[GIT_CONF_APPEND_LINE(NO_SOCKADDR_STORAGE=)], [GIT_CONF_APPEND_LINE(NO_SOCKADDR_STORAGE=YesPlease)], [#include ]) # Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). AC_CHECK_TYPE([struct addrinfo],[ -AC_CHECK_FUNC([getaddrinfo],[], -[GIT_CONF_APPEND_LINE(NO_IPV6=YesPlease)])],[],[ + AC_CHECK_FUNC([getaddrinfo], + [GIT_CONF_APPEND_LINE(NO_IPV6=)], + [GIT_CONF_APPEND_LINE(NO_IPV6=YesPlease)]) +],[GIT_CONF_APPEND_LINE(NO_IPV6=YesPlease)],[ #include #include #include @@ -231,6 +241,8 @@ AC_RUN_IFELSE( ]) if test $ac_cv_c_c99_format = no; then GIT_CONF_APPEND_LINE(NO_C99_FORMAT=YesPlease) +else + GIT_CONF_APPEND_LINE(NO_C99_FORMAT=) fi @@ -239,15 +251,18 @@ fi AC_MSG_NOTICE([CHECKS for library functions]) # # Define NO_STRCASESTR if you don't have strcasestr. -AC_CHECK_FUNC(strcasestr,[], +AC_CHECK_FUNC(strcasestr, +[GIT_CONF_APPEND_LINE(NO_STRCASESTR=)], [GIT_CONF_APPEND_LINE(NO_STRCASESTR=YesPlease)]) # # Define NO_STRLCPY if you don't have strlcpy. -AC_CHECK_FUNC(strlcpy,[], +AC_CHECK_FUNC(strlcpy, +[GIT_CONF_APPEND_LINE(NO_STRLCPY=)], [GIT_CONF_APPEND_LINE(NO_STRLCPY=YesPlease)]) # # Define NO_SETENV if you don't have setenv in the C library. -AC_CHECK_FUNC(setenv,[], +AC_CHECK_FUNC(setenv, +[GIT_CONF_APPEND_LINE(NO_SETENV=)], [GIT_CONF_APPEND_LINE(NO_SETENV=YesPlease)]) # # Define NO_MMAP if you want to avoid mmap. -- cgit v1.2.3 From b7e58b17b50bbbf00299fc89c480efd7cc92c1bd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 5 Aug 2006 04:16:02 -0700 Subject: Racy git: avoid having to be always too careful Immediately after a bulk checkout, most of the paths in the working tree would have the same timestamp as the index file, and this would force ce_match_stat() to take slow path for all of them. When writing an index file out, if many of the paths have very new (read: the same timestamp as the index file being written out) timestamp, we are better off delaying the return from the command, to make sure that later command to touch the working tree files will leave newer timestamps than recorded in the index, thereby avoiding to take the slow path. Signed-off-by: Junio C Hamano --- read-cache.c | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/read-cache.c b/read-cache.c index f92cdaacee..ce76c20f34 100644 --- a/read-cache.c +++ b/read-cache.c @@ -5,6 +5,7 @@ */ #include "cache.h" #include "cache-tree.h" +#include /* Index extensions. * @@ -923,7 +924,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce) * $ echo filfre >nitfol * $ git-update-index --add nitfol * - * but it does not. Whe the second update-index runs, + * but it does not. When the second update-index runs, * it notices that the entry "frotz" has the same timestamp * as index, and if we were to smudge it by resetting its * size to zero here, then the object name recorded @@ -945,7 +946,9 @@ int write_cache(int newfd, struct cache_entry **cache, int entries) { SHA_CTX c; struct cache_header hdr; - int i, removed; + int i, removed, recent; + struct stat st; + time_t now; for (i = removed = 0; i < entries; i++) if (!cache[i]->ce_mode) @@ -959,15 +962,19 @@ int write_cache(int newfd, struct cache_entry **cache, int entries) if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0) return -1; + now = fstat(newfd, &st) ? 0 : st.st_mtime; + recent = 0; for (i = 0; i < entries; i++) { struct cache_entry *ce = cache[i]; + time_t entry_time = (time_t) ntohl(ce->ce_mtime.sec); if (!ce->ce_mode) continue; - if (index_file_timestamp && - index_file_timestamp <= ntohl(ce->ce_mtime.sec)) + if (index_file_timestamp && index_file_timestamp <= entry_time) ce_smudge_racily_clean_entry(ce); if (ce_write(&c, newfd, ce, ce_size(ce)) < 0) return -1; + if (now && now <= entry_time) + recent++; } /* Write extension data here */ @@ -983,5 +990,34 @@ int write_cache(int newfd, struct cache_entry **cache, int entries) return -1; } } + + /* + * To prevent later ce_match_stat() from always falling into + * check_fs(), if we have too many entries that can trigger + * racily clean check, we are better off delaying the return. + * We arbitrarily say if more than 20 paths or 25% of total + * paths are very new, we delay the return until the index + * file gets a new timestamp. + * + * NOTE! NOTE! NOTE! + * + * This assumes that nobody is touching the working tree while + * we are updating the index. + */ + if (20 < recent || entries <= recent * 4) { + now = fstat(newfd, &st) ? 0 : st.st_mtime; + while (now && !fstat(newfd, &st) && st.st_mtime <= now) { + struct timespec rq, rm; + off_t where = lseek(newfd, 0, SEEK_CUR); + rq.tv_sec = 0; + rq.tv_nsec = 250000000; + nanosleep(&rq, &rm); + if ((where == (off_t) -1) || + (write(newfd, "", 1) != 1) || + (lseek(newfd, -1, SEEK_CUR) != where) || + ftruncate(newfd, where)) + break; + } + } return ce_flush(&c, newfd); } -- cgit v1.2.3 From f1efc38bf2856f875b878177c0f0a98d9eb3bb4e Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 8 Aug 2006 01:15:05 +0200 Subject: gitweb: Remove unused variables in git_shortlog_body and git_heads Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 -- 1 file changed, 2 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b3bfc6bd9e..eabece78c2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1065,7 +1065,6 @@ sub git_shortlog_body { #my $ref = defined $refs ? git_get_referencing($refs, $commit) : ''; my $ref = git_get_referencing($refs, $commit); my %co = git_read_commit($commit); - my %ad = date_str($co{'author_epoch'}); if ($alternate) { print "\n"; } else { @@ -1638,7 +1637,6 @@ sub git_heads { git_header_div('summary', $project); my $taglist = git_read_refs("refs/heads"); - my $alternate = 0; if (defined @$taglist) { git_heads_body($taglist, $head); } -- cgit v1.2.3 From d636ad9743783c728469a9b644c23c0415fe1a7b Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Fri, 4 Aug 2006 15:11:47 -0700 Subject: gitweb: bugfix: git_commit and git_commitdiff parents In git_commit() the hash base of $from_id is $parent, not $hash: - If status is "D", then action blob for $from_id wants $parent, not $hash. History needs $parent too. - If status is "R", then action blob for $from_id wants $parent, not $hash. Similarly in git_commitdiff() the hash base of $from_id is $hash_parent, not $hash. Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index eabece78c2..6a92316677 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2015,11 +2015,11 @@ sub git_commit { "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . "\n"; } elsif ($status eq "D") { print "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "\n" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file"), -class => "list"}, esc_html($file)) . "\n" . "[deleted " . file_type($from_mode). "]\n" . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file")}, "blob") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$parent;f=$file")}, "history") . "\n" } elsif ($status eq "M" || $status eq "T") { my $mode_chnge = ""; @@ -2061,7 +2061,7 @@ sub git_commit { print "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file"), -class => "list"}, esc_html($to_file)) . "\n" . "[moved from " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$from_file"), -class => "list"}, esc_html($from_file)) . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$from_file"), -class => "list"}, esc_html($from_file)) . " with " . (int $similarity) . "% similarity$mode_chng]\n" . "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob"); @@ -2177,15 +2177,17 @@ sub git_commitdiff { git_diff_print(undef, "/dev/null", $to_id, "b/$file"); } elsif ($status eq "D") { print "
" . file_type($from_mode) . ":" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . "(deleted)" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) . "(deleted)" . "
\n"; git_diff_print($from_id, "a/$file", undef, "/dev/null"); } elsif ($status eq "M") { if ($from_id ne $to_id) { print "
" . - file_type($from_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . + file_type($from_mode) . ":" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) . " -> " . - file_type($to_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id); + file_type($to_mode) . ":" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id); print "
\n"; git_diff_print($from_id, "a/$file", $to_id, "b/$file"); } -- cgit v1.2.3 From 82f930deadc9a7990cbc0988255e6a5afd641f4b Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Fri, 4 Aug 2006 15:09:59 -0700 Subject: gitweb: blame table row no highlight fix Until now blame just used the commit/tree/tags/etc style of highlight-able table rows, which have alternating light/dark rows that flash when mouse pointer passes over them. This is very annoying in blame, since the text is static and it interferes with the per-revision block highlighting. Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano --- gitweb/gitweb.css | 4 ++++ gitweb/gitweb.perl | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 460e728718..47c1ade87e 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -171,6 +171,10 @@ tr.dark { background-color: #f6f6f0; } +tr.dark2 { + background-color: #f6f6f0; +} + tr.dark:hover { background-color: #edece6; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6a92316677..ae13e3e701 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1498,7 +1498,7 @@ sub git_blame2 { git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype); - my @rev_color = (qw(light dark)); + my @rev_color = (qw(light2 dark2)); my $num_colors = scalar(@rev_color); my $current_color = 0; my $last_rev; -- cgit v1.2.3 From aa5481c1af08edbca821a0d025d26e9bc41a5c49 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 8 Aug 2006 12:23:47 -0700 Subject: debugging: XMALLOC_POISON Compile with -DXMALLOC_POISON=1 to catch errors from using uninitialized memory returned by xmalloc. Signed-off-by: Junio C Hamano --- git-compat-util.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git-compat-util.h b/git-compat-util.h index 93f558056d..3bcf5b13f2 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -91,6 +91,9 @@ static inline void *xmalloc(size_t size) ret = malloc(1); if (!ret) die("Out of memory, malloc failed"); +#ifdef XMALLOC_POISON + memset(ret, 0xA5, size); +#endif return ret; } -- cgit v1.2.3 From 329a304714255f6e5528b4311d29f9d83c85d201 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 8 Aug 2006 12:21:33 -0700 Subject: builtin-mv: fix use of uninitialized memory. Juergen Ruehle noticed that add_slash() tries to strcat() into uninitialized memory and fails. Signed-off-by: Junio C Hamano --- builtin-mv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin-mv.c b/builtin-mv.c index e47942c135..ce8187c1e9 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -48,7 +48,8 @@ static const char *add_slash(const char *path) if (path[len - 1] != '/') { char *with_slash = xmalloc(len + 2); memcpy(with_slash, path, len); - strcat(with_slash + len, "/"); + with_slash[len++] = '/'; + with_slash[len] = 0; return with_slash; } return path; -- cgit v1.2.3 From c96c29093f70f04e01ff1d3a9c1d509ef6afdb00 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 8 Aug 2006 13:11:16 -0700 Subject: GIT-VERSION-GEN: adjust for ancient git When an ancient "git" that does not understand "describe" command is on the $PATH, "git describe" emitted a Usage message without exiting non-zero status (which is a mistake we cannot fix retroactively). Catch this case to make sure we do not try using phoney multi-line string as a version number. Signed-off-by: Junio C Hamano --- GIT-VERSION-GEN | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 1ce217dd7b..14923c973b 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -3,9 +3,17 @@ GVF=GIT-VERSION-FILE DEF_VER=v1.4.2.GIT +LF=' +' + # First try git-describe, then see if there is a version file # (included in release tarballs), then default -if VN=$(git describe --abbrev=4 HEAD 2>/dev/null); then +if VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && + case "$VN" in + *$LF*) (exit 1) ;; + v[0-9]*) : happy ;; + esac +then VN=$(echo "$VN" | sed -e 's/-/./g'); elif test -f version then -- cgit v1.2.3 From 6c8d06aff107aa2132648d682a278111c1d08565 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 8 Aug 2006 16:01:32 -0400 Subject: git-push: allow pushing from subdirectories The semantics are equivalent to pushing from the root; we just try harder to find the .git directory. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git.c b/git.c index 6e72a893b7..18ba14ade1 100644 --- a/git.c +++ b/git.c @@ -229,7 +229,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "log", cmd_log, NEEDS_PREFIX | USE_PAGER }, { "whatchanged", cmd_whatchanged, NEEDS_PREFIX | USE_PAGER }, { "show", cmd_show, NEEDS_PREFIX | USE_PAGER }, - { "push", cmd_push }, + { "push", cmd_push, NEEDS_PREFIX }, { "format-patch", cmd_format_patch, NEEDS_PREFIX }, { "count-objects", cmd_count_objects }, { "diff", cmd_diff, NEEDS_PREFIX }, -- cgit v1.2.3 From 3900145ed72bf1dfd656af6a5b31034e818b425f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 8 Aug 2006 18:35:23 +0200 Subject: autoconf: Add configure target to main Makefile While at it fill git version information in configure.ac configure target needs autoconf, of course. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- INSTALL | 2 +- Makefile | 7 +++++++ configure.ac | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/INSTALL b/INSTALL index ba9778cd4d..fa9bf74a20 100644 --- a/INSTALL +++ b/INSTALL @@ -16,7 +16,7 @@ install" would not work. Alternatively you can use autoconf generated ./configure script to set up install paths (via config.mak.autogen), so you can write instead - $ autoconf ;# as yourself if ./configure doesn't exist yet + $ make configure ;# as yourself $ ./configure --prefix=/usr ;# as yourself $ make all doc ;# as yourself # make install install-doc ;# as root diff --git a/Makefile b/Makefile index ae4c0f2d90..d8cc83507d 100644 --- a/Makefile +++ b/Makefile @@ -595,6 +595,13 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css chmod +x $@+ mv $@+ $@ +configure: configure.ac + rm -f $@ $<+ + sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + $< > $<+ + autoconf -o $@ $<+ + rm -f $<+ + # These can record GIT_VERSION git$X git.spec \ $(patsubst %.sh,%,$(SCRIPT_SH)) \ diff --git a/configure.ac b/configure.ac index 3c56cc51ac..e2da89114e 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) -AC_INIT([git], [1.4.1], [git@vger.kernel.org]) +AC_INIT([git], [@@GIT_VERSION@@], [git@vger.kernel.org]) AC_CONFIG_SRCDIR([git.c]) -- cgit v1.2.3 From b52b1d433b47888d79a73a514aa3430257cba863 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 8 Aug 2006 18:36:21 +0200 Subject: autoconf: Error out on --without-shell and --without-perl Error out on --without-shell/--with-shell=no and --without-perl/--with-perl=no instead of just warning and continuing. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index e2da89114e..c2ddd9b6e8 100644 --- a/configure.ac +++ b/configure.ac @@ -37,7 +37,7 @@ AC_DEFUN([GIT_ARG_SET_PATH], AC_DEFUN([GIT_CONF_APPEND_PATH], [PROGRAM=m4_toupper($1); \ if test "$withval" = "no"; then \ - AC_MSG_WARN([You cannot use git without $1]); \ + AC_MSG_ERROR([You cannot use git without $1]); \ else \ if test "$withval" = "yes"; then \ AC_MSG_WARN([You should provide path for --with-$1=PATH]); \ -- cgit v1.2.3 From af6058cc2bf309631fc7f5cd44fb39b567286506 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 8 Aug 2006 18:38:06 +0200 Subject: autoconf: Improvements in NO_PYTHON/PYTHON_PATH handling Unset NO_PYTHON for --with-python without arguments, and when PYTHON_PATH is set. Do not check for PYTHON_PATH if it is set via --with-python=PYTHON_PATH. Prefer "python" over version specific names such as "python2.4". Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- configure.ac | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/configure.ac b/configure.ac index c2ddd9b6e8..c27a994da0 100644 --- a/configure.ac +++ b/configure.ac @@ -105,7 +105,10 @@ AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python AS_HELP_STRING([--no-python], [don't use python scripts])], [if test "$withval" = "no"; then \ NO_PYTHON=YesPlease; \ - elif test "$withval" != "yes"; then \ + elif test "$withval" = "yes"; then \ + NO_PYTHON=; \ + else \ + NO_PYTHON=; \ PYTHON_PATH=$withval; \ fi; \ ]) @@ -138,15 +141,15 @@ AC_CHECK_PROGS(TAR, [gtar tar]) # Define NO_PYTHON if you want to lose all benefits of the recursive merge. # Define PYTHON_PATH to provide path to Python. if test -z "$NO_PYTHON"; then - AC_PATH_PROGS(PYTHON_PATH, [python2.4 python2.3 python2 python]) + if test -z "$PYTHON_PATH"; then + AC_PATH_PROGS(PYTHON_PATH, [python python2.4 python2.3 python2]) + fi if test -n "$PYTHON_PATH"; then GIT_CONF_APPEND_LINE([PYTHON_PATH=@PYTHON_PATH@]) - else - GIT_CONF_APPEND_LINE([NO_PYTHON=@NO_PYTHON@]) + NO_PYTHON="" fi -else - GIT_CONF_APPEND_LINE([NO_PYTHON=@NO_PYTHON@]) fi +GIT_CONF_APPEND_LINE([NO_PYTHON=@NO_PYTHON@]) ## Checks for libraries. -- cgit v1.2.3 From 6015c28b1d6163f124332769989326ee470afbb6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 8 Aug 2006 14:47:32 -0700 Subject: read-cache: tweak racy-git delay logic Instead of looping over the entries and writing out, use a separate loop after all entries have been written out to check how many entries are racily clean. Make sure that the newly created index file gets the right timestamp when we check by flushing the buffered data by ce_write(). Signed-off-by: Junio C Hamano --- read-cache.c | 73 +++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/read-cache.c b/read-cache.c index ce76c20f34..c923a32707 100644 --- a/read-cache.c +++ b/read-cache.c @@ -841,6 +841,18 @@ unmap: static unsigned char write_buffer[WRITE_BUFFER_SIZE]; static unsigned long write_buffer_len; +static int ce_write_flush(SHA_CTX *context, int fd) +{ + unsigned int buffered = write_buffer_len; + if (buffered) { + SHA1_Update(context, write_buffer, buffered); + if (write(fd, write_buffer, buffered) != buffered) + return -1; + write_buffer_len = 0; + } + return 0; +} + static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len) { while (len) { @@ -851,8 +863,8 @@ static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len) memcpy(write_buffer + buffered, data, partial); buffered += partial; if (buffered == WRITE_BUFFER_SIZE) { - SHA1_Update(context, write_buffer, WRITE_BUFFER_SIZE); - if (write(fd, write_buffer, WRITE_BUFFER_SIZE) != WRITE_BUFFER_SIZE) + write_buffer_len = buffered; + if (ce_write_flush(context, fd)) return -1; buffered = 0; } @@ -962,19 +974,15 @@ int write_cache(int newfd, struct cache_entry **cache, int entries) if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0) return -1; - now = fstat(newfd, &st) ? 0 : st.st_mtime; - recent = 0; for (i = 0; i < entries; i++) { struct cache_entry *ce = cache[i]; - time_t entry_time = (time_t) ntohl(ce->ce_mtime.sec); if (!ce->ce_mode) continue; - if (index_file_timestamp && index_file_timestamp <= entry_time) + if (index_file_timestamp && + index_file_timestamp <= ntohl(ce->ce_mtime.sec)) ce_smudge_racily_clean_entry(ce); if (ce_write(&c, newfd, ce, ce_size(ce)) < 0) return -1; - if (now && now <= entry_time) - recent++; } /* Write extension data here */ @@ -1004,19 +1012,42 @@ int write_cache(int newfd, struct cache_entry **cache, int entries) * This assumes that nobody is touching the working tree while * we are updating the index. */ - if (20 < recent || entries <= recent * 4) { - now = fstat(newfd, &st) ? 0 : st.st_mtime; - while (now && !fstat(newfd, &st) && st.st_mtime <= now) { - struct timespec rq, rm; - off_t where = lseek(newfd, 0, SEEK_CUR); - rq.tv_sec = 0; - rq.tv_nsec = 250000000; - nanosleep(&rq, &rm); - if ((where == (off_t) -1) || - (write(newfd, "", 1) != 1) || - (lseek(newfd, -1, SEEK_CUR) != where) || - ftruncate(newfd, where)) - break; + + /* Make sure that the new index file has st_mtime + * that is current enough -- ce_write() batches the data + * so it might not have written anything yet. + */ + ce_write_flush(&c, newfd); + + now = fstat(newfd, &st) ? 0 : st.st_mtime; + if (now) { + recent = 0; + for (i = 0; i < entries; i++) { + struct cache_entry *ce = cache[i]; + time_t entry_time = (time_t) ntohl(ce->ce_mtime.sec); + if (!ce->ce_mode) + continue; + if (now && now <= entry_time) + recent++; + } + if (20 < recent && entries <= recent * 4) { +#if 0 + fprintf(stderr, "entries %d\n", entries); + fprintf(stderr, "recent %d\n", recent); + fprintf(stderr, "now %lu\n", now); +#endif + while (!fstat(newfd, &st) && st.st_mtime <= now) { + struct timespec rq, rm; + off_t where = lseek(newfd, 0, SEEK_CUR); + rq.tv_sec = 0; + rq.tv_nsec = 250000000; + nanosleep(&rq, &rm); + if ((where == (off_t) -1) || + (write(newfd, "", 1) != 1) || + (lseek(newfd, -1, SEEK_CUR) != where) || + ftruncate(newfd, where)) + break; + } } } return ce_flush(&c, newfd); -- cgit v1.2.3 From 424adc50b73f269ac3f3bd0195f9e7fea935109f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 8 Aug 2006 18:39:06 +0200 Subject: autoconf: Move variables which we always set to config.mak.in Move detected NO_STH and NEED_STH variables, which we always output, either setting or unsetting (setting to empty string) to config.mak.in and use setting appropriately named variables and doing AC_SUBST instead of adding them via GIT_CONF_APPEND_LINE macro and config.mak.append temporary file. Variables which might and might not be set are still added via config.mak.append; this include all STH_PATH variables. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- config.mak.in | 16 +++++++++++++ configure.ac | 75 +++++++++++++++++++++++++++++++++++------------------------ 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/config.mak.in b/config.mak.in index 04f508ab90..369e6116e0 100644 --- a/config.mak.in +++ b/config.mak.in @@ -22,3 +22,19 @@ VPATH = @srcdir@ export exec_prefix mandir export srcdir VPATH +NO_PYTHON=@NO_PYTHON@ +NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@ +NO_OPENSSL=@NO_OPENSSL@ +NO_CURL=@NO_CURL@ +NO_EXPAT=@NO_EXPAT@ +NEEDS_LIBICONV=@NEEDS_LIBICONV@ +NEEDS_SOCKET=@NEEDS_SOCKET@ +NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@ +NO_D_TYPE_IN_DIRENT=@NO_D_TYPE_IN_DIRENT@ +NO_SOCKADDR_STORAGE=@NO_SOCKADDR_STORAGE@ +NO_IPV6=@NO_IPV6@ +NO_C99_FORMAT=@NO_C99_FORMAT@ +NO_STRCASESTR=@NO_STRCASESTR@ +NO_STRLCPY=@NO_STRLCPY@ +NO_SETENV=@NO_SETENV@ + diff --git a/configure.ac b/configure.ac index c27a994da0..bc2824bb88 100644 --- a/configure.ac +++ b/configure.ac @@ -149,7 +149,6 @@ if test -z "$NO_PYTHON"; then NO_PYTHON="" fi fi -GIT_CONF_APPEND_LINE([NO_PYTHON=@NO_PYTHON@]) ## Checks for libraries. @@ -158,35 +157,41 @@ AC_MSG_NOTICE([CHECKS for libraries]) # Define NO_OPENSSL environment variable if you do not have OpenSSL. # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). AC_CHECK_LIB([crypto], [SHA1_Init], -[GIT_CONF_APPEND_LINE(NEEDS_SSL_WITH_CRYPTO=)], +[NEEDS_SSL_WITH_CRYPTO=], [AC_CHECK_LIB([ssl], [SHA1_Init], - [GIT_CONF_APPEND_LINE(NEEDS_SSL_WITH_CRYPTO=YesPlease) - GIT_CONF_APPEND_LINE(NEEDS_SSL_WITH_CRYPTO=)], - [GIT_CONF_APPEND_LINE(NO_OPENSSL=YesPlease)])]) + [NEEDS_SSL_WITH_CRYPTO=YesPlease + NEEDS_SSL_WITH_CRYPTO=], + [NO_OPENSSL=YesPlease])]) +AC_SUBST(NEEDS_SSL_WITH_CRYPTO) +AC_SUBST(NO_OPENSSL) # # Define NO_CURL if you do not have curl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. AC_CHECK_LIB([curl], [curl_global_init], -[GIT_CONF_APPEND_LINE(NO_CURL=)], -[GIT_CONF_APPEND_LINE(NO_CURL=YesPlease)]) +[NO_CURL=], +[NO_CURL=YesPlease]) +AC_SUBST(NO_CURL) # # Define NO_EXPAT if you do not have expat installed. git-http-push is # not built, and you cannot push using http:// and https:// transports. AC_CHECK_LIB([expat], [XML_ParserCreate], -[GIT_CONF_APPEND_LINE(NO_EXPAT=)], -[GIT_CONF_APPEND_LINE(NO_EXPAT=YesPlease)]) +[NO_EXPAT=], +[NO_EXPAT=YesPlease]) +AC_SUBST(NO_EXPAT) # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). AC_CHECK_LIB([c], [iconv], -[GIT_CONF_APPEND_LINE(NEEDS_LIBICONV=)], -[GIT_CONF_APPEND_LINE(NEEDS_LIBICONV=YesPlease)]) +[NEEDS_LIBICONV=], +[NEEDS_LIBICONV=YesPlease]) +AC_SUBST(NEEDS_LIBICONV) # # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, # Patrick Mauritz). AC_CHECK_LIB([c], [socket], -[GIT_CONF_APPEND_LINE(NEEDS_SOCKET=)], -[GIT_CONF_APPEND_LINE(NEEDS_SOCKET=YesPlease)]) +[NEEDS_SOCKET=], +[NEEDS_SOCKET=YesPlease]) +AC_SUBST(NEEDS_SOCKET) ## Checks for header files. @@ -197,33 +202,38 @@ AC_MSG_NOTICE([CHECKS for typedefs, structures, and compiler characteristics]) # # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent. AC_CHECK_MEMBER(struct dirent.d_ino, -[GIT_CONF_APPEND_LINE(NO_D_INO_IN_DIRENT=)], -[GIT_CONF_APPEND_LINE(NO_D_INO_IN_DIRENT=YesPlease)], +[NO_D_INO_IN_DIRENT=], +[NO_D_INO_IN_DIRENT=YesPlease], [#include ]) +AC_SUBST(NO_D_INO_IN_DIRENT) # # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks # d_type in struct dirent (latest Cygwin -- will be fixed soonish). AC_CHECK_MEMBER(struct dirent.d_type, -[GIT_CONF_APPEND_LINE(NO_D_TYPE_IN_DIRENT=)], -[GIT_CONF_APPEND_LINE(NO_D_TYPE_IN_DIRENT=YesPlease)], +[NO_D_TYPE_IN_DIRENT=], +[NO_D_TYPE_IN_DIRENT=YesPlease], [#include ]) +AC_SUBST(NO_D_TYPE_IN_DIRENT) # # Define NO_SOCKADDR_STORAGE if your platform does not have struct # sockaddr_storage. AC_CHECK_TYPE(struct sockaddr_storage, -[GIT_CONF_APPEND_LINE(NO_SOCKADDR_STORAGE=)], -[GIT_CONF_APPEND_LINE(NO_SOCKADDR_STORAGE=YesPlease)], +[NO_SOCKADDR_STORAGE=], +[NO_SOCKADDR_STORAGE=YesPlease], [#include ]) +AC_SUBST(NO_SOCKADDR_STORAGE) +# # Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). AC_CHECK_TYPE([struct addrinfo],[ AC_CHECK_FUNC([getaddrinfo], - [GIT_CONF_APPEND_LINE(NO_IPV6=)], - [GIT_CONF_APPEND_LINE(NO_IPV6=YesPlease)]) -],[GIT_CONF_APPEND_LINE(NO_IPV6=YesPlease)],[ + [NO_IPV6=], + [NO_IPV6=YesPlease]) +],[NO_IPV6=YesPlease],[ #include #include #include ]) +AC_SUBST(NO_IPV6) # # Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) # do not support the 'size specifiers' introduced by C99, namely ll, hh, @@ -243,11 +253,11 @@ AC_RUN_IFELSE( [ac_cv_c_c99_format=no]) ]) if test $ac_cv_c_c99_format = no; then - GIT_CONF_APPEND_LINE(NO_C99_FORMAT=YesPlease) + NO_C99_FORMAT=YesPlease else - GIT_CONF_APPEND_LINE(NO_C99_FORMAT=) + NO_C99_FORMAT= fi - +AC_SUBST(NO_C99_FORMAT) ## Checks for library functions. ## (in default C library and libraries checked by AC_CHECK_LIB) @@ -255,18 +265,21 @@ AC_MSG_NOTICE([CHECKS for library functions]) # # Define NO_STRCASESTR if you don't have strcasestr. AC_CHECK_FUNC(strcasestr, -[GIT_CONF_APPEND_LINE(NO_STRCASESTR=)], -[GIT_CONF_APPEND_LINE(NO_STRCASESTR=YesPlease)]) +[NO_STRCASESTR=], +[NO_STRCASESTR=YesPlease]) +AC_SUBST(NO_STRCASESTR) # # Define NO_STRLCPY if you don't have strlcpy. AC_CHECK_FUNC(strlcpy, -[GIT_CONF_APPEND_LINE(NO_STRLCPY=)], -[GIT_CONF_APPEND_LINE(NO_STRLCPY=YesPlease)]) +[NO_STRLCPY=], +[NO_STRLCPY=YesPlease]) +AC_SUBST(NO_STRLCPY) # # Define NO_SETENV if you don't have setenv in the C library. AC_CHECK_FUNC(setenv, -[GIT_CONF_APPEND_LINE(NO_SETENV=)], -[GIT_CONF_APPEND_LINE(NO_SETENV=YesPlease)]) +[NO_SETENV=], +[NO_SETENV=YesPlease]) +AC_SUBST(NO_SETENV) # # Define NO_MMAP if you want to avoid mmap. # -- cgit v1.2.3 From 1b1b678e3c4eebe0302455c248941e5627ce2b5b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 8 Aug 2006 13:42:35 -0700 Subject: autoconf: clean temporary file mak.append When configure is interrupted in the middle it leaves config.mak.append behind. Add it to .gitignore and make sure $(MAKE) clean removes it. Also earlier .gitignore listed config.mak.in which is a tracked file. Fix it. Signed-off-by: Junio C Hamano --- .gitignore | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index fb0fa3f16a..6e973b3d4b 100644 --- a/.gitignore +++ b/.gitignore @@ -140,7 +140,7 @@ config.mak autom4te.cache config.log config.status -config.mak.in config.mak.autogen +config.mak.append configure git-blame diff --git a/Makefile b/Makefile index d8cc83507d..d4297c27c1 100644 --- a/Makefile +++ b/Makefile @@ -793,7 +793,7 @@ clean: rm -f $(ALL_PROGRAMS) $(BUILT_INS) git$X rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags rm -rf autom4te.cache - rm -f config.log config.mak.autogen configure config.status config.cache + rm -f configure config.log config.mak.autogen config.mak.append config.status config.cache rm -rf $(GIT_TARNAME) .doc-tmp-dir rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz rm -f $(htmldocs).tar.gz $(manpages).tar.gz -- cgit v1.2.3 From d5dc6a76d49367cddc015e01d2e9aa22e64d7e28 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Wed, 9 Aug 2006 02:26:23 +0200 Subject: Update git-init-db(1) and documentation of core.sharedRepository Combine option descriptions in git-init-db(1). Reflect the changes to additionally allow all users to read the created git repository. Signed-off-by: Jonas Fonseca Signed-off-by: Junio C Hamano --- Documentation/config.txt | 9 +++++--- Documentation/git-init-db.txt | 51 +++++++++++++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index d89916bea7..ce722a2db0 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -83,9 +83,12 @@ core.repositoryFormatVersion:: version. core.sharedRepository:: - If true, the repository is made shareable between several users - in a group (making sure all the files and objects are group-writable). - See gitlink:git-init-db[1]. False by default. + When 'group' (or 'true'), the repository is made shareable between + several users in a group (making sure all the files and objects are + group-writable). When 'all' (or 'world' or 'everybody'), the + repository will be readable by all users, additionally to being + group-shareable. When 'umask' (or 'false'), git will use permissions + reported by umask(2). See gitlink:git-init-db[1]. False by default. core.warnAmbiguousRefs:: If true, git will warn you if the ref name you passed it is ambiguous diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index 0a4fc14b97..63cd5dab3f 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -8,17 +8,47 @@ git-init-db - Creates an empty git repository SYNOPSIS -------- -'git-init-db' [--template=] [--shared] +'git-init-db' [--template=] [--shared[=]] OPTIONS ------- + +-- + --template=:: - Provide the directory from which templates will be used. - The default template directory is `/usr/share/git-core/templates`. ---shared:: - Specify that the git repository is to be shared amongst several users. +Provide the directory from which templates will be used. The default template +directory is `/usr/share/git-core/templates`. + +When specified, `` is used as the source of the template +files rather than the default. The template files include some directory +structure, some suggested "exclude patterns", and copies of non-executing +"hook" files. The suggested patterns and hook files are all modifiable and +extensible. + +--shared[={false|true|umask|group|all|world|everybody}]:: + +Specify that the git repository is to be shared amongst several users. This +allows users belonging to the same group to push into that +repository. When specified, the config variable "core.sharedRepository" is +set so that files and directories under `$GIT_DIR` are created with the +requested permissions. When not specified, git will use permissions reported +by umask(2). + +The option can have the following values, defaulting to 'group' if no value +is given: + + - 'umask' (or 'false'): Use permissions reported by umask(2). The default, + when `--shared` is not specified. + + - 'group' (or 'true'): Make the repository group-writable, (and g+sx, since + the git group may be not the primary group of all users). + + - 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository + readable by all users. + +-- DESCRIPTION @@ -29,12 +59,6 @@ template files. An initial `HEAD` file that references the HEAD of the master branch is also created. -If `--template=` is specified, `` -is used as the source of the template files rather than the default. -The template files include some directory structure, some suggested -"exclude patterns", and copies of non-executing "hook" files. The -suggested patterns and hook files are all modifiable and extensible. - If the `$GIT_DIR` environment variable is set then it specifies a path to use instead of `./.git` for the base of the repository. @@ -42,11 +66,6 @@ If the object storage directory is specified via the `$GIT_OBJECT_DIRECTORY` environment variable then the sha1 directories are created underneath - otherwise the default `$GIT_DIR/objects` directory is used. -A shared repository allows users belonging to the same group to push into that -repository. When specifying `--shared` the config variable "core.sharedRepository" -is set to 'true' so that directories under `$GIT_DIR` are made group writable -(and g+sx, since the git group may be not the primary group of all users). - Running `git-init-db` in an existing repository is safe. It will not overwrite things that are already there. The primary reason for rerunning `git-init-db` is to pick up newly added templates. -- cgit v1.2.3 From 7b1e9d3d854f0c0df58b88809793c1b114a45b90 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Wed, 9 Aug 2006 02:19:22 +0200 Subject: autoconf: It is --without-python, not --no-python Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index bc2824bb88..3fd3d55e43 100644 --- a/configure.ac +++ b/configure.ac @@ -102,7 +102,7 @@ GIT_ARG_SET_PATH(perl) # Define NO_PYTHON if you want to lose all benefits of the recursive merge. # Define PYTHON_PATH to provide path to Python. AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python]) -AS_HELP_STRING([--no-python], [don't use python scripts])], +AS_HELP_STRING([--without-python], [don't use python scripts])], [if test "$withval" = "no"; then \ NO_PYTHON=YesPlease; \ elif test "$withval" = "yes"; then \ -- cgit v1.2.3 From fb6ff943de33dccc3f43541ed4ae4b71abddac8b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 7 Aug 2006 22:41:32 -0700 Subject: Documentation: git-status takes the same options as git-commit Signed-off-by: Junio C Hamano --- Documentation/git-status.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index e446f4812e..ce7857e5a9 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -8,7 +8,7 @@ git-status - Show working tree status SYNOPSIS -------- -'git-status' +'git-status' ... DESCRIPTION ----------- @@ -23,6 +23,10 @@ If there is no path that is different between the index file and the current HEAD commit, the command exits with non-zero status. +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`. + OUTPUT ------ -- cgit v1.2.3 From a20b4d899ae75e075641d7bfe9462a239c8b9236 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Wed, 9 Aug 2006 02:15:10 +0200 Subject: autoconf: Add support for setting CURLDIR, OPENSSLDIR, EXPATDIR Add support for --with-openssl=PATH and --without-openssl, --with-curl=PATH and --without-curl, --with-expat=PATH and --without-expat ./configure options, each setting or unsetting appropriate NO_PACKAGE and if called with argument also PACKAGEDIR (of which only CURLDIR is documented in Makefile) All above options are supported as override to autodetection; more to come in the same style (override to autodetection), so moved the bulk of comments for site configuration down. Needs review by someone well versed in autoconf and m4. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- configure.ac | 158 ++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 101 insertions(+), 57 deletions(-) diff --git a/configure.ac b/configure.ac index 3fd3d55e43..e890131c46 100644 --- a/configure.ac +++ b/configure.ac @@ -46,53 +46,29 @@ else \ fi; \ fi; \ ]) # GIT_CONF_APPEND_PATH +# +# GIT_PARSE_WITH(PACKAGE) +# ----------------------- +# For use in AC_ARG_WITH action-if-found, for packages default ON. +# * Set NO_PACKAGE=YesPlease for --without-PACKAGE +# * Set PACKAGEDIR=PATH for --with-PACKAGE=PATH +# * Unset NO_PACKAGE for --with-PACKAGE without ARG +AC_DEFUN([GIT_PARSE_WITH], +[PACKAGE=m4_toupper($1); \ +if test "$withval" = "no"; then \ + m4_toupper(NO_$1)=YesPlease; \ +elif test "$withval" = "yes"; then \ + m4_toupper(NO_$1)=; \ +else \ + m4_toupper(NO_$1)=; \ + GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \ +fi \ +])# GIT_PARSE_WITH -## Site configuration +## Site configuration related to programs (before tests) ## --with-PACKAGE[=ARG] and --without-PACKAGE # -# Define NO_SVN_TESTS if you want to skip time-consuming SVN interoperability -# tests. These tests take up a significant amount of the total test time -# but are not needed unless you plan to talk to SVN repos. -# -# Define MOZILLA_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast -# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default -# choice) has very fast version optimized for i586. -# -# Define PPC_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine optimized for PowerPC. -# -# Define ARM_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine optimized for ARM. -# -# Define NO_OPENSSL environment variable if you do not have OpenSSL. -# This also implies MOZILLA_SHA1. -# -# Define NO_CURL if you do not have curl installed. git-http-pull and -# git-http-push are not built, and you cannot use http:// and https:// -# transports. -# -# Define CURLDIR=/foo/bar if your curl header and library files are in -# /foo/bar/include and /foo/bar/lib directories. -# -# Define NO_EXPAT if you do not have expat installed. git-http-push is -# not built, and you cannot push using http:// and https:// transports. -# -# Define NO_FINK if you are building on Darwin/Mac OS X, have Fink -# installed in /sw, but don't want GIT to link against any libraries -# installed there. If defined you may specify your own (or Fink's) -# include directories and library directories by defining CFLAGS -# and LDFLAGS appropriately. -# -# Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X, -# have DarwinPorts installed in /opt/local, but don't want GIT to -# link against any libraries installed there. If defined you may -# specify your own (or DarwinPort's) include directories and -# library directories by defining CFLAGS and LDFLAGS appropriately. -# -# Define NO_MMAP if you want to avoid mmap. -# # Define SHELL_PATH to provide path to shell. GIT_ARG_SET_PATH(shell) # @@ -114,20 +90,6 @@ AS_HELP_STRING([--without-python], [don't use python scripts])], ]) AC_SUBST(NO_PYTHON) AC_SUBST(PYTHON_PATH) -# -## --enable-FEATURE[=ARG] and --disable-FEATURE -# Define COLLISION_CHECK below if you believe that SHA1's -# 1461501637330902918203684832716283019655932542976 hashes do not give you -# sufficient guarantee that no collisions between objects will ever happen. -# -# Define USE_NSEC below if you want git to care about sub-second file mtimes -# and ctimes. Note that you need recent 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). -# -# Define USE_STDEV below if you want git to care about the underlying device -# change being considered an inode change from the update-cache perspective. ## Checks for programs. @@ -259,6 +221,7 @@ else fi AC_SUBST(NO_C99_FORMAT) + ## Checks for library functions. ## (in default C library and libraries checked by AC_CHECK_LIB) AC_MSG_NOTICE([CHECKS for library functions]) @@ -299,6 +262,87 @@ AC_SUBST(NO_SETENV) # a missing newline at the end of the file. +## Site configuration (override autodetection) +## --with-PACKAGE[=ARG] and --without-PACKAGE +AC_MSG_NOTICE([CHECKS for site configuration]) +# +# Define NO_SVN_TESTS if you want to skip time-consuming SVN interoperability +# tests. These tests take up a significant amount of the total test time +# but are not needed unless you plan to talk to SVN repos. +# +# Define MOZILLA_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast +# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default +# choice) has very fast version optimized for i586. +# +# Define PPC_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine optimized for PowerPC. +# +# Define ARM_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine optimized for ARM. +# +# Define NO_OPENSSL environment variable if you do not have OpenSSL. +# This also implies MOZILLA_SHA1. +# +# Define OPENSSLDIR=/foo/bar if your openssl header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(openssl, +AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)]) +AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),\ +GIT_PARSE_WITH(openssl)) +# +# Define NO_CURL if you do not have curl installed. git-http-pull and +# git-http-push are not built, and you cannot use http:// and https:// +# transports. +# +# Define CURLDIR=/foo/bar if your curl header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(curl, +AS_HELP_STRING([--with-curl],[support http(s):// transports (default is YES)]) +AS_HELP_STRING([], [ARG can be also prefix for curl library and headers]), +GIT_PARSE_WITH(curl)) +# +# Define NO_EXPAT if you do not have expat installed. git-http-push is +# not built, and you cannot push using http:// and https:// transports. +# +# Define EXPATDIR=/foo/bar if your expat header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(expat, +AS_HELP_STRING([--with-expat], +[support git-push using http:// and https:// transports via WebDAV (default is YES)]) +AS_HELP_STRING([], [ARG can be also prefix for expat library and headers]), +GIT_PARSE_WITH(expat)) +# +# Define NO_FINK if you are building on Darwin/Mac OS X, have Fink +# installed in /sw, but don't want GIT to link against any libraries +# installed there. If defined you may specify your own (or Fink's) +# include directories and library directories by defining CFLAGS +# and LDFLAGS appropriately. +# +# Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X, +# have DarwinPorts installed in /opt/local, but don't want GIT to +# link against any libraries installed there. If defined you may +# specify your own (or DarwinPort's) include directories and +# library directories by defining CFLAGS and LDFLAGS appropriately. +# +# Define NO_MMAP if you want to avoid mmap. + +## --enable-FEATURE[=ARG] and --disable-FEATURE +# +# Define COLLISION_CHECK below if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# sufficient guarantee that no collisions between objects will ever happen. +# +# Define USE_NSEC below if you want git to care about sub-second file mtimes +# and ctimes. Note that you need recent 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). +# +# Define USE_STDEV below if you want git to care about the underlying device +# change being considered an inode change from the update-cache perspective. + + ## Output files AC_CONFIG_FILES(["${config_file}":"${config_in}":"${config_append}"]) AC_OUTPUT -- cgit v1.2.3 From 1d17c25c38a6b98d20b7bbe789b45996fd84d672 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 9 Aug 2006 00:57:01 -0700 Subject: Fix tutorial-2.html Honza Pazdziora noticed that one example did not match reality. Signed-off-by: Junio C Hamano --- Documentation/tutorial-2.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt index 894ca5e06f..2f4fe1217a 100644 --- a/Documentation/tutorial-2.txt +++ b/Documentation/tutorial-2.txt @@ -244,6 +244,7 @@ $ git ls-files --stage $ git cat-file -t 513feba2 blob $ git cat-file blob 513feba2 +hello world! hello world, again ------------------------------------------------ -- cgit v1.2.3 From 72ee96c0f1861fcdb9eaf39e78d86888437a27a9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 9 Aug 2006 12:45:27 -0700 Subject: check return value from diff_setup_done() Signed-off-by: Junio C Hamano --- builtin-diff.c | 3 ++- revision.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/builtin-diff.c b/builtin-diff.c index 1075855102..dd9886c6e8 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -253,7 +253,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix) argc = setup_revisions(argc, argv, &rev, NULL); if (!rev.diffopt.output_format) { rev.diffopt.output_format = DIFF_FORMAT_PATCH; - diff_setup_done(&rev.diffopt); + if (diff_setup_done(&rev.diffopt) < 0) + die("diff_setup_done failed"); } /* Do we have --cached and not have a pending object, then diff --git a/revision.c b/revision.c index a58257ad80..5a91d06b98 100644 --- a/revision.c +++ b/revision.c @@ -936,7 +936,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->diffopt.output_format = DIFF_FORMAT_PATCH; } revs->diffopt.abbrev = revs->abbrev; - diff_setup_done(&revs->diffopt); + if (diff_setup_done(&revs->diffopt) < 0) + die("diff_setup_done failed"); return left; } -- cgit v1.2.3 From 02c5cba2007856465710aa37cd41b404372ab95b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 9 Aug 2006 13:17:04 -0700 Subject: find_unique_abbrev() with len=0 should not abbreviate Signed-off-by: Junio C Hamano --- sha1_name.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sha1_name.c b/sha1_name.c index 5fe8e5d4bf..c5a05faeb6 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -193,7 +193,7 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len) is_null = !memcmp(sha1, null_sha1, 20); memcpy(hex, sha1_to_hex(sha1), 40); - if (len == 40) + if (len == 40 || !len) return hex; while (len < 40) { unsigned char sha1_ret[20]; -- cgit v1.2.3 From 03b9d560bed6029f43968ad3f09a8f9c015b20e0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 9 Aug 2006 13:17:19 -0700 Subject: make --find-copies-harder imply -C Signed-off-by: Junio C Hamano --- diff.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/diff.c b/diff.c index 895c137655..02a409d964 100644 --- a/diff.c +++ b/diff.c @@ -1515,9 +1515,10 @@ void diff_setup(struct diff_options *options) int diff_setup_done(struct diff_options *options) { - if ((options->find_copies_harder && - options->detect_rename != DIFF_DETECT_COPY) || - (0 <= options->rename_limit && !options->detect_rename)) + if (options->find_copies_harder) + options->detect_rename = DIFF_DETECT_COPY; + + if ((0 <= options->rename_limit && !options->detect_rename) return -1; if (options->output_format & (DIFF_FORMAT_NAME | -- cgit v1.2.3 From 943d5b73e2adf3cd0d3f72c9a06c75681a4ea3ca Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 9 Aug 2006 14:05:23 -0700 Subject: allow diff.renamelimit to be set regardless of -M/-C Signed-off-by: Junio C Hamano --- diff.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/diff.c b/diff.c index 02a409d964..b3b1781a9c 100644 --- a/diff.c +++ b/diff.c @@ -1518,9 +1518,6 @@ int diff_setup_done(struct diff_options *options) if (options->find_copies_harder) options->detect_rename = DIFF_DETECT_COPY; - if ((0 <= options->rename_limit && !options->detect_rename) - return -1; - if (options->output_format & (DIFF_FORMAT_NAME | DIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_CHECKDIFF | -- cgit v1.2.3 From 2c71810b90d122abdcc57fc3cb62174d16e77b58 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 9 Aug 2006 22:47:25 -0700 Subject: git-apply: applying a patch to make a symlink shorter. The internal representation of the result is counted string (i.e. char *buf and ulong size), which is fine for writing out to regular file, but throwing the buf at symlink(2) was a no-no. Reported by Willy Tarreau. Signed-off-by: Junio C Hamano --- builtin-apply.c | 11 +++++++++++ t/t4115-apply-symlink.sh | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100755 t/t4115-apply-symlink.sh diff --git a/builtin-apply.c b/builtin-apply.c index f8c6763c74..c15987386b 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1698,6 +1698,14 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * desc.buffer = buf; if (apply_fragments(&desc, patch) < 0) return -1; + + /* NUL terminate the result */ + if (desc.alloc <= desc.size) { + desc.buffer = xrealloc(desc.buffer, desc.size + 1); + desc.alloc++; + } + desc.buffer[desc.size] = 0; + patch->result = desc.buffer; patch->resultsize = desc.size; @@ -2040,6 +2048,9 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf, int fd; if (S_ISLNK(mode)) + /* Although buf:size is counted string, it also is NUL + * terminated. + */ return symlink(buf, path); fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666); if (fd < 0) diff --git a/t/t4115-apply-symlink.sh b/t/t4115-apply-symlink.sh new file mode 100755 index 0000000000..d5f2cfb186 --- /dev/null +++ b/t/t4115-apply-symlink.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply symlinks and partial files + +' + +. ./test-lib.sh + +test_expect_success setup ' + + ln -s path1/path2/path3/path4/path5 link1 && + git add link? && + git commit -m initial && + + git branch side && + + rm -f link? && + + ln -s htap6 link1 && + git update-index link? && + git commit -m second && + + git diff-tree -p HEAD^ HEAD >patch && + git apply --stat --summary patch + +' + +test_expect_success 'apply symlink patch' ' + + git checkout side && + git apply patch && + git diff-files -p >patched && + diff -u patch patched + +' + +test_expect_success 'apply --index symlink patch' ' + + git checkout -f side && + git apply --index patch && + git diff-index --cached -p HEAD >patched && + diff -u patch patched + +' + +test_done -- cgit v1.2.3 From 1e8d304507b2d7e3b411d9b351fd348edd253df8 Mon Sep 17 00:00:00 2001 From: Rutger Nijlunsing Date: Wed, 9 Aug 2006 20:54:23 +0200 Subject: http-push: Make WebDAV work with (broken?) default apache2 WebDAV module WebDAV on Debian unstable cannot handle renames on WebDAV from file.ext to newfile (without ext) when newfile* already exists. Normally, git creates a file like 'objects/xx/sha1.token', which is renamed to 'objects/xx/sha1' when transferred completely. Just use '_' instead of '.' so WebDAV doesn't see it as an extension change. Signed-off-by: Rutger Nijlunsing Acked-by: Johannes Schindelin 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 4021e7d927..d45733ef64 100644 --- a/http-push.c +++ b/http-push.c @@ -530,7 +530,7 @@ static void start_put(struct transfer_request *request) request->dest = xmalloc(strlen(request->url) + 14); sprintf(request->dest, "Destination: %s", request->url); posn += 38; - *(posn++) = '.'; + *(posn++) = '_'; strcpy(posn, request->lock->token); slot = get_active_slot(); -- cgit v1.2.3 From 567a03d14cb624b0edc76be9cffda11edac2bea3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 10 Aug 2006 00:30:33 -0700 Subject: combine-diff: use color Using the same mechanism as the regular diffs, color combined diff output. Signed-off-by: Junio C Hamano --- combine-diff.c | 50 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/combine-diff.c b/combine-diff.c index 919112bba9..ba8baca0ab 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -497,11 +497,17 @@ static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long printf(" -%lu,%lu", l0, l1-l0); } -static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent) +static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, + int use_color) { unsigned long mark = (1UL<lost_head; while (ll) { + fputs(c_old, stdout); for (j = 0; j < num_parent; j++) { if (ll->parent_map & (1UL<line); + printf("%s%s\n", ll->line, c_reset); ll = ll->next; } if (cnt < lno) break; p_mask = 1; + if (!(sl->flag & (mark-1))) + fputs(c_plain, stdout); + else + fputs(c_new, stdout); for (j = 0; j < num_parent; j++) { if (p_mask & sl->flag) putchar('+'); @@ -554,7 +566,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent) putchar(' '); p_mask <<= 1; } - printf("%.*s\n", sl->len, sl->bol); + printf("%.*s%s\n", sl->len, sl->bol, c_reset); } } } @@ -586,14 +598,15 @@ static void reuse_combine_diff(struct sline *sline, unsigned long cnt, sline->p_lno[i] = sline->p_lno[j]; } -static void dump_quoted_path(const char *prefix, const char *path) +static void dump_quoted_path(const char *prefix, const char *path, + const char *c_meta, const char *c_reset) { - fputs(prefix, stdout); + printf("%s%s", c_meta, prefix); if (quote_c_style(path, NULL, NULL, 0)) quote_c_style(path, NULL, stdout, 0); else printf("%s", path); - putchar('\n'); + printf("%s\n", c_reset); } static int show_patch_diff(struct combine_diff_path *elem, int num_parent, @@ -699,18 +712,22 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, if (show_hunks || mode_differs || working_tree_file) { const char *abb; + int use_color = opt->color_diff; + const char *c_meta = diff_get_color(use_color, DIFF_METAINFO); + const char *c_reset = diff_get_color(use_color, DIFF_RESET); if (rev->loginfo) show_log(rev, opt->msg_sep); - dump_quoted_path(dense ? "diff --cc " : "diff --combined ", elem->path); - printf("index "); + dump_quoted_path(dense ? "diff --cc " : "diff --combined ", + elem->path, c_meta, c_reset); + printf("%sindex ", c_meta); for (i = 0; i < num_parent; i++) { abb = find_unique_abbrev(elem->parent[i].sha1, abbrev); printf("%s%s", i ? "," : "", abb); } abb = find_unique_abbrev(elem->sha1, abbrev); - printf("..%s\n", abb); + printf("..%s%s\n", abb, c_reset); if (mode_differs) { int added = !!elem->mode; @@ -719,10 +736,11 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, DIFF_STATUS_ADDED) added = 0; if (added) - printf("new file mode %06o", elem->mode); + printf("%snew file mode %06o", + c_meta, elem->mode); else { if (!elem->mode) - printf("deleted file "); + printf("%sdeleted file ", c_meta); printf("mode "); for (i = 0; i < num_parent; i++) { printf("%s%06o", i ? "," : "", @@ -731,11 +749,11 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, if (elem->mode) printf("..%06o", elem->mode); } - putchar('\n'); + printf("%s\n", c_reset); } - dump_quoted_path("--- a/", elem->path); - dump_quoted_path("+++ b/", elem->path); - dump_sline(sline, cnt, num_parent); + dump_quoted_path("--- a/", elem->path, c_meta, c_reset); + dump_quoted_path("+++ b/", elem->path, c_meta, c_reset); + dump_sline(sline, cnt, num_parent, opt->color_diff); } free(result); -- cgit v1.2.3 From 306ea2df03322ac8c29f4eb5a968acb7ef3c8f72 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 10 Aug 2006 00:50:15 -0700 Subject: Fix git-diff A...B Commit 9919f41 meant to make git-diff A...B to (usually) mean "git-diff `git-merge-base A B` B", but it got the parameters wrong and ended up showing "git-diff `git-merge-base A B` A" by mistake. Signed-off-by: Junio C Hamano --- builtin-diff.c | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin-diff.c b/builtin-diff.c index dd9886c6e8..a090e298a5 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -349,6 +349,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) * A and B. We have ent[0] == merge-base, ent[1] == A, * and ent[2] == B. Show diff between the base and B. */ + ent[1] = ent[2]; return builtin_diff_tree(&rev, argc, argv, ent); } else -- cgit v1.2.3 From 242abf106c6929028c2dc916504879885cd64d4d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 10 Aug 2006 00:56:40 -0700 Subject: builtin-apply: remove unused increment We do not use desc.alloc after assigning desc.buffer to patch->result; do not bother to increment it. Signed-off-by: Junio C Hamano --- builtin-apply.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index c15987386b..be2c7152cd 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1700,10 +1700,8 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * return -1; /* NUL terminate the result */ - if (desc.alloc <= desc.size) { + if (desc.alloc <= desc.size) desc.buffer = xrealloc(desc.buffer, desc.size + 1); - desc.alloc++; - } desc.buffer[desc.size] = 0; patch->result = desc.buffer; -- cgit v1.2.3 From 83a2b841d6b90e6f4b797df40ed3a105364574b6 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Thu, 10 Aug 2006 17:02:30 +0200 Subject: Add has_extension() The little helper has_extension() documents through its name what we are trying to do and makes sure we don't forget the underrun check. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- builtin-help.c | 2 +- git-compat-util.h | 6 ++++++ http-fetch.c | 2 +- index-pack.c | 2 +- local-fetch.c | 4 ++-- refs.c | 2 +- sha1_file.c | 2 +- verify-pack.c | 2 +- 8 files changed, 14 insertions(+), 8 deletions(-) diff --git a/builtin-help.c b/builtin-help.c index fb731cc934..7a7f7759e5 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -140,7 +140,7 @@ static void list_commands(const char *exec_path, const char *pattern) continue; entlen = strlen(de->d_name); - if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe")) + if (has_extension(de->d_name, entlen, ".exe")) entlen -= 4; if (longest < entlen) diff --git a/git-compat-util.h b/git-compat-util.h index 3bcf5b13f2..dd92093652 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -139,6 +139,12 @@ static inline ssize_t xwrite(int fd, const void *buf, size_t len) } } +static inline int has_extension(const char *filename, int len, const char *ext) +{ + int extlen = strlen(ext); + return len > extlen && !memcmp(filename + len - extlen, ext, extlen); +} + /* Sane ctype - no locale, and works with signed chars */ #undef isspace #undef isdigit diff --git a/http-fetch.c b/http-fetch.c index 36af3e5b94..6ea39f0589 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -870,7 +870,7 @@ static void process_ls_pack(struct remote_ls_ctx *ls) if (strlen(ls->dentry_name) == 63 && !strncmp(ls->dentry_name, "objects/pack/pack-", 18) && - !strncmp(ls->dentry_name+58, ".pack", 5)) { + has_extension(ls->dentry_name, 63, ".pack")) { get_sha1_hex(ls->dentry_name + 18, sha1); setup_index(ls->repo, sha1); } diff --git a/index-pack.c b/index-pack.c index b39953dc69..a91e39ecd6 100644 --- a/index-pack.c +++ b/index-pack.c @@ -447,7 +447,7 @@ int main(int argc, char **argv) usage(index_pack_usage); if (!index_name) { int len = strlen(pack_name); - if (len < 5 || strcmp(pack_name + len - 5, ".pack")) + if (!has_extension(pack_name, len, ".pack")) die("packfile name '%s' does not end with '.pack'", pack_name); index_name_buf = xmalloc(len); diff --git a/local-fetch.c b/local-fetch.c index 4bf86fbbe2..b6ec170c01 100644 --- a/local-fetch.c +++ b/local-fetch.c @@ -43,8 +43,8 @@ static int setup_indices(void) return -1; while ((de = readdir(dir)) != NULL) { int namelen = strlen(de->d_name); - if (namelen != 50 || - strcmp(de->d_name + namelen - 5, ".pack")) + if (namelen != 50 || + !has_extension(de->d_name, namelen, ".pack")) continue; get_sha1_hex(de->d_name + 5, sha1); setup_index(sha1); diff --git a/refs.c b/refs.c index 02850b6908..b01835f634 100644 --- a/refs.c +++ b/refs.c @@ -147,7 +147,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u namelen = strlen(de->d_name); if (namelen > 255) continue; - if (namelen>5 && !strcmp(de->d_name+namelen-5,".lock")) + if (has_extension(de->d_name, namelen, ".lock")) continue; memcpy(path + baselen, de->d_name, namelen+1); if (stat(git_path("%s", path), &st) < 0) diff --git a/sha1_file.c b/sha1_file.c index 43bc2ea0cf..a1bb01ca35 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -590,7 +590,7 @@ static void prepare_packed_git_one(char *objdir, int local) int namelen = strlen(de->d_name); struct packed_git *p; - if (strcmp(de->d_name + namelen - 4, ".idx")) + if (!has_extension(de->d_name, namelen, ".idx")) continue; /* we have .idx. Is it a file we can map? */ diff --git a/verify-pack.c b/verify-pack.c index c99db9dd79..ef00204ad4 100644 --- a/verify-pack.c +++ b/verify-pack.c @@ -10,7 +10,7 @@ static int verify_one_pack(char *arg, int verbose) /* Should name foo.idx, but foo.pack may be named; * convert it to foo.idx */ - if (!strcmp(arg + len - 5, ".pack")) { + if (has_extension(arg, len, ".pack")) { strcpy(arg + len - 5, ".idx"); len--; } -- cgit v1.2.3 From 6f05b57da8e82c471ea6765da67e813d496ed278 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Thu, 10 Aug 2006 17:02:31 +0200 Subject: git-verify-pack: show usage when no pack was specified Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- verify-pack.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/verify-pack.c b/verify-pack.c index ef00204ad4..7201596bf9 100644 --- a/verify-pack.c +++ b/verify-pack.c @@ -34,6 +34,7 @@ int main(int ac, char **av) int errs = 0; int verbose = 0; int no_more_options = 0; + int nothing_done = 1; while (1 < ac) { char path[PATH_MAX]; @@ -50,8 +51,13 @@ int main(int ac, char **av) strcpy(path, av[1]); if (verify_one_pack(path, verbose)) errs++; + nothing_done = 0; } ac--; av++; } + + if (nothing_done) + usage(verify_pack_usage); + return !!errs; } -- cgit v1.2.3 From ae9c86f2b6c89a3a0991209dae51086f884959c0 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Thu, 10 Aug 2006 17:02:32 +0200 Subject: git-verify-pack: more careful path handling Use strlcpy() to copy the filename into a buffer and complain if it doesn't fit. Also move the path buffer into verify_one_pack(); it is used only there. Now we can const'ify the first argument of this function. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- verify-pack.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/verify-pack.c b/verify-pack.c index 7201596bf9..77b3d282da 100644 --- a/verify-pack.c +++ b/verify-pack.c @@ -1,11 +1,16 @@ #include "cache.h" #include "pack.h" -static int verify_one_pack(char *arg, int verbose) +static int verify_one_pack(const char *path, int verbose) { - int len = strlen(arg); + char arg[PATH_MAX]; + int len; struct packed_git *g; - + + len = strlcpy(arg, path, PATH_MAX); + if (len >= PATH_MAX) + return error("name too long: %s", path); + while (1) { /* Should name foo.idx, but foo.pack may be named; * convert it to foo.idx @@ -37,8 +42,6 @@ int main(int ac, char **av) int nothing_done = 1; while (1 < ac) { - char path[PATH_MAX]; - if (!no_more_options && av[1][0] == '-') { if (!strcmp("-v", av[1])) verbose = 1; @@ -48,8 +51,7 @@ int main(int ac, char **av) usage(verify_pack_usage); } else { - strcpy(path, av[1]); - if (verify_one_pack(path, verbose)) + if (verify_one_pack(av[1], verbose)) errs++; nothing_done = 0; } -- cgit v1.2.3 From 68f4c78b95b9d119d8888b40b0a93b93a39b2f26 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Thu, 10 Aug 2006 17:02:33 +0200 Subject: git-verify-pack: insist on .idx extension git-verify-pack can be called with a filename without .idx extension. add_packed_git() on the other hand depends on its presence. So instead of trying to call it with whatever the user gave us check for that extension and add it if it's missing. That means that you can't name your index file "blah" and your pack file ".pack" anymore ("git-verify-pack blah" currently works in that case). I think this regression is a good change. ;-) Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- verify-pack.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/verify-pack.c b/verify-pack.c index 77b3d282da..002b711617 100644 --- a/verify-pack.c +++ b/verify-pack.c @@ -18,13 +18,12 @@ static int verify_one_pack(const char *path, int verbose) if (has_extension(arg, len, ".pack")) { strcpy(arg + len - 5, ".idx"); len--; + } else if (!has_extension(arg, len, ".idx")) { + if (len + 4 >= PATH_MAX) + return error("name too long: %s.idx", arg); + strcpy(arg + len, ".idx"); + len += 4; } - /* Should name foo.idx now */ - if ((g = add_packed_git(arg, len, 1))) - break; - /* No? did you name just foo? */ - strcpy(arg + len, ".idx"); - len += 4; if ((g = add_packed_git(arg, len, 1))) break; return error("packfile %s not found.", arg); -- cgit v1.2.3 From fc5fc50980958f742a9f3d79fb7a64f02e87877a Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Thu, 10 Aug 2006 17:02:34 +0200 Subject: git-verify-pack: get rid of while loop Get rid of that while loop which was apparently used as a way to avoid goto's (why?). It's easy now because there is only one break left at the end of it. Also make the comment clearer. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- verify-pack.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/verify-pack.c b/verify-pack.c index 002b711617..c7293140f6 100644 --- a/verify-pack.c +++ b/verify-pack.c @@ -11,23 +11,23 @@ static int verify_one_pack(const char *path, int verbose) if (len >= PATH_MAX) return error("name too long: %s", path); - while (1) { - /* Should name foo.idx, but foo.pack may be named; - * convert it to foo.idx - */ - if (has_extension(arg, len, ".pack")) { - strcpy(arg + len - 5, ".idx"); - len--; - } else if (!has_extension(arg, len, ".idx")) { - if (len + 4 >= PATH_MAX) - return error("name too long: %s.idx", arg); - strcpy(arg + len, ".idx"); - len += 4; - } - if ((g = add_packed_git(arg, len, 1))) - break; - return error("packfile %s not found.", arg); + /* + * In addition to "foo.idx" we accept "foo.pack" and "foo"; + * normalize these forms to "foo.idx" for add_packed_git(). + */ + if (has_extension(arg, len, ".pack")) { + strcpy(arg + len - 5, ".idx"); + len--; + } else if (!has_extension(arg, len, ".idx")) { + if (len + 4 >= PATH_MAX) + return error("name too long: %s.idx", arg); + strcpy(arg + len, ".idx"); + len += 4; } + + if (!(g = add_packed_git(arg, len, 1))) + return error("packfile %s not found.", arg); + return verify_pack(g, verbose); } -- cgit v1.2.3 From d0d619c8c50b90fbbd6a7e0994fde073341bf92b Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Thu, 10 Aug 2006 17:02:35 +0200 Subject: git-verify-pack: free pack after use and a cleanup Plug memory leak in verify_one_pack() by freeing the struct packed_git we got from add_packed_git(). Also rename g to pack and pull an assignment out of an if statement while we're at it. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- verify-pack.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/verify-pack.c b/verify-pack.c index c7293140f6..78d789c62d 100644 --- a/verify-pack.c +++ b/verify-pack.c @@ -5,7 +5,8 @@ static int verify_one_pack(const char *path, int verbose) { char arg[PATH_MAX]; int len; - struct packed_git *g; + struct packed_git *pack; + int err; len = strlcpy(arg, path, PATH_MAX); if (len >= PATH_MAX) @@ -25,10 +26,14 @@ static int verify_one_pack(const char *path, int verbose) len += 4; } - if (!(g = add_packed_git(arg, len, 1))) + pack = add_packed_git(arg, len, 1); + if (!pack) return error("packfile %s not found.", arg); - return verify_pack(g, verbose); + err = verify_pack(pack, verbose); + free(pack); + + return err; } static const char verify_pack_usage[] = "git-verify-pack [-v] ..."; -- cgit v1.2.3 From f711ab5470cd1da7fdafa3b7b5e39015dcfca5ce Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Thu, 10 Aug 2006 17:02:36 +0200 Subject: git-verify-pack: buffer overrun paranoia Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- verify-pack.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/verify-pack.c b/verify-pack.c index 78d789c62d..99c352ee3a 100644 --- a/verify-pack.c +++ b/verify-pack.c @@ -26,6 +26,15 @@ static int verify_one_pack(const char *path, int verbose) len += 4; } + /* + * add_packed_git() uses our buffer (containing "foo.idx") to + * build the pack filename ("foo.pack"). Make sure it fits. + */ + if (len + 1 >= PATH_MAX) { + arg[len - 4] = '\0'; + return error("name too long: %s.pack", arg); + } + pack = add_packed_git(arg, len, 1); if (!pack) return error("packfile %s not found.", arg); -- cgit v1.2.3 From 0eaf22f4c4bba89457e733264e71343368712793 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Thu, 10 Aug 2006 17:02:37 +0200 Subject: git-verify-pack: no need to count errors Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- verify-pack.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/verify-pack.c b/verify-pack.c index 99c352ee3a..f440a39678 100644 --- a/verify-pack.c +++ b/verify-pack.c @@ -49,7 +49,7 @@ static const char verify_pack_usage[] = "git-verify-pack [-v] ..."; int main(int ac, char **av) { - int errs = 0; + int err = 0; int verbose = 0; int no_more_options = 0; int nothing_done = 1; @@ -65,7 +65,7 @@ int main(int ac, char **av) } else { if (verify_one_pack(av[1], verbose)) - errs++; + err = 1; nothing_done = 0; } ac--; av++; @@ -74,5 +74,5 @@ int main(int ac, char **av) if (nothing_done) usage(verify_pack_usage); - return !!errs; + return err; } -- cgit v1.2.3 From 2e3ed670eb09feffe847af55db38da3dcecc2a88 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Thu, 10 Aug 2006 17:02:38 +0200 Subject: git-verify-pack: make builtin Convert git-verify-pack to a builtin command. Also rename ac to argc and av to argv for consistancy. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- Makefile | 1 + builtin-verify-pack.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 1 + verify-pack.c | 78 -------------------------------------------------- 5 files changed, 82 insertions(+), 78 deletions(-) create mode 100644 builtin-verify-pack.c delete mode 100644 verify-pack.c diff --git a/Makefile b/Makefile index 733fa660d9..a3ba585cee 100644 --- a/Makefile +++ b/Makefile @@ -275,6 +275,7 @@ BUILTIN_OBJS = \ builtin-update-index.o \ builtin-update-ref.o \ builtin-upload-tar.o \ + builtin-verify-pack.o \ builtin-write-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) diff --git a/builtin-verify-pack.c b/builtin-verify-pack.c new file mode 100644 index 0000000000..d700761e15 --- /dev/null +++ b/builtin-verify-pack.c @@ -0,0 +1,79 @@ +#include "builtin.h" +#include "cache.h" +#include "pack.h" + +static int verify_one_pack(const char *path, int verbose) +{ + char arg[PATH_MAX]; + int len; + struct packed_git *pack; + int err; + + len = strlcpy(arg, path, PATH_MAX); + if (len >= PATH_MAX) + return error("name too long: %s", path); + + /* + * In addition to "foo.idx" we accept "foo.pack" and "foo"; + * normalize these forms to "foo.idx" for add_packed_git(). + */ + if (has_extension(arg, len, ".pack")) { + strcpy(arg + len - 5, ".idx"); + len--; + } else if (!has_extension(arg, len, ".idx")) { + if (len + 4 >= PATH_MAX) + return error("name too long: %s.idx", arg); + strcpy(arg + len, ".idx"); + len += 4; + } + + /* + * add_packed_git() uses our buffer (containing "foo.idx") to + * build the pack filename ("foo.pack"). Make sure it fits. + */ + if (len + 1 >= PATH_MAX) { + arg[len - 4] = '\0'; + return error("name too long: %s.pack", arg); + } + + pack = add_packed_git(arg, len, 1); + if (!pack) + return error("packfile %s not found.", arg); + + err = verify_pack(pack, verbose); + free(pack); + + return err; +} + +static const char verify_pack_usage[] = "git-verify-pack [-v] ..."; + +int cmd_verify_pack(int argc, const char **argv, const char *prefix) +{ + int err = 0; + int verbose = 0; + int no_more_options = 0; + int nothing_done = 1; + + while (1 < argc) { + if (!no_more_options && argv[1][0] == '-') { + if (!strcmp("-v", argv[1])) + verbose = 1; + else if (!strcmp("--", argv[1])) + no_more_options = 1; + else + usage(verify_pack_usage); + } + else { + if (verify_one_pack(argv[1], verbose)) + err = 1; + nothing_done = 0; + } + argc--; argv++; + } + + if (nothing_done) + usage(verify_pack_usage); + + return err; +} diff --git a/builtin.h b/builtin.h index c0bdb051bd..ade58c4a1f 100644 --- a/builtin.h +++ b/builtin.h @@ -59,5 +59,6 @@ extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); extern int cmd_version(int argc, const char **argv, const char *prefix); extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); +extern int cmd_verify_pack(int argc, const char **argv, const char *prefix); #endif diff --git a/git.c b/git.c index db0f86790d..5da7787d86 100644 --- a/git.c +++ b/git.c @@ -270,6 +270,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "version", cmd_version }, { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, { "write-tree", cmd_write_tree, RUN_SETUP }, + { "verify-pack", cmd_verify_pack }, }; int i; diff --git a/verify-pack.c b/verify-pack.c deleted file mode 100644 index f440a39678..0000000000 --- a/verify-pack.c +++ /dev/null @@ -1,78 +0,0 @@ -#include "cache.h" -#include "pack.h" - -static int verify_one_pack(const char *path, int verbose) -{ - char arg[PATH_MAX]; - int len; - struct packed_git *pack; - int err; - - len = strlcpy(arg, path, PATH_MAX); - if (len >= PATH_MAX) - return error("name too long: %s", path); - - /* - * In addition to "foo.idx" we accept "foo.pack" and "foo"; - * normalize these forms to "foo.idx" for add_packed_git(). - */ - if (has_extension(arg, len, ".pack")) { - strcpy(arg + len - 5, ".idx"); - len--; - } else if (!has_extension(arg, len, ".idx")) { - if (len + 4 >= PATH_MAX) - return error("name too long: %s.idx", arg); - strcpy(arg + len, ".idx"); - len += 4; - } - - /* - * add_packed_git() uses our buffer (containing "foo.idx") to - * build the pack filename ("foo.pack"). Make sure it fits. - */ - if (len + 1 >= PATH_MAX) { - arg[len - 4] = '\0'; - return error("name too long: %s.pack", arg); - } - - pack = add_packed_git(arg, len, 1); - if (!pack) - return error("packfile %s not found.", arg); - - err = verify_pack(pack, verbose); - free(pack); - - return err; -} - -static const char verify_pack_usage[] = "git-verify-pack [-v] ..."; - -int main(int ac, char **av) -{ - int err = 0; - int verbose = 0; - int no_more_options = 0; - int nothing_done = 1; - - while (1 < ac) { - if (!no_more_options && av[1][0] == '-') { - if (!strcmp("-v", av[1])) - verbose = 1; - else if (!strcmp("--", av[1])) - no_more_options = 1; - else - usage(verify_pack_usage); - } - else { - if (verify_one_pack(av[1], verbose)) - err = 1; - nothing_done = 0; - } - ac--; av++; - } - - if (nothing_done) - usage(verify_pack_usage); - - return err; -} -- cgit v1.2.3 From 26298b5f7ba816960032f427aef331a46e2fb71b Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 10 Aug 2006 12:38:47 +0200 Subject: gitweb: Whitespace cleanup - tabs are for indent, spaces are for align Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index eabece78c2..ddf593a5bf 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1003,7 +1003,7 @@ sub git_get_paging_nav { if ($page > 0) { $paging_nav .= " ⋅ " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)), - -accesskey => "p", -title => "Alt-p"}, "prev"); + -accesskey => "p", -title => "Alt-p"}, "prev"); } else { $paging_nav .= " ⋅ prev"; } @@ -1011,7 +1011,7 @@ sub git_get_paging_nav { if ($nrevs >= (100 * ($page+1)-1)) { $paging_nav .= " ⋅ " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)), - -accesskey => "n", -title => "Alt-n"}, "next"); + -accesskey => "n", -title => "Alt-n"}, "next"); } else { $paging_nav .= " ⋅ next"; } @@ -1844,9 +1844,9 @@ sub git_log { next if !%co; my %ad = date_str($co{'author_epoch'}); git_header_div('commit', - "$co{'age_string'}" . - esc_html($co{'title'}) . $ref, - $commit); + "$co{'age_string'}" . + esc_html($co{'title'}) . $ref, + $commit); print "
\n" . "
\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . @@ -1911,8 +1911,8 @@ sub git_commit { } git_header_html(undef, $expires); git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', - $hash, $co{'tree'}, $hash, - $formats_nav); + $hash, $co{'tree'}, $hash, + $formats_nav); if (defined $co{'parent'}) { git_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); -- cgit v1.2.3 From 65cdb5f1658c4911b3f17a2e99332e5397c52240 Mon Sep 17 00:00:00 2001 From: Rutger Nijlunsing Date: Thu, 10 Aug 2006 22:00:26 +0200 Subject: Add Documentation/howto/setup-git-server-over-http.txt A small howto on how to setup GIT over HTTP transport protocol by setting up WebDAV access on apache2. [jc: minimum ispell fixes applied] Signed-off-by: Rutger Nijlunsing Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/howto/setup-git-server-over-http.txt | 256 +++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 Documentation/howto/setup-git-server-over-http.txt diff --git a/Documentation/howto/setup-git-server-over-http.txt b/Documentation/howto/setup-git-server-over-http.txt new file mode 100644 index 0000000000..ba191569af --- /dev/null +++ b/Documentation/howto/setup-git-server-over-http.txt @@ -0,0 +1,256 @@ +From: Rutger Nijlunsing +Subject: Setting up a git repository which can be pushed into and pulled from over HTTP. +Date: Thu, 10 Aug 2006 22:00:26 +0200 + +Since Apache is one of those packages people like to compile +themselves while others prefer the bureaucrat's dream Debian, it is +impossible to give guidelines which will work for everyone. Just send +some feedback to the mailing list at git@vger.kernel.org to get this +document tailored to your favorite distro. + + +What's needed: + +- Have an Apache web-server + + On Debian: + $ apt-get install apache2 + To get apache2 by default started, + edit /etc/default/apache2 and set NO_START=0 + +- can edit the configuration of it. + + This could be found under /etc/httpd, or refer to your Apache documentation. + + On Debian: this means being able to edit files under /etc/apache2 + +- can restart it. + + 'apachectl --graceful' might do. If it doesn't, just stop and + restart apache. Be warning that active connections to your server + might be aborted by this. + + On Debian: + $ /etc/init.d/apache2 restart + or + $ /etc/init.d/apache2 force-reload + (which seems to do the same) + This adds symlinks from the /etc/apache2/mods-enabled to + /etc/apache2/mods-available. + +- have permissions to chown a directory + +- have git installed at the server _and_ client + +In effect, this probably means you're going to be root. + + +Step 1: setup a bare GIT repository +----------------------------------- + +At the time of writing, git-http-push cannot remotely create a GIT +repository. So we have to do that at the server side with git. Another +option would be to generate an empty repository at the client and copy +it to the server with WebDAV. But then you're probably the first to +try that out :) + +Create the directory under the DocumentRoot of the directories served +by Apache. As an example we take /usr/local/apache2, but try "grep +DocumentRoot /where/ever/httpd.conf" to find your root: + + $ cd /usr/local/apache/htdocs + $ mkdir my-new-repo.git + + On Debian: + + $ cd /var/www + $ mkdir my-new-repo.git + + +Initialize a bare repository + + $ cd my-new-repo.git + $ git --bare init-db + + +Change the ownership to your web-server's credentials. Use "grep ^User +httpd.conf" and "grep ^Group httpd.conf" to find out: + + $ chown -R www.www . + + On Debian: + + $ chown -R www-data.www-data . + + +If you do not know which user Apache runs as, you can alternatively do +a "chmod -R a+w .", inspect the files which are created later on, and +set the permissions appropriately. + +Restart apache2, and check whether http://server/my-new-repo.git gives +a directory listing. If not, check whether apache started up +successfully. + + +Step 2: enable DAV on this repository +------------------------------------- + +First make sure the dav_module is loaded. For this, insert in httpd.conf: + + LoadModule dav_module libexec/httpd/libdav.so + AddModule mod_dav.c + +Also make sure that this line exists which is the file used for +locking DAV operations: + + DAVLockDB "/usr/local/apache2/temp/DAV.lock" + + On Debian these steps can be performed with: + + Enable the dav and dav_fs modules of apache: + $ a2enmod dav_fs + (just to be sure. dav_fs might be unneeded, I don't know) + $ a2enmod dav + The DAV lock is located in /etc/apache2/mods-available/dav_fs.conf: + DAVLockDB /var/lock/apache2/DAVLock + +Of course, it can point somewhere else, but the string is actually just a +prefix in some Apache configurations, and therefore the _directory_ has to +be writable by the user Apache runs as. + +Then, add something like this to your httpd.conf + + + DAV on + AuthType Basic + AuthName "Git" + AuthUserFile /usr/local/apache2/conf/passwd.git + Require valid-user + + + On Debian: + Create (or add to) /etc/apache2/conf.d/git.conf : + + + DAV on + AuthType Basic + AuthName "Git" + AuthUserFile /etc/apache2/passwd.git + Require valid-user + + + Debian automatically reads all files under /etc/apach2/conf.d. + +The password file can be somewhere else, but it has to be readable by +Apache and preferably not readable by the world. + +Create this file by + $ htpasswd -c /usr/local/apache2/conf/passwd.git + + On Debian: + $ htpasswd -c /etc/apache2/passwd.git + +You will be asked a password, and the file is created. Subsequent calls +to htpasswd should omit the '-c' option, since you want to append to the +existing file. + +You need to restart Apache. + +Now go to http://@/my-new-repo.git in your +browser to check whether it asks for a password and accepts the right +password. + +On Debian: + + To test the WebDAV part, do: + + $ apt-get install litmus + $ litmus http:///my-new-repo.git + + Most tests should pass. + +A command line tool to test WebDAV is cadaver. + +If you're into Windows, from XP onwards Internet Explorer supports +WebDAV. For this, do Internet Explorer -> Open Location -> +http:///my-new-repo.git [x] Open as webfolder -> login . + + +Step 3: setup the client +------------------------ + +Make sure that you have HTTP support, i.e. your git was built with curl. +The easiest way to check is to look for the executable 'git-http-push'. + +Then, add the following to your $HOME/.netrc (you can do without, but will be +asked to input your password a _lot_ of times): + + machine + login + password + +...and set permissions: + chmod 600 ~/.netrc + +If you want to access the web-server by its IP, you have to type that in, +instead of the server name. + +To check whether all is OK, do: + + curl --netrc --location -v http://@/my-new-repo.git/ + +...this should give a directory listing in HTML of /var/www/my-new-repo.git . + + +Now, add the remote in your existing repository which contains the project +you want to export: + + $ git-repo-config remote.upload.url \ + http://@/my-new-repo.git/ + +It is important to put the last '/'; Without it, the server will send +a redirect which git-http-push does not (yet) understand, and git-http-push +will repeat the request infinitely. + + +Step 4: make the initial push +----------------------------- + +From your client repository, do + + $ git push upload master + +This pushes branch 'master' (which is assumed to be the branch you +want to export) to repository called 'upload', which we previously +defined with git-repo-config. + + +Troubleshooting: +---------------- + +If git-http-push says + + Error: no DAV locking support on remote repo http://... + +then it means the web-server did not accept your authentication. Make sure +that the user name and password matches in httpd.conf, .netrc and the URL +you are uploading to. + +If git-http-push shows you an error (22/502) when trying to MOVE a blob, +it means that your web-server somehow does not recognize its name in the +request; This can happen when you start Apache, but then disable the +network interface. A simple restart of Apache helps. + +Errors like (22/502) are of format (curl error code/http error +code). So (22/404) means something like 'not found' at the server. + +Reading /usr/local/apache2/logs/error_log is often helpful. + + On Debian: Read /var/log/apache2/error.log instead. + + +Debian References: http://www.debian-administration.org/articles/285 + +Authors + Johannes Schindelin + Rutger Nijlunsing -- cgit v1.2.3 From f59a59e22f0005d799fe1d40e0c1f7e5c1f68b76 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 28 Jul 2006 23:56:15 +0200 Subject: Add the --color-words option to the diff options family With this option, the changed words are shown inline. For example, if a file containing "This is foo" is changed to "This is bar", the diff will now show "This is " in plain text, "foo" in red, and "bar" in green. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 3 + diff.c | 178 +++++++++++++++++++++++++++++++++++++++-- diff.h | 3 +- 3 files changed, 177 insertions(+), 7 deletions(-) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 47ba9a403a..b5d9763594 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -36,6 +36,9 @@ Turn off colored diff, even when the configuration file gives the default to color output. +--color-words:: + Show colored word diff, i.e. color words which have changed. + --no-renames:: Turn off rename detection, even when the configuration file gives the default to do so. diff --git a/diff.c b/diff.c index b3b1781a9c..a8710cb9e2 100644 --- a/diff.c +++ b/diff.c @@ -358,12 +358,152 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) return 0; } +struct diff_words_buffer { + mmfile_t text; + long alloc; + long current; /* output pointer */ + int suppressed_newline; +}; + +static void diff_words_append(char *line, unsigned long len, + struct diff_words_buffer *buffer) +{ + if (buffer->text.size + len > buffer->alloc) { + buffer->alloc = (buffer->text.size + len) * 3 / 2; + buffer->text.ptr = xrealloc(buffer->text.ptr, buffer->alloc); + } + line++; + len--; + memcpy(buffer->text.ptr + buffer->text.size, line, len); + buffer->text.size += len; +} + +struct diff_words_data { + struct xdiff_emit_state xm; + struct diff_words_buffer minus, plus; +}; + +static void print_word(struct diff_words_buffer *buffer, int len, int color, + int suppress_newline) +{ + const char *ptr; + int eol = 0; + + if (len == 0) + return; + + ptr = buffer->text.ptr + buffer->current; + buffer->current += len; + + if (ptr[len - 1] == '\n') { + eol = 1; + len--; + } + + fputs(diff_get_color(1, color), stdout); + fwrite(ptr, len, 1, stdout); + fputs(diff_get_color(1, DIFF_RESET), stdout); + + if (eol) { + if (suppress_newline) + buffer->suppressed_newline = 1; + else + putchar('\n'); + } +} + +static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) +{ + struct diff_words_data *diff_words = priv; + + if (diff_words->minus.suppressed_newline) { + if (line[0] != '+') + putchar('\n'); + diff_words->minus.suppressed_newline = 0; + } + + len--; + switch (line[0]) { + case '-': + print_word(&diff_words->minus, len, DIFF_FILE_OLD, 1); + break; + case '+': + print_word(&diff_words->plus, len, DIFF_FILE_NEW, 0); + break; + case ' ': + print_word(&diff_words->plus, len, DIFF_PLAIN, 0); + diff_words->minus.current += len; + break; + } +} + +/* this executes the word diff on the accumulated buffers */ +static void diff_words_show(struct diff_words_data *diff_words) +{ + xpparam_t xpp; + xdemitconf_t xecfg; + xdemitcb_t ecb; + mmfile_t minus, plus; + int i; + + minus.size = diff_words->minus.text.size; + minus.ptr = xmalloc(minus.size); + memcpy(minus.ptr, diff_words->minus.text.ptr, minus.size); + for (i = 0; i < minus.size; i++) + if (isspace(minus.ptr[i])) + minus.ptr[i] = '\n'; + diff_words->minus.current = 0; + + plus.size = diff_words->plus.text.size; + plus.ptr = xmalloc(plus.size); + memcpy(plus.ptr, diff_words->plus.text.ptr, plus.size); + for (i = 0; i < plus.size; i++) + if (isspace(plus.ptr[i])) + plus.ptr[i] = '\n'; + diff_words->plus.current = 0; + + xpp.flags = XDF_NEED_MINIMAL; + xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc; + xecfg.flags = 0; + ecb.outf = xdiff_outf; + ecb.priv = diff_words; + diff_words->xm.consume = fn_out_diff_words_aux; + xdl_diff(&minus, &plus, &xpp, &xecfg, &ecb); + + free(minus.ptr); + free(plus.ptr); + diff_words->minus.text.size = diff_words->plus.text.size = 0; + + if (diff_words->minus.suppressed_newline) { + putchar('\n'); + diff_words->minus.suppressed_newline = 0; + } +} + struct emit_callback { struct xdiff_emit_state xm; int nparents, color_diff; const char **label_path; + struct diff_words_data *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); + + if (ecbdata->diff_words->minus.text.ptr) + free (ecbdata->diff_words->minus.text.ptr); + if (ecbdata->diff_words->plus.text.ptr) + free (ecbdata->diff_words->plus.text.ptr); + free(ecbdata->diff_words); + ecbdata->diff_words = NULL; + } +} + const char *diff_get_color(int diff_use_color, enum color_diff ix) { if (diff_use_color) @@ -398,12 +538,31 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) else { int nparents = ecbdata->nparents; int color = DIFF_PLAIN; - for (i = 0; i < nparents && len; i++) { - if (line[i] == '-') - color = DIFF_FILE_OLD; - else if (line[i] == '+') - color = DIFF_FILE_NEW; - } + if (ecbdata->diff_words && nparents != 1) + /* fall back to normal diff */ + free_diff_words_data(ecbdata); + if (ecbdata->diff_words) { + if (line[0] == '-') { + diff_words_append(line, len, + &ecbdata->diff_words->minus); + return; + } else if (line[0] == '+') { + diff_words_append(line, 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); + line++; + len--; + } else + for (i = 0; i < nparents && len; i++) { + if (line[i] == '-') + color = DIFF_FILE_OLD; + else if (line[i] == '+') + color = DIFF_FILE_NEW; + } set = diff_get_color(ecbdata->color_diff, color); } if (len > 0 && line[len-1] == '\n') @@ -836,7 +995,12 @@ static void builtin_diff(const char *name_a, ecb.outf = xdiff_outf; ecb.priv = &ecbdata; ecbdata.xm.consume = fn_out_consume; + if (o->color_diff_words) + ecbdata.diff_words = + xcalloc(1, sizeof(struct diff_words_data)); xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); + if (o->color_diff_words) + free_diff_words_data(&ecbdata); } free_ab_and_return: @@ -1697,6 +1861,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->xdl_opts |= XDF_IGNORE_WHITESPACE; else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change")) options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE; + else if (!strcmp(arg, "--color-words")) + options->color_diff = options->color_diff_words = 1; else if (!strcmp(arg, "--no-renames")) options->detect_rename = 0; else diff --git a/diff.h b/diff.h index 2cced530fa..b007240a5d 100644 --- a/diff.h +++ b/diff.h @@ -46,7 +46,8 @@ struct diff_options { full_index:1, silent_on_remove:1, find_copies_harder:1, - color_diff:1; + color_diff:1, + color_diff_words:1; int context; int break_opt; int detect_rename; -- cgit v1.2.3 From 5bb1cda5f73988963e7470f3cd75a380751f6d99 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Fri, 11 Aug 2006 14:01:45 +0200 Subject: drop length argument of has_extension As Fredrik points out the current interface of has_extension() is potentially confusing. Its parameters include both a nul-terminated string and a length-limited string. This patch drops the length argument, requiring two nul-terminated strings; all callsites are updated. I checked that all of them indeed provide nul-terminated strings. Filenames need to be nul-terminated anyway if they are to be passed to open() etc. The performance penalty of the additional strlen() is negligible compared to the system calls which inevitably surround has_extension() calls. Additionally, change has_extension() to use size_t inside instead of int, as that is the exact type strlen() returns and memcmp() expects. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- builtin-help.c | 2 +- git-compat-util.h | 5 +++-- http-fetch.c | 2 +- index-pack.c | 2 +- local-fetch.c | 2 +- refs.c | 2 +- sha1_file.c | 2 +- verify-pack.c | 4 ++-- 8 files changed, 11 insertions(+), 10 deletions(-) diff --git a/builtin-help.c b/builtin-help.c index 7a7f7759e5..6484cb9df2 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -140,7 +140,7 @@ static void list_commands(const char *exec_path, const char *pattern) continue; entlen = strlen(de->d_name); - if (has_extension(de->d_name, entlen, ".exe")) + if (has_extension(de->d_name, ".exe")) entlen -= 4; if (longest < entlen) diff --git a/git-compat-util.h b/git-compat-util.h index dd92093652..b2e18954c0 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -139,9 +139,10 @@ static inline ssize_t xwrite(int fd, const void *buf, size_t len) } } -static inline int has_extension(const char *filename, int len, const char *ext) +static inline int has_extension(const char *filename, const char *ext) { - int extlen = strlen(ext); + size_t len = strlen(filename); + size_t extlen = strlen(ext); return len > extlen && !memcmp(filename + len - extlen, ext, extlen); } diff --git a/http-fetch.c b/http-fetch.c index 6ea39f0589..de5fc44e66 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -870,7 +870,7 @@ static void process_ls_pack(struct remote_ls_ctx *ls) if (strlen(ls->dentry_name) == 63 && !strncmp(ls->dentry_name, "objects/pack/pack-", 18) && - has_extension(ls->dentry_name, 63, ".pack")) { + has_extension(ls->dentry_name, ".pack")) { get_sha1_hex(ls->dentry_name + 18, sha1); setup_index(ls->repo, sha1); } diff --git a/index-pack.c b/index-pack.c index a91e39ecd6..b20659c259 100644 --- a/index-pack.c +++ b/index-pack.c @@ -447,7 +447,7 @@ int main(int argc, char **argv) usage(index_pack_usage); if (!index_name) { int len = strlen(pack_name); - if (!has_extension(pack_name, len, ".pack")) + if (!has_extension(pack_name, ".pack")) die("packfile name '%s' does not end with '.pack'", pack_name); index_name_buf = xmalloc(len); diff --git a/local-fetch.c b/local-fetch.c index b6ec170c01..7d01845d39 100644 --- a/local-fetch.c +++ b/local-fetch.c @@ -44,7 +44,7 @@ static int setup_indices(void) while ((de = readdir(dir)) != NULL) { int namelen = strlen(de->d_name); if (namelen != 50 || - !has_extension(de->d_name, namelen, ".pack")) + !has_extension(de->d_name, ".pack")) continue; get_sha1_hex(de->d_name + 5, sha1); setup_index(sha1); diff --git a/refs.c b/refs.c index b01835f634..28a9394605 100644 --- a/refs.c +++ b/refs.c @@ -147,7 +147,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u namelen = strlen(de->d_name); if (namelen > 255) continue; - if (has_extension(de->d_name, namelen, ".lock")) + if (has_extension(de->d_name, ".lock")) continue; memcpy(path + baselen, de->d_name, namelen+1); if (stat(git_path("%s", path), &st) < 0) diff --git a/sha1_file.c b/sha1_file.c index a1bb01ca35..3db956dd5c 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -590,7 +590,7 @@ static void prepare_packed_git_one(char *objdir, int local) int namelen = strlen(de->d_name); struct packed_git *p; - if (!has_extension(de->d_name, namelen, ".idx")) + if (!has_extension(de->d_name, ".idx")) continue; /* we have .idx. Is it a file we can map? */ diff --git a/verify-pack.c b/verify-pack.c index f440a39678..357970da39 100644 --- a/verify-pack.c +++ b/verify-pack.c @@ -16,10 +16,10 @@ static int verify_one_pack(const char *path, int verbose) * In addition to "foo.idx" we accept "foo.pack" and "foo"; * normalize these forms to "foo.idx" for add_packed_git(). */ - if (has_extension(arg, len, ".pack")) { + if (has_extension(arg, ".pack")) { strcpy(arg + len - 5, ".idx"); len--; - } else if (!has_extension(arg, len, ".idx")) { + } else if (!has_extension(arg, ".idx")) { if (len + 4 >= PATH_MAX) return error("name too long: %s.idx", arg); strcpy(arg + len, ".idx"); -- cgit v1.2.3 From 17a10f3709c211769c9fada79bd1608aa64f6080 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 11 Aug 2006 04:34:07 -0700 Subject: git-svn: correctly kill keyword expansion without munging EOLs This bugfix applies to users of the svn command-line client only. We no longer muck with newlines when killing keyword expansion. This tended to generate unintended diffs in commits because svn revert -R would destroy the manual EOL changes we were doing. Of course, we didn't need the EOL munging in the first place, as svn seems to do it for us even in the text-base files. Now we set the mtime and atime the files changed by keyword expansion killing to avoid triggering a change on svn revert, which svn still seems to want to do. Thanks to Seth Falcon for reporting this bug. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano --- git-svn.perl | 45 ++++----------------------------------------- 1 file changed, 4 insertions(+), 41 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 6453771f9c..3327ad3644 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -31,6 +31,7 @@ use File::Basename qw/dirname basename/; use File::Path qw/mkpath/; use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/; use File::Spec qw//; +use File::Copy qw/copy/; use POSIX qw/strftime/; use IPC::Open3; use Memoize; @@ -77,9 +78,6 @@ my %cmt_opts = ( 'edit|e' => \$_edit, 'copy-similarity|C=i'=> \$_cp_similarity ); -# yes, 'native' sets "\n". Patches to fix this for non-*nix systems welcome: -my %EOL = ( CR => "\015", LF => "\012", CRLF => "\015\012", native => "\012" ); - my %cmd = ( fetch => [ \&fetch, "Download new revisions from SVN", { 'revision|r=s' => \$_revision, %fc_opts } ], @@ -1760,43 +1758,6 @@ sub svn_info { sub sys { system(@_) == 0 or croak $? } -sub eol_cp { - my ($from, $to) = @_; - my $es = svn_propget_base('svn:eol-style', $to); - open my $rfd, '<', $from or croak $!; - binmode $rfd or croak $!; - open my $wfd, '>', $to or croak $!; - binmode $wfd or croak $!; - eol_cp_fd($rfd, $wfd, $es); - close $rfd or croak $!; - close $wfd or croak $!; -} - -sub eol_cp_fd { - my ($rfd, $wfd, $es) = @_; - my $eol = defined $es ? $EOL{$es} : undef; - my $buf; - use bytes; - while (1) { - my ($r, $w, $t); - defined($r = sysread($rfd, $buf, 4096)) or croak $!; - return unless $r; - if ($eol) { - if ($buf =~ /\015$/) { - my $c; - defined($r = sysread($rfd,$c,1)) or croak $!; - $buf .= $c if $r > 0; - } - $buf =~ s/(?:\015\012|\015|\012)/$eol/gs; - $r = length($buf); - } - for ($w = 0; $w < $r; $w += $t) { - $t = syswrite($wfd, $buf, $r - $w, $w) or croak $!; - } - } - no bytes; -} - sub do_update_index { my ($z_cmd, $cmd, $no_text_base) = @_; @@ -1824,9 +1785,11 @@ sub do_update_index { 'text-base',"$f.svn-base"); $tb =~ s#^/##; } + my @s = stat($x); unlink $x or croak $!; - eol_cp($tb, $x); + copy($tb, $x); chmod(($mode &~ umask), $x) or croak $!; + utime $s[8], $s[9], $x; } print $ui $x,"\0"; } -- cgit v1.2.3 From 308906fa6e98132cab839a4f42701386fba368ef Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 11 Aug 2006 11:11:29 -0700 Subject: git-svn: bugfix: allow SVN:: lib users to track the root of the repository I'm not sure if anybody has hit this (besides me), but this fixes the problem where I ran into while attempting to import a small repo at the root level: I ended up with all the commits, but with no file/tree changes at all throughout the entire history. Also, fix a warning if the commit message is not defined for revision 0. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano --- git-svn.perl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index 3327ad3644..7d9839e7ac 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2580,7 +2580,9 @@ sub libsvn_connect { sub libsvn_get_file { my ($gui, $f, $rev) = @_; my $p = $f; - return unless ($p =~ s#^\Q$SVN_PATH\E/##); + if (length $SVN_PATH > 0) { + return unless ($p =~ s#^\Q$SVN_PATH\E/##); + } my ($hash, $pid, $in, $out); my $pool = SVN::Pool->new; @@ -2627,6 +2629,7 @@ sub libsvn_log_entry { if (defined $_authors && ! defined $users{$author}) { die "Author: $author not defined in $_authors file\n"; } + $msg = '' if ($rev == 0 && !defined $msg); return { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", author => $author, msg => $msg."\n", parents => $parents || [] } } -- cgit v1.2.3 From c8769f76d992ed391d169cd71be009ca17727271 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 11 Aug 2006 18:47:50 -0700 Subject: git-sh-setup: do not use repo-config to test the git directory Since repo-config does not fail in non-git directory, it is not a good command to use to test the git-ness nor validate the repository revision of $GIT_DIR. Original patch by Robert Shearman but with minor fixes. Signed-off-by: Junio C Hamano --- git-sh-setup.sh | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/git-sh-setup.sh b/git-sh-setup.sh index d15747f1ed..42f9b1c125 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -35,17 +35,12 @@ case "$1" in exit esac +# Make sure we are in a valid repository of a vintage we understand. if [ -z "$SUBDIRECTORY_OK" ] then : ${GIT_DIR=.git} - : ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"} - - # Make sure we are in a valid repository of a vintage we understand. - GIT_DIR="$GIT_DIR" git repo-config --get core.nosuch >/dev/null - if test $? = 128 - then - exit - fi + GIT_DIR=$(GIT_DIR="$GIT_DIR" git-rev-parse --git-dir) || exit else GIT_DIR=$(git-rev-parse --git-dir) || exit fi +: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"} -- cgit v1.2.3 From 0d042fecf2f4130717d198f9cc2d792e72370bda Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 11 Aug 2006 00:44:42 -0700 Subject: git-grep: show pathnames relative to the current directory By default, the command shows pathnames relative to the current directory. Use --full-name (the same flag to do so in ls-files) if you want to see the full pathname relative to the project root. This makes it very pleasant to run in Emacs compilation (or "grep-find") buffer. Signed-off-by: Junio C Hamano --- Documentation/git-grep.txt | 8 ++++++- builtin-grep.c | 59 ++++++++++++++++++++++++++++++++++++++-------- t/t7002-grep.sh | 31 ++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index dc7683383c..7545dd9a3e 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git-grep' [--cached] [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp] - [-v | --invert-match] + [-v | --invert-match] [--full-name] [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings] [-n] [-l | --files-with-matches] [-L | --files-without-match] [-c | --count] @@ -47,6 +47,12 @@ OPTIONS -v | --invert-match:: Select non-matching lines. +--full-name:: + When run from a subdirectory, the command usually + outputs paths relative to the current directory. This + option forces paths to be output relative to the project + top directory. + -E | --extended-regexp | -G | --basic-regexp:: Use POSIX extended/basic regexp for patterns. Default is to use basic regexp. diff --git a/builtin-grep.c b/builtin-grep.c index 93b7e07b30..a561612e7e 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -123,6 +123,7 @@ struct grep_opt { struct grep_pat *pattern_list; struct grep_pat **pattern_tail; struct grep_expr *pattern_expression; + int prefix_length; regex_t regexp; unsigned linenum:1; unsigned invert:1; @@ -136,6 +137,7 @@ struct grep_opt { #define GREP_BINARY_TEXT 2 unsigned binary:2; unsigned extended:1; + unsigned relative:1; int regflags; unsigned pre_context; unsigned post_context; @@ -632,19 +634,40 @@ static int grep_buffer(struct grep_opt *opt, const char *name, return !!last_hit; } -static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name) +static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name, int tree_name_len) { unsigned long size; char *data; char type[20]; + char *to_free = NULL; int hit; + data = read_sha1_file(sha1, type, &size); if (!data) { error("'%s': unable to read %s", name, sha1_to_hex(sha1)); return 0; } + if (opt->relative && opt->prefix_length) { + static char name_buf[PATH_MAX]; + char *cp; + int name_len = strlen(name) - opt->prefix_length + 1; + + if (!tree_name_len) + name += opt->prefix_length; + else { + if (ARRAY_SIZE(name_buf) <= name_len) + cp = to_free = xmalloc(name_len); + else + cp = name_buf; + memcpy(cp, name, tree_name_len); + strcpy(cp + tree_name_len, + name + tree_name_len + opt->prefix_length); + name = cp; + } + } hit = grep_buffer(opt, name, data, size); free(data); + free(to_free); return hit; } @@ -674,6 +697,8 @@ static int grep_file(struct grep_opt *opt, const char *filename) return 0; } close(i); + if (opt->relative && opt->prefix_length) + filename += opt->prefix_length; i = grep_buffer(opt, filename, data, st.st_size); free(data); return i; @@ -720,7 +745,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) char *argptr = randarg; struct grep_pat *p; - if (opt->extended) + if (opt->extended || (opt->relative && opt->prefix_length)) return -1; len = nr = 0; push_arg("grep"); @@ -845,7 +870,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) if (!pathspec_matches(paths, ce->name)) continue; if (cached) - hit |= grep_sha1(opt, ce->sha1, ce->name); + hit |= grep_sha1(opt, ce->sha1, ce->name, 0); else hit |= grep_file(opt, ce->name); } @@ -860,11 +885,12 @@ static int grep_tree(struct grep_opt *opt, const char **paths, int hit = 0; struct name_entry entry; char *down; - char *path_buf = xmalloc(PATH_MAX + strlen(tree_name) + 100); + int tn_len = strlen(tree_name); + char *path_buf = xmalloc(PATH_MAX + tn_len + 100); - if (tree_name[0]) { - int offset = sprintf(path_buf, "%s:", tree_name); - down = path_buf + offset; + if (tn_len) { + tn_len = sprintf(path_buf, "%s:", tree_name); + down = path_buf + tn_len; strcat(down, base); } else { @@ -886,7 +912,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths, if (!pathspec_matches(paths, down)) ; else if (S_ISREG(entry.mode)) - hit |= grep_sha1(opt, entry.sha1, path_buf); + hit |= grep_sha1(opt, entry.sha1, path_buf, tn_len); else if (S_ISDIR(entry.mode)) { char type[20]; struct tree_desc sub; @@ -907,7 +933,7 @@ static int grep_object(struct grep_opt *opt, const char **paths, struct object *obj, const char *name) { if (obj->type == OBJ_BLOB) - return grep_sha1(opt, obj->sha1, name); + return grep_sha1(opt, obj->sha1, name, 0); if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { struct tree_desc tree; void *data; @@ -945,6 +971,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) int i; memset(&opt, 0, sizeof(opt)); + opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; + opt.relative = 1; opt.pattern_tail = &opt.pattern_list; opt.regflags = REG_NEWLINE; @@ -1118,6 +1146,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } die(emsg_missing_argument, arg); } + if (!strcmp("--full-name", arg)) { + opt.relative = 0; + continue; + } if (!strcmp("--", arg)) { /* later processing wants to have this at argv[1] */ argv--; @@ -1176,8 +1208,15 @@ int cmd_grep(int argc, const char **argv, const char *prefix) verify_filename(prefix, argv[j]); } - if (i < argc) + if (i < argc) { paths = get_pathspec(prefix, argv + i); + if (opt.prefix_length && opt.relative) { + /* Make sure we do not get outside of paths */ + for (i = 0; paths[i]; i++) + if (strncmp(prefix, paths[i], opt.prefix_length)) + die("git-grep: cannot generate relative filenames containing '..'"); + } + } else if (prefix) { paths = xcalloc(2, sizeof(const char *)); paths[0] = prefix; diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 00a7d762ce..6bfb899ed1 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -3,7 +3,7 @@ # Copyright (c) 2006 Junio C Hamano # -test_description='git grep -w +test_description='git grep various. ' . ./test-lib.sh @@ -19,7 +19,9 @@ test_expect_success setup ' echo x x xx x >x && echo y yy >y && echo zzz > z && - git add file x y z && + mkdir t && + echo test >t/t && + git add file x y z t/t && git commit -m initial ' @@ -80,6 +82,31 @@ do diff expected actual fi ' + + test_expect_success "grep $L (t-1)" ' + echo "${HC}t/t:1:test" >expected && + git grep -n -e test $H >actual && + diff expected actual + ' + + test_expect_success "grep $L (t-2)" ' + echo "${HC}t:1:test" >expected && + ( + cd t && + git grep -n -e test $H + ) >actual && + diff expected actual + ' + + test_expect_success "grep $L (t-3)" ' + echo "${HC}t/t:1:test" >expected && + ( + cd t && + git grep --full-name -n -e test $H + ) >actual && + diff expected actual + ' + done test_done -- cgit v1.2.3 From a69a165fb43c88ab5c2adc2fe33b065ff537177c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 11 Aug 2006 23:21:41 -0700 Subject: git-svn: split the path from the url correctly with limited perms This version of the splitter (that only affects SVN:: library users) works when one only has limited read-permissions to the repository they're fetching from. Updated from the original patch to workaround some SVN bug somewhere, which only seems to happen against file:// repositories... Here's the diff against the original patch I submitted: @@ -1159,8 +1159,8 @@ sub repo_path_split { } if ($_use_lib) { - $SVN = libsvn_connect($full_url); - my $url = $SVN->get_repos_root; + my $tmp = libsvn_connect($full_url); + my $url = $tmp->get_repos_root; $full_url =~ s#^\Q$url\E/*##; push @repo_path_split_cache, qr/^(\Q$url\E)/; return ($url, $full_url); Somehow connecting to a repository with the full url makes the returned SVN::Ra object act strangely and break things, so now we just drop the SVN::Ra object that we made our initial connection with. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano --- git-svn.perl | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 7d9839e7ac..0d58bb9b37 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1158,27 +1158,24 @@ sub repo_path_split { } } - my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i); - $path =~ s#^/+##; - my @paths = split(m#/+#, $path); - if ($_use_lib) { - while (1) { - $SVN = libsvn_connect($url); - last if (defined $SVN && - defined eval { $SVN->get_latest_revnum }); - my $n = shift @paths || last; - $url .= "/$n"; - } + my $tmp = libsvn_connect($full_url); + my $url = $tmp->get_repos_root; + $full_url =~ s#^\Q$url\E/*##; + push @repo_path_split_cache, qr/^(\Q$url\E)/; + return ($url, $full_url); } else { + my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i); + $path =~ s#^/+##; + my @paths = split(m#/+#, $path); while (quiet_run(qw/svn ls --non-interactive/, $url)) { my $n = shift @paths || last; $url .= "/$n"; } + push @repo_path_split_cache, qr/^(\Q$url\E)/; + $path = join('/',@paths); + return ($url, $path); } - push @repo_path_split_cache, qr/^(\Q$url\E)/; - $path = join('/',@paths); - return ($url, $path); } sub setup_git_svn { -- cgit v1.2.3 From fd7bcfb524a3313ef5361bdd8493ba50635f50f0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 12 Aug 2006 16:16:47 -0700 Subject: git-am: give better diagnostics when the patch does not apply during --3way If the user tries to apply a patch that was hand-edited in such a way that it does not apply to the original file recorded on its "index" line anymore, we did detect the situation but did not issue an error message that is specific enough. Signed-off-by: Junio C Hamano --- git-am.sh | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/git-am.sh b/git-am.sh index 04f0119435..d0af786aec 100755 --- a/git-am.sh +++ b/git-am.sh @@ -45,6 +45,12 @@ go_next () { this=$next } +cannot_fallback () { + echo "$1" + echo "Cannot fall back to three-way merge." + exit 1 +} + fall_back_3way () { O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd` @@ -52,19 +58,23 @@ fall_back_3way () { mkdir "$dotest/patch-merge-tmp-dir" # First see if the patch records the index info that we can use. - if git-apply -z --index-info "$dotest/patch" \ - >"$dotest/patch-merge-index-info" 2>/dev/null && - GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ - git-update-index -z --index-info <"$dotest/patch-merge-index-info" && - GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ - git-write-tree >"$dotest/patch-merge-base+" && - # index has the base tree now. - GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ + git-apply -z --index-info "$dotest/patch" \ + >"$dotest/patch-merge-index-info" && + GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ + git-update-index -z --index-info <"$dotest/patch-merge-index-info" && + GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ + git-write-tree >"$dotest/patch-merge-base+" || + cannot_fallback "Patch does not record usable index information." + + echo Using index info to reconstruct a base tree... + if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ git-apply $binary --cached <"$dotest/patch" then - echo Using index info to reconstruct a base tree... mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base" mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index" + else + cannot_fallback "Did you hand edit your patch? +It does not apply to blobs recorded in its index." fi test -f "$dotest/patch-merge-index" && -- cgit v1.2.3 From 40aaae88adfe05be435b7bd17093d49869f3276f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 12 Aug 2006 01:03:47 -0700 Subject: Better error message when we are unable to lock the index file Most of the callers except the one in refs.c use the function to update the index file. Among the index writers, everybody except write-tree dies if they cannot open it for writing. This gives the function an extra argument, to tell it to die when it cannot create a new file as the lockfile. The only caller that does not have to die is write-tree, because updating the index for the cache-tree part is optional and not being able to do so does not affect the correctness. I think we do not have to be so careful and make the failure into die() the same way as other callers, but that would be a different patch. Signed-off-by: Junio C Hamano --- builtin-add.c | 4 +--- builtin-apply.c | 7 ++----- builtin-mv.c | 5 +---- builtin-read-tree.c | 4 +--- builtin-rm.c | 4 +--- builtin-update-index.c | 4 +--- builtin-write-tree.c | 2 +- cache.h | 2 +- checkout-index.c | 2 +- lockfile.c | 10 +++++++++- refs.c | 8 +------- 11 files changed, 20 insertions(+), 32 deletions(-) diff --git a/builtin-add.c b/builtin-add.c index 096b611b5b..0cb9c81200 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -93,9 +93,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) git_config(git_default_config); - newfd = hold_lock_file_for_update(&lock_file, get_index_file()); - if (newfd < 0) - die("unable to create new index file"); + newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); if (read_cache() < 0) die("index file corrupt"); diff --git a/builtin-apply.c b/builtin-apply.c index be2c7152cd..9cf477c701 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2234,12 +2234,9 @@ static int apply_patch(int fd, const char *filename, apply = 0; write_index = check_index && apply; - if (write_index && newfd < 0) { + if (write_index && newfd < 0) newfd = hold_lock_file_for_update(&lock_file, - get_index_file()); - if (newfd < 0) - die("unable to create new index file"); - } + get_index_file(), 1); if (check_index) { if (read_cache() < 0) die("unable to read index file"); diff --git a/builtin-mv.c b/builtin-mv.c index ce8187c1e9..a731f8d9cf 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -72,10 +72,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) git_config(git_default_config); - newfd = hold_lock_file_for_update(&lock_file, get_index_file()); - if (newfd < 0) - die("unable to create new index file"); - + newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); if (read_cache() < 0) die("index file corrupt"); diff --git a/builtin-read-tree.c b/builtin-read-tree.c index b30160a5b3..71a7026df4 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -884,9 +884,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) git_config(git_default_config); - newfd = hold_lock_file_for_update(&lock_file, get_index_file()); - if (newfd < 0) - die("unable to create new index file"); + newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); git_config(git_default_config); diff --git a/builtin-rm.c b/builtin-rm.c index 8af3d7eb48..593d86744c 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -52,9 +52,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) git_config(git_default_config); - newfd = hold_lock_file_for_update(&lock_file, get_index_file()); - if (newfd < 0) - die("unable to create new index file"); + newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); if (read_cache() < 0) die("index file corrupt"); diff --git a/builtin-update-index.c b/builtin-update-index.c index 24dca47d8d..d2556f376b 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -491,9 +491,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) /* We can't free this memory, it becomes part of a linked list parsed atexit() */ lock_file = xcalloc(1, sizeof(struct lock_file)); - newfd = hold_lock_file_for_update(lock_file, get_index_file()); - if (newfd < 0) - die("unable to create new cachefile"); + newfd = hold_lock_file_for_update(lock_file, get_index_file(), 1); entries = read_cache(); if (entries < 0) diff --git a/builtin-write-tree.c b/builtin-write-tree.c index 6b62d7dc8c..ca06149f18 100644 --- a/builtin-write-tree.c +++ b/builtin-write-tree.c @@ -18,7 +18,7 @@ int write_tree(unsigned char *sha1, int missing_ok, const char *prefix) /* We can't free this memory, it becomes part of a linked list parsed atexit() */ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - newfd = hold_lock_file_for_update(lock_file, get_index_file()); + newfd = hold_lock_file_for_update(lock_file, get_index_file(), 0); entries = read_cache(); if (entries < 0) diff --git a/cache.h b/cache.h index b8c21e07b2..b2ab2088e3 100644 --- a/cache.h +++ b/cache.h @@ -175,7 +175,7 @@ struct lock_file { struct lock_file *next; char filename[PATH_MAX]; }; -extern int hold_lock_file_for_update(struct lock_file *, const char *path); +extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); extern int commit_lock_file(struct lock_file *); extern void rollback_lock_file(struct lock_file *); diff --git a/checkout-index.c b/checkout-index.c index 61152f34b7..dfb1c44415 100644 --- a/checkout-index.c +++ b/checkout-index.c @@ -206,7 +206,7 @@ int main(int argc, char **argv) state.refresh_cache = 1; if (newfd < 0) newfd = hold_lock_file_for_update - (&lock_file, get_index_file()); + (&lock_file, get_index_file(), 1); if (newfd < 0) die("cannot open index.lock file."); continue; diff --git a/lockfile.c b/lockfile.c index 2346e0e9ef..2a2fea3cb6 100644 --- a/lockfile.c +++ b/lockfile.c @@ -22,7 +22,7 @@ static void remove_lock_file_on_signal(int signo) raise(signo); } -int hold_lock_file_for_update(struct lock_file *lk, const char *path) +static int lock_file(struct lock_file *lk, const char *path) { int fd; sprintf(lk->filename, "%s.lock", path); @@ -41,6 +41,14 @@ int hold_lock_file_for_update(struct lock_file *lk, const char *path) return fd; } +int hold_lock_file_for_update(struct lock_file *lk, const char *path, int die_on_error) +{ + int fd = lock_file(lk, path); + if (fd < 0 && die_on_error) + die("unable to create '%s': %s", path, strerror(errno)); + return fd; +} + int commit_lock_file(struct lock_file *lk) { char result_file[PATH_MAX]; diff --git a/refs.c b/refs.c index 28a9394605..86ef916614 100644 --- a/refs.c +++ b/refs.c @@ -319,13 +319,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *path, if (safe_create_leading_directories(lock->ref_file)) die("unable to create directory for %s", lock->ref_file); - lock->lock_fd = hold_lock_file_for_update(lock->lk, lock->ref_file); - if (lock->lock_fd < 0) { - error("Couldn't open lock file %s: %s", - lock->lk->filename, strerror(errno)); - unlock_ref(lock); - return NULL; - } + lock->lock_fd = hold_lock_file_for_update(lock->lk, lock->ref_file, 1); return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock; } -- cgit v1.2.3 From 6c7f4cebdb40c0d95c63d59538fd235dcf978029 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 12 Aug 2006 18:04:07 -0700 Subject: t/t4013: fix futzing with the version string. Signed-off-by: Junio C Hamano --- t/t4013-diff-various.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index b24c829f0f..71c454356f 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -88,7 +88,7 @@ test_expect_success setup ' +*+ [initial] Initial EOF -V=`git version | sed -e 's/^git version //'` +V=`git version | sed -e 's/^git version //' -e 's/\./\\./g'` while read cmd do case "$cmd" in @@ -103,7 +103,9 @@ do test_expect_success "git $cmd" ' { echo "\$ git $cmd" - git $cmd | sed -e "s/$V/g-i-t--v-e-r-s-i-o-n/" + git $cmd | + sed -e "s/^\\(-*\\)$V\\(-*\\)\$/\\1g-i-t--v-e-r-s-i-o-n\2/" \ + -e "s/^\\( *boundary=\"-*\\)$V\\(-*\\)\"\$/\\1g-i-t--v-e-r-s-i-o-n\2\"/" echo "\$" } >"$actual" && if test -f "$expect" -- cgit v1.2.3 From e77235ea38385a127d2afc969435a56d3ff2b16d Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Sun, 13 Aug 2006 00:34:37 -0700 Subject: Fix regex pattern in commit-msg Between the count and the line output, some uniq(1) versions put a TAB character, not a space. Make sure both are handled. Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano --- templates/hooks--commit-msg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/hooks--commit-msg b/templates/hooks--commit-msg index 643822d235..23617f3906 100644 --- a/templates/hooks--commit-msg +++ b/templates/hooks--commit-msg @@ -11,4 +11,4 @@ # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | - sort | uniq -c | sed -e '/^[ ]*1 /d')" + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" -- cgit v1.2.3 From 9a1ae9ab03abaffc977421f182997f3e735e7098 Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Sun, 13 Aug 2006 01:41:22 -0700 Subject: sample commit-msg hook: no silent exit on duplicate Signed-off-by lines git-commit would silently exit if duplicate Signed-off-by lines were found. Users of git-commit would not know it, unless they checked '$?'. This patch makes git-commit actually print out a message that nothing was commited since duplicate Signed-off-lines were found. Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano --- templates/hooks--commit-msg | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/templates/hooks--commit-msg b/templates/hooks--commit-msg index 23617f3906..0b906caa98 100644 --- a/templates/hooks--commit-msg +++ b/templates/hooks--commit-msg @@ -11,4 +11,8 @@ # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | - sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} + -- cgit v1.2.3 From 89b0c4b5a3a9d026d9adb30d4e896965e480725e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 13 Aug 2006 19:19:34 -0700 Subject: Fix type of combine-diff.c::show_patch_diff() The other function, show_raw_diff() is void and no callers use return value from neither. Signed-off-by: Junio C Hamano --- combine-diff.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/combine-diff.c b/combine-diff.c index ba8baca0ab..f2f3806477 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -609,15 +609,15 @@ static void dump_quoted_path(const char *prefix, const char *path, printf("%s\n", c_reset); } -static int show_patch_diff(struct combine_diff_path *elem, int num_parent, - int dense, struct rev_info *rev) +static void show_patch_diff(struct combine_diff_path *elem, int num_parent, + int dense, struct rev_info *rev) { struct diff_options *opt = &rev->diffopt; unsigned long result_size, cnt, lno; char *result, *cp; struct sline *sline; /* survived lines */ int mode_differs = 0; - int i, show_hunks, shown_header = 0; + int i, show_hunks; int working_tree_file = !memcmp(elem->sha1, null_sha1, 20); int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV; mmfile_t result_file; @@ -769,7 +769,6 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, } free(sline[0].p_lno); free(sline); - return shown_header; } #define COLONS "::::::::::::::::::::::::::::::::" @@ -837,11 +836,10 @@ void show_combined_diff(struct combine_diff_path *p, return; if (opt->output_format & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME | - DIFF_FORMAT_NAME_STATUS)) { + DIFF_FORMAT_NAME_STATUS)) show_raw_diff(p, num_parent, rev); - } else if (opt->output_format & DIFF_FORMAT_PATCH) { + else if (opt->output_format & DIFF_FORMAT_PATCH) show_patch_diff(p, num_parent, dense, rev); - } } void diff_tree_combined(const unsigned char *sha1, -- cgit v1.2.3 From 847e01fb00872401c093957c2d6dd908aa8af30d Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 14 Aug 2006 02:05:47 +0200 Subject: gitweb: Great subroutines renaming Rename some of subroutines to better reflect what they do. Some renames were not performed because subroutine name reflects hash key. Subroutines name guideline: * git_ prefix for subroutines related to git commands, git repository, or to gitweb actions * git_get_ prefix for inner subroutines calling git command or reading some file in the repository and returning some output * parse_ prefix for subroutines parsing some text (or reading and parsing some text) into hash or list * format_ prefix for subroutines formatting, post-processing or generating some HTML/text fragment * _get_ infix for subroutines which return result * _print_ infix for subroutines which print fragment of output * _body suffix for subroutines which outputs main part (body) of related action (usually table) * _nav suffix for subroutines related to navigation bars * _div suffix for subroutines returning or printing div element * subroutine names should not be based on how the result is obtained, as this might change easily Renames performed: - git_get_referencing => format_ref_marker - git_get_paging_nav => format_paging_nav - git_read_head => git_get_head_hash - git_read_hash => git_get_hash_by_ref - git_read_description => git_get_project_description - git_read_projects => git_get_projects_list - read_info_ref => git_get_references - git_read_refs => git_get_refs_list - date_str => parse_date - git_read_tag => parse_tag - git_read_commit => parse_commit - git_blob_plain_mimetype => blob_mimetype - git_page_nav => git_print_page_nav - git_header_div => git_print_header_div Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 250 ++++++++++++++++++++++++++--------------------------- 1 file changed, 125 insertions(+), 125 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 626fcc9201..28df59e6be 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -362,7 +362,7 @@ sub format_log_line_html { } # format marker of refs pointing to given object -sub git_get_referencing { +sub format_ref_marker { my ($refs, $id) = @_; if (defined $refs->{$id}) { @@ -376,7 +376,7 @@ sub git_get_referencing { ## git utility subroutines, invoking git commands # get HEAD ref of given project as hash -sub git_read_head { +sub git_get_head_hash { my $project = shift; my $oENV = $ENV{'GIT_DIR'}; my $retval = undef; @@ -445,7 +445,7 @@ sub git_get_hash_by_path { ## git utility functions, directly accessing git repository # assumes that PATH is not symref -sub git_read_hash { +sub git_get_hash_by_ref { my $path = shift; open my $fd, "$projectroot/$path" or return undef; @@ -457,7 +457,7 @@ sub git_read_hash { } } -sub git_read_description { +sub git_get_project_description { my $path = shift; open my $fd, "$projectroot/$path/description" or return undef; @@ -467,7 +467,7 @@ sub git_read_description { return $descr; } -sub git_read_projects { +sub git_get_projects_list { my @list; if (-d $projects_list) { @@ -511,7 +511,7 @@ sub git_read_projects { return @list; } -sub read_info_ref { +sub git_get_references { my $type = shift || ""; my %refs; # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 @@ -536,7 +536,7 @@ sub read_info_ref { ## ---------------------------------------------------------------------- ## parse to hash functions -sub date_str { +sub parse_date { my $epoch = shift; my $tz = shift || "-0000"; @@ -561,7 +561,7 @@ sub date_str { return %date; } -sub git_read_tag { +sub parse_tag { my $tag_id = shift; my %tag; my @comment; @@ -596,7 +596,7 @@ sub git_read_tag { return %tag } -sub git_read_commit { +sub parse_commit { my $commit_id = shift; my $commit_text = shift; @@ -693,7 +693,7 @@ sub git_read_commit { ## ...................................................................... ## parse to array of hashes functions -sub git_read_refs { +sub git_get_refs_list { my $ref_dir = shift; my @reflist; @@ -707,7 +707,7 @@ sub git_read_refs { }, "$projectroot/$project/$ref_dir"); foreach my $ref_file (@refs) { - my $ref_id = git_read_hash("$project/$ref_dir/$ref_file"); + my $ref_id = git_get_hash_by_ref("$project/$ref_dir/$ref_file"); my $type = git_get_type($ref_id) || next; my %ref_item; my %co; @@ -716,10 +716,10 @@ sub git_read_refs { $ref_item{'epoch'} = 0; $ref_item{'age'} = "unknown"; if ($type eq "tag") { - my %tag = git_read_tag($ref_id); + my %tag = parse_tag($ref_id); $ref_item{'comment'} = $tag{'comment'}; if ($tag{'type'} eq "commit") { - %co = git_read_commit($tag{'object'}); + %co = parse_commit($tag{'object'}); $ref_item{'epoch'} = $co{'committer_epoch'}; $ref_item{'age'} = $co{'age_string'}; } elsif (defined($tag{'epoch'})) { @@ -731,7 +731,7 @@ sub git_read_refs { $ref_item{'name'} = $tag{'name'}; $ref_item{'refid'} = $tag{'object'}; } elsif ($type eq "commit"){ - %co = git_read_commit($ref_id); + %co = parse_commit($ref_id); $ref_item{'reftype'} = "commit"; $ref_item{'name'} = $ref_file; $ref_item{'title'} = $co{'title'}; @@ -806,7 +806,7 @@ sub mimetype_guess { return $mime; } -sub git_blob_plain_mimetype { +sub blob_mimetype { my $fd = shift; my $filename = shift; @@ -925,7 +925,7 @@ EOF sub git_footer_html { print "
\n"; if (defined $project) { - my $descr = git_read_description($project); + my $descr = git_get_project_description($project); if (defined $descr) { print "\n"; } @@ -955,7 +955,7 @@ sub die_error { ## ---------------------------------------------------------------------- ## functions printing or outputting HTML: navigation -sub git_page_nav { +sub git_print_page_nav { my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_; $extra = '' if !defined $extra; # pager or formats @@ -989,7 +989,7 @@ sub git_page_nav { "
\n"; } -sub git_get_paging_nav { +sub format_paging_nav { my ($action, $hash, $head, $page, $nrevs) = @_; my $paging_nav; @@ -1022,7 +1022,7 @@ sub git_get_paging_nav { ## ...................................................................... ## functions printing or outputting HTML: div -sub git_header_div { +sub git_print_header_div { my ($action, $title, $hash, $hash_base) = @_; my $rest = ''; @@ -1062,9 +1062,9 @@ sub git_shortlog_body { my $alternate = 0; for (my $i = $from; $i <= $to; $i++) { my $commit = $revlist->[$i]; - #my $ref = defined $refs ? git_get_referencing($refs, $commit) : ''; - my $ref = git_get_referencing($refs, $commit); - my %co = git_read_commit($commit); + #my $ref = defined $refs ? format_ref_marker($refs, $commit) : ''; + my $ref = format_ref_marker($refs, $commit); + my %co = parse_commit($commit); if ($alternate) { print "\n"; } else { @@ -1282,24 +1282,24 @@ sub git_project_list { die_error(undef, "Unknown order parameter"); } - my @list = git_read_projects(); + my @list = git_get_projects_list(); my @projects; if (!@list) { die_error(undef, "No projects found"); } foreach my $pr (@list) { - my $head = git_read_head($pr->{'path'}); + my $head = git_get_head_hash($pr->{'path'}); if (!defined $head) { next; } $ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}"; - my %co = git_read_commit($head); + my %co = parse_commit($head); if (!%co) { next; } $pr->{'commit'} = \%co; if (!defined $pr->{'descr'}) { - my $descr = git_read_description($pr->{'path'}) || ""; + my $descr = git_get_project_description($pr->{'path'}) || ""; $pr->{'descr'} = chop_str($descr, 25, 5); } if (!defined $pr->{'owner'}) { @@ -1383,10 +1383,10 @@ sub git_project_list { } sub git_summary { - my $descr = git_read_description($project) || "none"; - my $head = git_read_head($project); - my %co = git_read_commit($head); - my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); + my $descr = git_get_project_description($project) || "none"; + my $head = git_get_head_hash($project); + my %co = parse_commit($head); + my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); my $owner; if (-f $projects_list) { @@ -1407,9 +1407,9 @@ sub git_summary { $owner = get_file_owner("$projectroot/$project"); } - my $refs = read_info_ref(); + my $refs = git_get_references(); git_header_html(); - git_page_nav('summary','', $head); + git_print_page_nav('summary','', $head); print "
 
\n"; print "\n" . @@ -1418,24 +1418,24 @@ sub git_summary { "\n" . "
last change$cd{'rfc2822'}
\n"; - open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) + open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_get_head_hash($project) or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; - git_header_div('shortlog'); + git_print_header_div('shortlog'); git_shortlog_body(\@revlist, 0, 15, $refs, $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "...")); - my $taglist = git_read_refs("refs/tags"); + my $taglist = git_get_refs_list("refs/tags"); if (defined @$taglist) { - git_header_div('tags'); + git_print_header_div('tags'); git_tags_body($taglist, 0, 15, $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "...")); } - my $headlist = git_read_refs("refs/heads"); + my $headlist = git_get_refs_list("refs/heads"); if (defined @$headlist) { - git_header_div('heads'); + git_print_header_div('heads'); git_heads_body($headlist, $head, 0, 15, $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "...")); } @@ -1444,11 +1444,11 @@ sub git_summary { } sub git_tag { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); git_header_html(); - git_page_nav('','', $head,undef,$head); - my %tag = git_read_tag($hash); - git_header_div('commit', esc_html($tag{'name'}), $hash); + git_print_page_nav('','', $head,undef,$head); + my %tag = parse_tag($hash); + git_print_header_div('commit', esc_html($tag{'name'}), $hash); print "
\n" . "\n" . "\n" . @@ -1457,7 +1457,7 @@ sub git_tag { "\n" . "\n"; if (defined($tag{'author'})) { - my %ad = date_str($tag{'epoch'}, $tag{'tz'}); + my %ad = parse_date($tag{'epoch'}, $tag{'tz'}); print "\n"; print "\n"; } @@ -1477,9 +1477,9 @@ sub git_blame2 { my $ftype; die_error(undef, "Permission denied") if (!git_get_project_config_bool ('blame')); die_error('404 Not Found', "File name not defined") if (!$file_name); - $hash_base ||= git_read_head($project); + $hash_base ||= git_get_head_hash($project); die_error(undef, "Couldn't find base commit") unless ($hash_base); - my %co = git_read_commit($hash_base) + my %co = parse_commit($hash_base) or die_error(undef, "Reading commit failed"); if (!defined $hash) { $hash = git_get_hash_by_path($hash_base, $file_name, "blob") @@ -1495,8 +1495,8 @@ sub git_blame2 { my $formats_nav = $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); - git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_header_div('commit', esc_html($co{'title'}), $hash_base); + 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); my @rev_color = (qw(light2 dark2)); my $num_colors = scalar(@rev_color); @@ -1535,9 +1535,9 @@ sub git_blame { my $fd; die_error('403 Permission denied', "Permission denied") if (!git_get_project_config_bool ('blame')); die_error('404 Not Found', "File name not defined") if (!$file_name); - $hash_base ||= git_read_head($project); + $hash_base ||= git_get_head_hash($project); die_error(undef, "Couldn't find base commit") unless ($hash_base); - my %co = git_read_commit($hash_base) + my %co = parse_commit($hash_base) or die_error(undef, "Reading commit failed"); if (!defined $hash) { $hash = git_get_hash_by_path($hash_base, $file_name, "blob") @@ -1549,8 +1549,8 @@ sub git_blame { my $formats_nav = $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); - git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_header_div('commit', esc_html($co{'title'}), $hash_base); + 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, 'blob'); print "
\n"; print <a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | "; @@ -1707,8 +1707,8 @@ sub git_blob { } else { $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain"); } - git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print "
\n" . "

\n" . @@ -1730,7 +1730,7 @@ sub git_blob { sub git_tree { if (!defined $hash) { - $hash = git_read_head($project); + $hash = git_get_head_hash($project); if (defined $file_name) { my $base = $hash_base || $hash; $hash = git_get_hash_by_path($base, $file_name, "tree"); @@ -1746,16 +1746,16 @@ sub git_tree { close $fd or die_error(undef, "Reading tree failed"); $/ = "\n"; - my $refs = read_info_ref(); - my $ref = git_get_referencing($refs, $hash_base); + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $hash_base); git_header_html(); my $base_key = ""; my $base = ""; my $have_blame = git_get_project_config_bool ('blame'); - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { + if (defined $hash_base && (my %co = parse_commit($hash_base))) { $base_key = ";hb=$hash_base"; - git_page_nav('tree','', $hash_base); - git_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); + git_print_page_nav('tree','', $hash_base); + git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { print "
\n"; print "

\n"; @@ -1811,14 +1811,14 @@ sub git_tree { } sub git_log { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); if (!defined $hash) { $hash = $head; } if (!defined $page) { $page = 0; } - my $refs = read_info_ref(); + my $refs = git_get_references(); my $limit = sprintf("--max-count=%i", (100 * ($page+1))); open my $fd, "-|", $GIT, "rev-list", $limit, $hash @@ -1826,24 +1826,24 @@ sub git_log { my @revlist = map { chomp; $_ } <$fd>; close $fd; - my $paging_nav = git_get_paging_nav('log', $hash, $head, $page, $#revlist); + my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#revlist); git_header_html(); - git_page_nav('log','', $hash,undef,undef, $paging_nav); + git_print_page_nav('log','', $hash,undef,undef, $paging_nav); if (!@revlist) { - my %co = git_read_commit($hash); + my %co = parse_commit($hash); - git_header_div('summary', $project); + git_print_header_div('summary', $project); print "
Last change $co{'age_string'}.

\n"; } for (my $i = ($page * 100); $i <= $#revlist; $i++) { my $commit = $revlist[$i]; - my $ref = git_get_referencing($refs, $commit); - my %co = git_read_commit($commit); + my $ref = format_ref_marker($refs, $commit); + my %co = parse_commit($commit); next if !%co; - my %ad = date_str($co{'author_epoch'}); - git_header_div('commit', + my %ad = parse_date($co{'author_epoch'}); + git_print_header_div('commit', "$co{'age_string'}" . esc_html($co{'title'}) . $ref, $commit); @@ -1881,12 +1881,12 @@ sub git_log { } sub git_commit { - my %co = git_read_commit($hash); + my %co = parse_commit($hash); if (!%co) { die_error(undef, "Unknown commit object"); } - my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); - my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); + my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); + my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); my $parent = $co{'parent'}; if (!defined $parent) { @@ -1902,22 +1902,22 @@ sub git_commit { if ($hash =~ m/^[0-9a-fA-F]{40}$/) { $expires = "+1d"; } - my $refs = read_info_ref(); - my $ref = git_get_referencing($refs, $co{'id'}); + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $co{'id'}); my $formats_nav = ''; if (defined $file_name && defined $co{'parent'}) { my $parent = $co{'parent'}; $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame"); } git_header_html(undef, $expires); - git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', + git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', $hash, $co{'tree'}, $hash, $formats_nav); if (defined $co{'parent'}) { - git_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); + git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); } else { - git_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash); + git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash); } print "
\n" . "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'type'}) . "
author" . esc_html($tag{'author'}) . "
" . $ad{'rfc2822'} . sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) . "
\n"; @@ -2079,11 +2079,11 @@ sub git_commit { sub git_blobdiff { mkdir($git_temp, 0700); git_header_html(); - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { + if (defined $hash_base && (my %co = parse_commit($hash_base))) { my $formats_nav = $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); - git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print "
\n" . "

\n" . @@ -2109,7 +2109,7 @@ sub git_blobdiff_plain { sub git_commitdiff { mkdir($git_temp, 0700); - my %co = git_read_commit($hash); + my %co = parse_commit($hash); if (!%co) { die_error(undef, "Unknown commit object"); } @@ -2126,13 +2126,13 @@ sub git_commitdiff { if ($hash =~ m/^[0-9a-fA-F]{40}$/) { $expires = "+1d"; } - my $refs = read_info_ref(); - my $ref = git_get_referencing($refs, $co{'id'}); + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $co{'id'}); my $formats_nav = $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); git_header_html(undef, $expires); - git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); - git_header_div('commit', esc_html($co{'title'}) . $ref, $hash); + git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash); print "
\n"; my $comment = $co{'comment'}; my $empty = 0; @@ -2200,7 +2200,7 @@ sub git_commitdiff { sub git_commitdiff_plain { mkdir($git_temp, 0700); - my %co = git_read_commit($hash); + my %co = parse_commit($hash); if (!%co) { die_error(undef, "Unknown commit object"); } @@ -2214,7 +2214,7 @@ sub git_commitdiff_plain { # try to figure out the next tag after this commit my $tagname; - my $refs = read_info_ref("tags"); + my $refs = git_get_references("tags"); open $fd, "-|", $GIT, "rev-list", "HEAD"; my @commits = map { chomp; $_ } <$fd>; close $fd; @@ -2228,7 +2228,7 @@ sub git_commitdiff_plain { } print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\""); - my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); + my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); my $comment = $co{'comment'}; print "From: $co{'author'}\n" . "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n". @@ -2264,17 +2264,17 @@ sub git_commitdiff_plain { sub git_history { if (!defined $hash_base) { - $hash_base = git_read_head($project); + $hash_base = git_get_head_hash($project); } my $ftype; - my %co = git_read_commit($hash_base); + my %co = parse_commit($hash_base); if (!%co) { die_error(undef, "Unknown commit object"); } - my $refs = read_info_ref(); + my $refs = git_get_references(); git_header_html(); - git_page_nav('','', $hash_base,$co{'tree'},$hash_base); - git_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); if (!defined $hash && defined $file_name) { $hash = git_get_hash_by_path($hash_base, $file_name); } @@ -2290,11 +2290,11 @@ sub git_history { while (my $line = <$fd>) { if ($line =~ m/^([0-9a-fA-F]{40})/){ my $commit = $1; - my %co = git_read_commit($commit); + my %co = parse_commit($commit); if (!%co) { next; } - my $ref = git_get_referencing($refs, $commit); + my $ref = format_ref_marker($refs, $commit); if ($alternate) { print "
\n"; } else { @@ -2330,9 +2330,9 @@ sub git_search { die_error(undef, "Text field empty"); } if (!defined $hash) { - $hash = git_read_head($project); + $hash = git_get_head_hash($project); } - my %co = git_read_commit($hash); + my %co = parse_commit($hash); if (!%co) { die_error(undef, "Unknown commit object"); } @@ -2351,8 +2351,8 @@ sub git_search { $pickaxe_search = 1; } git_header_html(); - git_page_nav('','', $hash,$co{'tree'},$hash); - git_header_div('commit', esc_html($co{'title'}), $hash); + git_print_page_nav('','', $hash,$co{'tree'},$hash); + git_print_header_div('commit', esc_html($co{'title'}), $hash); print "
\n"; my $alternate = 0; @@ -2370,7 +2370,7 @@ sub git_search { next; } my @commit_lines = split "\n", $commit_text; - my %co = git_read_commit(undef, \@commit_lines); + my %co = parse_commit(undef, \@commit_lines); if (!%co) { next; } @@ -2451,7 +2451,7 @@ sub git_search { print "\n" . "\n"; } - %co = git_read_commit($1); + %co = parse_commit($1); } } close $fd; @@ -2461,14 +2461,14 @@ sub git_search { } sub git_shortlog { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); if (!defined $hash) { $hash = $head; } if (!defined $page) { $page = 0; } - my $refs = read_info_ref(); + my $refs = git_get_references(); my $limit = sprintf("--max-count=%i", (100 * ($page+1))); open my $fd, "-|", $GIT, "rev-list", $limit, $hash @@ -2476,7 +2476,7 @@ sub git_shortlog { my @revlist = map { chomp; $_ } <$fd>; close $fd; - my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist); + my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#revlist); my $next_link = ''; if ($#revlist >= (100 * ($page+1)-1)) { $next_link = @@ -2486,8 +2486,8 @@ sub git_shortlog { git_header_html(); - git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); - git_header_div('summary', $project); + git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); + git_print_header_div('summary', $project); git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link); @@ -2499,7 +2499,7 @@ sub git_shortlog { sub git_rss { # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ - open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) + open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_get_head_hash($project) or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-rev-list failed"); @@ -2514,12 +2514,12 @@ sub git_rss { for (my $i = 0; $i <= $#revlist; $i++) { my $commit = $revlist[$i]; - my %co = git_read_commit($commit); + my %co = parse_commit($commit); # we read 150, we always show 30 and the ones more recent than 48 hours if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { last; } - my %cd = date_str($co{'committer_epoch'}); + my %cd = parse_date($co{'committer_epoch'}); open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next; my @difftree = map { chomp; $_ } <$fd>; close $fd or next; @@ -2556,7 +2556,7 @@ sub git_rss { } sub git_opml { - my @list = git_read_projects(); + my @list = git_get_projects_list(); print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); print "\n". @@ -2569,12 +2569,12 @@ sub git_opml { foreach my $pr (@list) { my %proj = %$pr; - my $head = git_read_head($proj{'path'}); + my $head = git_get_head_hash($proj{'path'}); if (!defined $head) { next; } $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; - my %co = git_read_commit($head); + my %co = parse_commit($head); if (!%co) { next; } -- cgit v1.2.3 From a446d6bb53cb59f559603c411747d7ac5373e326 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 14 Aug 2006 02:07:00 +0200 Subject: gitweb: Separate ref parsing in git_get_refs_list into parse_ref Note that for each ref there are usually two calls to git subroutines: first to get the type of ref, second to parse ref if ref is of commit or tag type. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 80 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 28df59e6be..0c4ec92894 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -690,6 +690,49 @@ sub parse_commit { return %co; } +# parse ref from ref_file, given by ref_id, with given type +sub parse_ref { + my $ref_file = shift; + my $ref_id = shift; + my $type = shift || git_get_type($ref_id); + my %ref_item; + + $ref_item{'type'} = $type; + $ref_item{'id'} = $ref_id; + $ref_item{'epoch'} = 0; + $ref_item{'age'} = "unknown"; + if ($type eq "tag") { + my %tag = parse_tag($ref_id); + $ref_item{'comment'} = $tag{'comment'}; + if ($tag{'type'} eq "commit") { + my %co = parse_commit($tag{'object'}); + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; + } elsif (defined($tag{'epoch'})) { + my $age = time - $tag{'epoch'}; + $ref_item{'epoch'} = $tag{'epoch'}; + $ref_item{'age'} = age_string($age); + } + $ref_item{'reftype'} = $tag{'type'}; + $ref_item{'name'} = $tag{'name'}; + $ref_item{'refid'} = $tag{'object'}; + } elsif ($type eq "commit"){ + my %co = parse_commit($ref_id); + $ref_item{'reftype'} = "commit"; + $ref_item{'name'} = $ref_file; + $ref_item{'title'} = $co{'title'}; + $ref_item{'refid'} = $ref_id; + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; + } else { + $ref_item{'reftype'} = $type; + $ref_item{'name'} = $ref_file; + $ref_item{'refid'} = $ref_id; + } + + return %ref_item; +} + ## ...................................................................... ## parse to array of hashes functions @@ -709,44 +752,11 @@ sub git_get_refs_list { foreach my $ref_file (@refs) { my $ref_id = git_get_hash_by_ref("$project/$ref_dir/$ref_file"); my $type = git_get_type($ref_id) || next; - my %ref_item; - my %co; - $ref_item{'type'} = $type; - $ref_item{'id'} = $ref_id; - $ref_item{'epoch'} = 0; - $ref_item{'age'} = "unknown"; - if ($type eq "tag") { - my %tag = parse_tag($ref_id); - $ref_item{'comment'} = $tag{'comment'}; - if ($tag{'type'} eq "commit") { - %co = parse_commit($tag{'object'}); - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } elsif (defined($tag{'epoch'})) { - my $age = time - $tag{'epoch'}; - $ref_item{'epoch'} = $tag{'epoch'}; - $ref_item{'age'} = age_string($age); - } - $ref_item{'reftype'} = $tag{'type'}; - $ref_item{'name'} = $tag{'name'}; - $ref_item{'refid'} = $tag{'object'}; - } elsif ($type eq "commit"){ - %co = parse_commit($ref_id); - $ref_item{'reftype'} = "commit"; - $ref_item{'name'} = $ref_file; - $ref_item{'title'} = $co{'title'}; - $ref_item{'refid'} = $ref_id; - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } else { - $ref_item{'reftype'} = $type; - $ref_item{'name'} = $ref_file; - $ref_item{'refid'} = $ref_id; - } + my %ref_item = parse_ref($ref_file, $ref_id, $type); push @reflist, \%ref_item; } - # sort tags by age + # sort refs by age @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; return \@reflist; } -- cgit v1.2.3 From 17d07443188909ef5f8b8c24043cb6d9fef51bca Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 14 Aug 2006 02:08:27 +0200 Subject: gitweb: Refactor printing shortened title in git_shortlog_body and git_tags_body Separate printing of perhaps shortened title (subject) in git_shortlog_body and git_tags_body into format_subject_html. While at it, remove presentation element ... used to format title (subject) and move formatting to CSS. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.css | 5 +++++ gitweb/gitweb.perl | 34 ++++++++++++++++++---------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 47c1ade87e..f58a418fab 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -117,9 +117,14 @@ div.list_head { a.list { text-decoration: none; + font-weight: bold; color: #000000; } +table.tags a.list { + font-weight: normal; +} + a.list:hover { text-decoration: underline; color: #880000; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0c4ec92894..c4d6eab779 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -372,6 +372,22 @@ sub format_ref_marker { } } +# format, perhaps shortened and with markers, title line +sub format_subject_html { + my ($long, $short, $query, $extra) = @_; + $extra = '' unless defined($extra); + + if (length($short) < length($long)) { + return $cgi->a({-href => "$my_uri?" . esc_param($query), + -class => "list", -title => $long}, + esc_html($short) . $extra); + } else { + return $cgi->a({-href => "$my_uri?" . esc_param($query), + -class => "list"}, + esc_html($long) . $extra); + } +} + ## ---------------------------------------------------------------------- ## git utility subroutines, invoking git commands @@ -1085,15 +1101,7 @@ sub git_shortlog_body { print "\n" . "\n" . "\n" . "\n" . "\n" . "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 10)) . ""; - if (length($co{'title_short'}) < length($co{'title'})) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), - -class => "list", -title => "$co{'title'}"}, - "" . esc_html($co{'title_short'}) . "$ref"); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), - -class => "list"}, - "" . esc_html($co{'title'}) . "$ref"); - } + print format_subject_html($co{'title'}, $co{'title_short'}, "p=$project;a=commit;h=$commit", $ref); print "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . " | " . @@ -1139,13 +1147,7 @@ sub git_tags_body { ""; if (defined $comment) { - if (length($comment_short) < length($comment)) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), - -class => "list", -title => $comment}, $comment_short); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), - -class => "list"}, $comment); - } + print format_subject_html($comment, $comment_short, "p=$project;a=tag;h=$tag{'id'}"); } print ""; -- cgit v1.2.3 From 581860e1b8ac01c2cdcb5d98862ef7a80e789781 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 14 Aug 2006 02:09:08 +0200 Subject: gitweb: Separate main part of git_history into git_history_body Separates main part of git_history into git_history_body subroutine, and makes output more similar to git_shortlog. Adds "diff to current" link only for history of regular file (blob). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 96 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 36 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c4d6eab779..5af6e77e22 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1117,6 +1117,64 @@ sub git_shortlog_body { print "
\n"; } +sub git_history_body { + # Warning: assumes constant type (blob or tree) during history + my ($fd, $refs, $hash_base, $ftype, $extra) = @_; + + print "\n"; + my $alternate = 0; + while (my $line = <$fd>) { + if ($line !~ m/^([0-9a-fA-F]{40})/) { + next; + } + + my $commit = $1; + my %co = parse_commit($commit); + if (!%co) { + next; + } + + my $ref = format_ref_marker($refs, $commit); + + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n" . + # shortlog uses chop_str($co{'author_name'}, 10) + "\n" . + "\n" . + "\n" . + "\n"; + } + if (defined $extra) { + print "\n" . + "\n" . + "\n"; + } + print "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 15, 3)) . ""; + # originally git_history used chop_str($co{'title'}, 50) + print format_subject_html($co{'title'}, $co{'title_short'}, "p=$project;a=commit;h=$commit", $ref); + print "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$ftype;hb=$commit;f=$file_name")}, $ftype); + + if ($ftype eq 'blob') { + my $blob_current = git_get_hash_by_path($hash_base, $file_name); + my $blob_parent = git_get_hash_by_path($commit, $file_name); + if (defined $blob_current && defined $blob_parent && + $blob_current ne $blob_parent) { + print " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$blob_current;hp=$blob_parent;hb=$commit;f=$file_name")}, + "diff to current"); + } + } + print "
$extra
\n"; +} + sub git_tags_body { # uses global variable $project my ($taglist, $from, $to, $extra) = @_; @@ -2297,42 +2355,8 @@ sub git_history { open my $fd, "-|", $GIT, "rev-list", "--full-history", $hash_base, "--", $file_name; - print "\n"; - my $alternate = 0; - while (my $line = <$fd>) { - if ($line =~ m/^([0-9a-fA-F]{40})/){ - my $commit = $1; - my %co = parse_commit($commit); - if (!%co) { - next; - } - my $ref = format_ref_marker($refs, $commit); - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n" . - "\n" . - "\n" . - "\n" . - "\n"; - } - } - print "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, "" . - esc_html(chop_str($co{'title'}, 50)) . "$ref") . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$ftype;hb=$commit;f=$file_name")}, $ftype); - my $blob = git_get_hash_by_path($hash_base, $file_name); - my $blob_parent = git_get_hash_by_path($commit, $file_name); - if (defined $blob && defined $blob_parent && $blob ne $blob_parent) { - print " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$blob;hp=$blob_parent;hb=$commit;f=$file_name")}, - "diff to current"); - } - print "
\n"; + git_history_body($fd, $refs, $hash_base, $ftype); + close $fd; git_footer_html(); } -- cgit v1.2.3 From 1e0cf030c047741cab9ef36b91bc2ae57b83e405 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 14 Aug 2006 02:10:06 +0200 Subject: gitweb: Separate finding project owner into git_get_project_owner Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 50 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 5af6e77e22..6be6c55009 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -527,6 +527,37 @@ sub git_get_projects_list { return @list; } +sub git_get_project_owner { + my $project = shift; + my $owner; + + return undef unless $project; + + # read from file (url-encoded): + # 'git%2Fgit.git Linus+Torvalds' + # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' + # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' + if (-f $projects_list) { + open (my $fd , $projects_list); + while (my $line = <$fd>) { + chomp $line; + my ($pr, $ow) = split ' ', $line; + $pr = unescape($pr); + $ow = unescape($ow); + if ($pr eq $project) { + $owner = decode("utf8", $ow, Encode::FB_DEFAULT); + last; + } + } + close $fd; + } + if (!defined $owner) { + $owner = get_file_owner("$projectroot/$project"); + } + + return $owner; +} + sub git_get_references { my $type = shift || ""; my %refs; @@ -1458,24 +1489,7 @@ sub git_summary { my %co = parse_commit($head); my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); - my $owner; - if (-f $projects_list) { - open (my $fd , $projects_list); - while (my $line = <$fd>) { - chomp $line; - my ($pr, $ow) = split ' ', $line; - $pr = unescape($pr); - $ow = unescape($ow); - if ($pr eq $project) { - $owner = decode("utf8", $ow, Encode::FB_DEFAULT); - last; - } - } - close $fd; - } - if (!defined $owner) { - $owner = get_file_owner("$projectroot/$project"); - } + my $owner = git_get_project_owner($project); my $refs = git_get_references(); git_header_html(); -- cgit v1.2.3 From d294e1cad4eef263c7f4912332fd0094e37e95a7 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 14 Aug 2006 02:14:20 +0200 Subject: gitweb: Change appereance of marker of refs pointing to given object Change git_get_references to include type of ref in the %refs value, which means putting everything after 'refs/' as a ref name, not only last part of the name. Instead of separating refs pointing to the same object by " / " separator, use anonymous array reference to store all refs pointing to given object. Use 'git-ls-remote .' if $projectroot/$project/info/refs does not exist. (Perhaps it should be used always.) Refs are now in separate span elements. Class is dependent on the ref type: currently known classes are 'tag', 'head', 'remote', and 'ref' (last one for HEAD and other refs in the main directory). There is encompassing span element of class refs, just in case of unknown ref type. This might be considered cleaner separating of git_get_references into filling %refs hash only, and not taking part in formatting ref marker. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.css | 19 +++++++++++++++++-- gitweb/gitweb.perl | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index f58a418fab..21ce99cc91 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -326,15 +326,30 @@ a.rss_logo:hover { background-color: #ee5500; } -span.tag { +span.refs span { padding: 0px 4px; font-size: 10px; font-weight: normal; - background-color: #ffffaa; border: 1px solid; + background-color: #ffaaff; + border-color: #ffccff #ff00ee #ff00ee #ffccff; +} + +span.refs span.ref { + background-color: #aaaaff; + border-color: #ccccff #0033cc #0033cc #ccccff; +} + +span.refs span.tag { + background-color: #ffffaa; border-color: #ffffcc #ffee00 #ffee00 #ffffcc; } +span.refs span.head { + background-color: #aaffaa; + border-color: #ccffcc #00cc33 #00cc33 #ccffcc; +} + span.atnight { color: #cc0000; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6be6c55009..4fe3fc7b44 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -364,9 +364,26 @@ sub format_log_line_html { # format marker of refs pointing to given object sub format_ref_marker { my ($refs, $id) = @_; + my $markers = ''; if (defined $refs->{$id}) { - return ' ' . esc_html($refs->{$id}) . ''; + foreach my $ref (@{$refs->{$id}}) { + my ($type, $name) = qw(); + # e.g. tags/v2.6.11 or heads/next + if ($ref =~ m!^(.*?)s?/(.*)$!) { + $type = $1; + $name = $2; + } else { + $type = "ref"; + $name = $ref; + } + + $markers .= " " . esc_html($name) . ""; + } + } + + if ($markers) { + return ' '. $markers . ''; } else { return ""; } @@ -561,18 +578,24 @@ sub git_get_project_owner { sub git_get_references { my $type = shift || ""; my %refs; + my $fd; # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} - open my $fd, "$projectroot/$project/info/refs" or return; + if (-f "$projectroot/$project/info/refs") { + open $fd, "$projectroot/$project/info/refs" + or return; + } else { + open $fd, "-|", $GIT, "ls-remote", "." + or return; + } + while (my $line = <$fd>) { chomp $line; - # attention: for $type == "" it saves only last path part of ref name - # e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb' - if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) { + if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?[^\^]+)/) { if (defined $refs{$1}) { - $refs{$1} .= " / $2"; + push @{$refs{$1}}, $2; } else { - $refs{$1} = $2; + $refs{$1} = [ $2 ]; } } } -- cgit v1.2.3 From 618918e5411e2e769ad5e8d44c5d1cc363b983b5 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 14 Aug 2006 02:15:22 +0200 Subject: gitweb: Skip comments in mime.types like file Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 1 + 1 file changed, 1 insertion(+) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 4fe3fc7b44..52ae2aa861 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -858,6 +858,7 @@ sub mimetype_guess_file { my %mimemap; open(MIME, $mimemap) or return undef; while () { + next if m/^#/; # skip comments my ($mime, $exts) = split(/\t+/); if (defined $exts) { my @exts = split(/\s+/, $exts); -- cgit v1.2.3 From d5aa50de621220d7451a5d497479327d2a608009 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 14 Aug 2006 02:16:33 +0200 Subject: gitweb: True fix: Support for the standard mime.types map in gitweb True fix for error in mimetype_guess, error introduced in original commit 2d00737489b8c61ed616b261c7c9bd314e2b0b41 and later fixed temporarily by commenting out the line that caused error in commit 57bd4d3523efecf60197040cad34154aff4ddf80. Gitweb now supports mime.types map $mimetypes_file relative to project. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 52ae2aa861..15875a8663 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -880,7 +880,10 @@ sub mimetype_guess { if ($mimetypes_file) { my $file = $mimetypes_file; - #$file =~ m#^/# or $file = "$projectroot/$path/$file"; + if ($file !~ m!^/!) { # if it is relative path + # it is relative to project + $file = "$projectroot/$project/$file"; + } $mime = mimetype_guess_file($filename, $file); } $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); -- cgit v1.2.3 From 4a4a1a53d1c4a272d4cf0d53116896d057174671 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 14 Aug 2006 02:18:33 +0200 Subject: gitweb: Separate printing difftree in git_commit into git_difftree_body Separate printing difftree in git_commit into separate git_difftree_body subroutine. Add support for "C" (copied) status. For "M" and "C" add parameter 'fp' (filename parent) to the "diff" link; currently not supported by git_blobdiff ("blobdiff" action). Reindented, realigned, added comments. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.css | 4 + gitweb/gitweb.perl | 237 ++++++++++++++++++++++++++++++++--------------------- 2 files changed, 146 insertions(+), 95 deletions(-) diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 21ce99cc91..9013895857 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -226,6 +226,10 @@ table.diff_tree span.file_status.mode_chnge { color: #777777; } +table.diff_tree span.file_status.copied { + color: #70a070; +} + /* age2: 60*60*24*2 <= age */ table.project_list td.age2, table.blame td.age2 { font-style: italic; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 15875a8663..ab28caa8c8 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1136,6 +1136,145 @@ sub git_print_page_path { ## ...................................................................... ## functions printing large fragments of HTML +sub git_difftree_body { + my ($difftree, $parent) = @_; + + print "
\n"; + if ($#{$difftree} > 10) { + print(($#{$difftree} + 1) . " files changed:\n"); + } + print "
\n"; + + print "\n"; + my $alternate = 0; + foreach my $line (@{$difftree}) { + # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' + # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' + if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) { + next; + } + my $from_mode = $1; + my $to_mode = $2; + my $from_id = $3; + my $to_id = $4; + my $status = $5; + my $similarity = $6; # score + my $file = validate_input(unquote($7)); + + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + + if ($status eq "A") { # created + my $mode_chng = ""; + if (S_ISREG(oct $to_mode)) { + $mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777); + } + print "\n" . + "\n" . + "\n"; + + } elsif ($status eq "D") { # deleted + print "\n" . + "\n" . + "\n" + + } elsif ($status eq "M" || $status eq "T") { # modified, or type changed + my $mode_chnge = ""; + if ($from_mode != $to_mode) { + $mode_chnge = " [changed"; + if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) { + $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode); + } + if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) { + if (S_ISREG($from_mode) && S_ISREG($to_mode)) { + $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777); + } elsif (S_ISREG($to_mode)) { + $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777); + } + } + $mode_chnge .= "]\n"; + } + print "\n" . + "\n" . + "\n"; + + } elsif ($status eq "R") { # renamed + my ($from_file, $to_file) = split "\t", $file; + my $mode_chng = ""; + if ($from_mode != $to_mode) { + $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); + } + print "\n" . + "\n" . + "\n"; + + } elsif ($status eq "C") { # copied + my ($from_file, $to_file) = split "\t", $file; + my $mode_chng = ""; + if ($from_mode != $to_mode) { + $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); + } + print "\n" . + "\n" . + "\n"; + } # we should not encounter Unmerged (U) or Unknown (X) status + print "\n"; + } + print "
" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), + -class => "list"}, esc_html($file)) . + "[new " . file_type($to_mode) . "$mode_chng]" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . + "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file"), + -class => "list"}, esc_html($file)) . "[deleted " . file_type($from_mode). "]" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file")}, "blob") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$parent;f=$file")}, "history") . + ""; + if ($to_id ne $from_id) { # modified + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file"), + -class => "list"}, esc_html($file)); + } else { # mode changed + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), + -class => "list"}, esc_html($file)); + } + print "$mode_chnge" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob"); + if ($to_id ne $from_id) { # modified + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file")}, "diff"); + } + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . "\n"; + print "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file"), + -class => "list"}, esc_html($to_file)) . "[moved from " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$from_file"), + -class => "list"}, esc_html($from_file)) . + " with " . (int $similarity) . "% similarity$mode_chng]" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob"); + if ($to_id ne $from_id) { + print " | " . + $cgi->a({-href => "$my_uri?" . + esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file;fp=$from_file")}, "diff"); + } + print "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file"), + -class => "list"}, esc_html($to_file)) . "[copied from " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$from_file"), + -class => "list"}, esc_html($from_file)) . + " with " . (int $similarity) . "% similarity$mode_chng]" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob"); + if ($to_id ne $from_id) { + print " | " . + $cgi->a({-href => "$my_uri?" . + esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file;fp=$from_file")}, "diff"); + } + print "
\n"; +} + sub git_shortlog_body { # uses global variable $project my ($revlist, $from, $to, $refs, $extra) = @_; @@ -2089,101 +2228,9 @@ sub git_commit { } } print "
\n"; - print "
\n"; - if ($#difftree > 10) { - print(($#difftree + 1) . " files changed:\n"); - } - print "
\n"; - print "\n"; - my $alternate = 0; - foreach my $line (@difftree) { - # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' - # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) { - next; - } - my $from_mode = $1; - my $to_mode = $2; - my $from_id = $3; - my $to_id = $4; - my $status = $5; - my $similarity = $6; - my $file = validate_input(unquote($7)); - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - if ($status eq "A") { - my $mode_chng = ""; - if (S_ISREG(oct $to_mode)) { - $mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777); - } - print "\n" . - "\n" . - "\n"; - } elsif ($status eq "D") { - print "\n" . - "\n" . - "\n" - } elsif ($status eq "M" || $status eq "T") { - my $mode_chnge = ""; - if ($from_mode != $to_mode) { - $mode_chnge = " [changed"; - if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) { - $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode); - } - if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) { - if (S_ISREG($from_mode) && S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777); - } elsif (S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777); - } - } - $mode_chnge .= "]\n"; - } - print "\n" . - "\n" . - "\n"; - } elsif ($status eq "R") { - my ($from_file, $to_file) = split "\t", $file; - my $mode_chng = ""; - if ($from_mode != $to_mode) { - $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); - } - print "\n" . - "\n" . - "\n"; - } - print "\n"; - } - print "
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "[new " . file_type($to_mode) . "$mode_chng]" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file"), -class => "list"}, esc_html($file)) . "[deleted " . file_type($from_mode). "]" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$parent;f=$file")}, "history") . - ""; - if ($to_id ne $from_id) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); - } - print "$mode_chnge"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob"); - if ($to_id ne $from_id) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file")}, "diff"); - } - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . "\n"; - print "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file"), -class => "list"}, esc_html($to_file)) . "[moved from " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$from_file"), -class => "list"}, esc_html($from_file)) . - " with " . (int $similarity) . "% similarity$mode_chng]" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob"); - if ($to_id ne $from_id) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file")}, "diff"); - } - print "
\n"; + + git_difftree_body(\@difftree, $parent); + git_footer_html(); } -- cgit v1.2.3 From 2de21fac9842650fceb3db68f15711e38fabc3c8 Mon Sep 17 00:00:00 2001 From: Yasushi SHOJI Date: Tue, 15 Aug 2006 07:50:49 +0900 Subject: gitweb: configurable home link string I've always found difficult to figure out git URL for clone from gitweb URL because git:// and http:// are different on many site including kernel.org. I've found this enhancement at http://dev.laptop.org/git when I was on git channel, and thought that it'd be nice if all public gitweb site show it's git URL on its page. This patch allow us to change the home link string. The current default is "projects" as we all see on gitweb now. ie. kernel.org might set this variable to "git://git.kernel.org/pub/scm/" Signed-off-by: Yasushi SHOJI Signed-off-by: Junio C Hamano --- Makefile | 2 ++ gitweb/gitweb.perl | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a538710ed6..b363f50d48 100644 --- a/Makefile +++ b/Makefile @@ -123,6 +123,7 @@ GIT_PYTHON_DIR = $(prefix)/share/git-core/python # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl +GITWEB_HOME_LINK_STR = projects GITWEB_SITENAME = GITWEB_PROJECTROOT = /pub/git GITWEB_LIST = @@ -617,6 +618,7 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ -e 's|++GIT_BINDIR++|$(bindir)|g' \ -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \ + -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \ diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ab28caa8c8..b5b89de91b 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -36,6 +36,9 @@ our $git_temp = "/tmp/gitweb"; # target of the home link on top of all pages our $home_link = $my_uri; +# string of the home link on top of all pages +our $home_link_str = "++GITWEB_HOME_LINK_STR++"; + # name of your site or organization to appear in page titles # replace this with something more descriptive for clearer bookmarks our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled"; @@ -974,7 +977,7 @@ EOF "
" . "\"git\"" . "\n"; - print $cgi->a({-href => esc_param($home_link)}, "projects") . " / "; + print $cgi->a({-href => esc_param($home_link)}, $home_link_str) . " / "; if (defined $project) { print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project)); if (defined $action) { -- cgit v1.2.3 From b4e275992f7ea56b7944abb2b9339c7f6f5c9423 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:18:11 -0700 Subject: blame.c return cleanup Removes conditional from return Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- blame.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/blame.c b/blame.c index 7099b53c72..54a43d5c54 100644 --- a/blame.c +++ b/blame.c @@ -351,10 +351,7 @@ static int fill_util_info(struct commit *commit) assert(util); assert(util->pathname); - if (get_blob_sha1(commit->tree, util->pathname, util->sha1)) - return 1; - else - return 0; + return !!get_blob_sha1(commit->tree, util->pathname, util->sha1); } static void alloc_line_map(struct commit *commit) -- cgit v1.2.3 From b756776d19356a961cc3d1861b15720a6d0e8462 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:22:15 -0700 Subject: builtin-grep.c cleanup Removes conditional return. Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- builtin-grep.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/builtin-grep.c b/builtin-grep.c index a561612e7e..3ec99b7010 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -390,9 +390,7 @@ static int buffer_is_binary(const char *ptr, unsigned long size) { if (FIRST_FEW_BYTES < size) size = FIRST_FEW_BYTES; - if (memchr(ptr, 0, size)) - return 1; - return 0; + return !!memchr(ptr, 0, size); } static int fixmatch(const char *pattern, char *line, regmatch_t *match) -- cgit v1.2.3 From 9e0ec82cac84e6963d7e4aa229b7464daaed4ef8 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:26:58 -0700 Subject: builtin-push.c cleanup Removes conditional return in builtin-push.c Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- builtin-push.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/builtin-push.c b/builtin-push.c index 53bc378f73..c09ff2f65e 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -32,10 +32,8 @@ static int expand_one_ref(const char *ref, const unsigned char *sha1) /* Ignore the "refs/" at the beginning of the refname */ ref += 5; - if (strncmp(ref, "tags/", 5)) - return 0; - - add_refspec(strdup(ref)); + if (!strncmp(ref, "tags/", 5)) + add_refspec(strdup(ref)); return 0; } -- cgit v1.2.3 From 8c0b2bb636c15a1cd0adb8bcf3b42497d699c884 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:34:16 -0700 Subject: diff.c cleanup Removes conditional return. Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- diff.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/diff.c b/diff.c index 8861b853e7..2327e6065b 100644 --- a/diff.c +++ b/diff.c @@ -904,9 +904,7 @@ static int mmfile_is_binary(mmfile_t *mf) long sz = mf->size; if (FIRST_FEW_BYTES < sz) sz = FIRST_FEW_BYTES; - if (memchr(mf->ptr, 0, sz)) - return 1; - return 0; + return !!memchr(mf->ptr, 0, sz); } static void builtin_diff(const char *name_a, -- cgit v1.2.3 From 0bc87ffb6c8edbb44bd72a4e1a5aa81c64f7eb37 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:37:05 -0700 Subject: http-push.c cleanup Removes conditional return. Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- http-push.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/http-push.c b/http-push.c index 0359ae5b6f..22a3e2bc0a 100644 --- a/http-push.c +++ b/http-push.c @@ -2186,10 +2186,7 @@ static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha struct commit *branch = lookup_commit(branch_sha1); struct commit_list *merge_bases = get_merge_bases(head, branch, 1); - if (merge_bases && !merge_bases->next && merge_bases->item == branch) - return 1; - - return 0; + return (merge_bases && !merge_bases->next && merge_bases->item == branch); } static int delete_remote_branch(char *pattern, int force) -- cgit v1.2.3 From 968a1d65f430e4e8234204c62361e3d21cac810c Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:38:14 -0700 Subject: read-cache.c cleanup Removes conditional returns. Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- read-cache.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/read-cache.c b/read-cache.c index c923a32707..b18f9f72d9 100644 --- a/read-cache.c +++ b/read-cache.c @@ -880,10 +880,8 @@ static int write_index_ext_header(SHA_CTX *context, int fd, { ext = htonl(ext); sz = htonl(sz); - if ((ce_write(context, fd, &ext, 4) < 0) || - (ce_write(context, fd, &sz, 4) < 0)) - return -1; - return 0; + return ((ce_write(context, fd, &ext, 4) < 0) || + (ce_write(context, fd, &sz, 4) < 0)) ? -1 : 0; } static int ce_flush(SHA_CTX *context, int fd) @@ -905,9 +903,7 @@ static int ce_flush(SHA_CTX *context, int fd) /* Append the SHA1 signature at the end */ SHA1_Final(write_buffer + left, context); left += 20; - if (write(fd, write_buffer, left) != left) - return -1; - return 0; + return (write(fd, write_buffer, left) != left) ? -1 : 0; } static void ce_smudge_racily_clean_entry(struct cache_entry *ce) -- cgit v1.2.3 From a976b0a5933f37e7e8a1829703705268bc829794 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Aug 2006 18:36:00 -0700 Subject: Remove combine-diff.c::uninteresting() A patch from David Rientjes made me realize we do not have to have this function -- just call diff_unmodified_pair() directly. Signed-off-by: Junio C Hamano --- combine-diff.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/combine-diff.c b/combine-diff.c index f2f3806477..4c6bfed56e 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -7,13 +7,6 @@ #include "xdiff-interface.h" #include "log-tree.h" -static int uninteresting(struct diff_filepair *p) -{ - if (diff_unmodified_pair(p)) - return 1; - return 0; -} - static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent) { struct diff_queue_struct *q = &diff_queued_diff; @@ -25,7 +18,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, for (i = 0; i < q->nr; i++) { int len; const char *path; - if (uninteresting(q->queue[i])) + if (diff_unmodified_pair(q->queue[i])) continue; path = q->queue[i]->two->path; len = strlen(path); @@ -57,7 +50,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, const char *path; int len; - if (uninteresting(q->queue[i])) + if (diff_unmodified_pair(q->queue[i])) continue; path = q->queue[i]->two->path; len = strlen(path); -- cgit v1.2.3 From eddd1c8cefa596abbb81ac5a0af21ba104033b3d Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:19:15 -0700 Subject: Make pprint_tag void and cleans up call in cmd_cat_file. Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- builtin-cat-file.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/builtin-cat-file.c b/builtin-cat-file.c index 814fb0743f..df009ade7a 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -26,7 +26,7 @@ static void flush_buffer(const char *buf, unsigned long size) } } -static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) +static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) { /* the parser in tag.c is useless here. */ const char *endp = buf + size; @@ -91,7 +91,6 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long */ if (cp < endp) flush_buffer(cp, endp - cp); - return 0; } int cmd_cat_file(int argc, const char **argv, const char *prefix) @@ -145,8 +144,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) buf = read_sha1_file(sha1, type, &size); if (!buf) die("Cannot read object %s", argv[2]); - if (!strcmp(type, tag_type)) - return pprint_tag(sha1, buf, size); + if (!strcmp(type, tag_type)) { + pprint_tag(sha1, buf, size); + return 0; + } /* otherwise just spit out the data */ break; -- cgit v1.2.3 From cf995ede2cf53d70e5de2525184597211c827dc5 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:39:27 -0700 Subject: Make show_entry void Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- tree-diff.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tree-diff.c b/tree-diff.c index 1cdf8aa908..916f489c5b 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -15,7 +15,8 @@ static char *malloc_base(const char *base, const char *path, int pathlen) return newbase; } -static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base); +static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, + const char *base); static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) { @@ -131,7 +132,8 @@ static void show_tree(struct diff_options *opt, const char *prefix, struct tree_ } /* A file entry went away or appeared */ -static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base) +static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, + const char *base) { unsigned mode; const char *path; @@ -152,11 +154,9 @@ static int show_entry(struct diff_options *opt, const char *prefix, struct tree_ free(tree); free(newbase); - return 0; + } else { + opt->add_remove(opt, prefix[0], mode, sha1, base, path); } - - opt->add_remove(opt, prefix[0], mode, sha1, base, path); - return 0; } int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) -- cgit v1.2.3 From f7f0fbfcf4fb9367a12aba3a205fb5e1276a2800 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:20:12 -0700 Subject: Make checkout_all void. Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- builtin-checkout-index.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c index 8d0dbad49e..6b55f931cb 100644 --- a/builtin-checkout-index.c +++ b/builtin-checkout-index.c @@ -122,7 +122,7 @@ static int checkout_file(const char *name, int prefix_length) return -1; } -static int checkout_all(const char *prefix, int prefix_length) +static void checkout_all(const char *prefix, int prefix_length) { int i, errs = 0; struct cache_entry* last_ce = NULL; @@ -153,7 +153,6 @@ static int checkout_all(const char *prefix, int prefix_length) * exit with the same code as die(). */ exit(128); - return 0; } static const char checkout_cache_usage[] = -- cgit v1.2.3 From b5524c826dc36e5d9d2455cfca85aa62f1a776d1 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:36:18 -0700 Subject: Make fsck_dir void. Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- fsck-objects.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fsck-objects.c b/fsck-objects.c index e167f4105f..4ba3377aef 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -366,13 +366,13 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino) sha1_list.nr = ++nr; } -static int fsck_dir(int i, char *path) +static void fsck_dir(int i, char *path) { DIR *dir = opendir(path); struct dirent *de; if (!dir) - return 0; + return; while ((de = readdir(dir)) != NULL) { char name[100]; @@ -398,7 +398,6 @@ static int fsck_dir(int i, char *path) fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name); } closedir(dir); - return 0; } static int default_refs = 0; -- cgit v1.2.3 From aa145403da905aaa9f129b0f13b5bca7994cb329 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:38:50 -0700 Subject: Make pack_objects void. Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- send-pack.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/send-pack.c b/send-pack.c index 10bc8bc359..b7cc1a9089 100644 --- a/send-pack.c +++ b/send-pack.c @@ -111,7 +111,7 @@ static void rev_list(int fd, struct ref *refs) exec_rev_list(refs); } -static int pack_objects(int fd, struct ref *refs) +static void pack_objects(int fd, struct ref *refs) { pid_t rev_list_pid; @@ -126,7 +126,6 @@ static int pack_objects(int fd, struct ref *refs) * We don't wait for the rev-list pipeline in the parent: * we end up waiting for the other end instead */ - return 0; } static void unmark_and_free(struct commit_list *list, unsigned int mark) -- cgit v1.2.3 From 74b504f67173a9f4dd06f01d109c58ea13f279b4 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:40:06 -0700 Subject: Make track_tree_refs void. Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- tree.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tree.c b/tree.c index a6032e35ec..ef456be9dd 100644 --- a/tree.c +++ b/tree.c @@ -144,7 +144,7 @@ struct tree *lookup_tree(const unsigned char *sha1) return (struct tree *) obj; } -static int track_tree_refs(struct tree *item) +static void track_tree_refs(struct tree *item) { int n_refs = 0, i; struct object_refs *refs; @@ -174,7 +174,6 @@ static int track_tree_refs(struct tree *item) refs->ref[i++] = obj; } set_object_refs(&item->object, refs); - return 0; } int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size) -- cgit v1.2.3 From 59076eba6e1a41c2e0032dbd656d8429e9655358 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:40:51 -0700 Subject: Make upload_pack void and remove conditional return. Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- upload-pack.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/upload-pack.c b/upload-pack.c index bbd6bd60b5..27e2abe36c 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -459,18 +459,17 @@ static int send_ref(const char *refname, const unsigned char *sha1) return 0; } -static int upload_pack(void) +static void upload_pack(void) { reset_timeout(); head_ref(send_ref); for_each_ref(send_ref); packet_flush(1); receive_needs(); - if (!want_obj.nr) - return 0; - get_common_commits(); - create_pack_file(); - return 0; + if (want_obj.nr) { + get_common_commits(); + create_pack_file(); + } } int main(int argc, char **argv) -- cgit v1.2.3 From 78b713a1d0909c8eb8dc0bad5df624c33d065c16 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 14 Aug 2006 13:32:01 -0700 Subject: Make sha1flush void and remove conditional return. Signed-off-by: David Rientjes Signed-off-by: Junio C Hamano --- csum-file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/csum-file.c b/csum-file.c index 6a7b40fd09..e2278897d0 100644 --- a/csum-file.c +++ b/csum-file.c @@ -10,7 +10,7 @@ #include "cache.h" #include "csum-file.h" -static int sha1flush(struct sha1file *f, unsigned int count) +static void sha1flush(struct sha1file *f, unsigned int count) { void *buf = f->buffer; @@ -21,7 +21,7 @@ static int sha1flush(struct sha1file *f, unsigned int count) count -= ret; if (count) continue; - return 0; + return; } if (!ret) die("sha1 file '%s' write error. Out of diskspace", f->name); -- cgit v1.2.3 From 6a0ebe8ced66b154a48074d3e3321ff1972ec669 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Aug 2006 23:24:55 -0700 Subject: t4116 apply --reverse test The binary patch test needs to be made more careful not to have the postimage blob in the repository in which the patch is applied Signed-off-by: Junio C Hamano --- t/t4116-apply-reverse.sh | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100755 t/t4116-apply-reverse.sh diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh new file mode 100755 index 0000000000..69aebe6005 --- /dev/null +++ b/t/t4116-apply-reverse.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply in reverse + +' + +. ./test-lib.sh + +test_expect_success setup ' + + for i in a b c d e f g h i j k l m n; do echo $i; done >file1 && + tr "[ijk]" '\''[\0\1\2]'\'' file2 && + + git add file1 file2 && + git commit -m initial && + git tag initial && + + for i in a b c g h i J K L m o n p q; do echo $i; done >file1 && + tr "[mon]" '\''[\0\1\2]'\'' file2 && + + git commit -a -m second && + + git diff --binary -R initial >patch + +' + +test_expect_success 'apply in forward' ' + + git apply --index --binary patch && + git diff initial >diff && + diff -u /dev/null diff + +' + +test_expect_success 'apply in reverse' ' + + git apply --reverse --binary --index patch && + git diff >diff && + diff -u /dev/null diff + +' + +test_done -- cgit v1.2.3 From f686d03034c74bd0de2002b7c277a0838662ea00 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Aug 2006 23:26:51 -0700 Subject: git-apply --reverse: simplify reverse option. Having is_reverse in each patch did not make sense. This will hopefully simplify the work needed to introduce reversible binary diff format. Signed-off-by: Junio C Hamano --- builtin-apply.c | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 9cf477c701..267aab0e84 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -37,6 +37,7 @@ static int numstat = 0; static int summary = 0; static int check = 0; static int apply = 1; +static int apply_in_reverse = 0; static int no_add = 0; static int show_index_info = 0; static int line_termination = '\n'; @@ -120,7 +121,7 @@ struct fragment { struct patch { char *new_name, *old_name, *def_name; unsigned int old_mode, new_mode; - int is_rename, is_copy, is_new, is_delete, is_binary, is_reverse; + int is_rename, is_copy, is_new, is_delete, is_binary; #define BINARY_DELTA_DEFLATED 1 #define BINARY_LITERAL_DEFLATED 2 unsigned long deflate_origlen; @@ -1143,7 +1144,6 @@ static void reverse_patches(struct patch *p) swap(frag->newpos, frag->oldpos); swap(frag->newlines, frag->oldlines); } - p->is_reverse = !p->is_reverse; } } @@ -1363,8 +1363,7 @@ static int apply_line(char *output, const char *patch, int plen) return plen; } -static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, - int reverse, int inaccurate_eof) +static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, int inaccurate_eof) { int match_beginning, match_end; char *buf = desc->buffer; @@ -1396,7 +1395,7 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, if (len < size && patch[len] == '\\') plen--; first = *patch; - if (reverse) { + if (apply_in_reverse) { if (first == '-') first = '+'; else if (first == '+') @@ -1536,7 +1535,7 @@ static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) void *result; /* Binary patch is irreversible */ - if (patch->is_reverse) + if (apply_in_reverse) return error("cannot reverse-apply a binary patch to '%s'", patch->new_name ? patch->new_name : patch->old_name); @@ -1657,8 +1656,7 @@ static int apply_fragments(struct buffer_desc *desc, struct patch *patch) return apply_binary(desc, patch); while (frag) { - if (apply_one_fragment(desc, frag, patch->is_reverse, - patch->inaccurate_eof) < 0) + if (apply_one_fragment(desc, frag, patch->inaccurate_eof) < 0) return error("patch failed: %s:%ld", name, frag->oldpos); frag = frag->next; @@ -2194,8 +2192,7 @@ static int use_patch(struct patch *p) return 1; } -static int apply_patch(int fd, const char *filename, - int reverse, int inaccurate_eof) +static int apply_patch(int fd, const char *filename, int inaccurate_eof) { unsigned long offset, size; char *buffer = read_patch_file(fd, &size); @@ -2215,7 +2212,7 @@ static int apply_patch(int fd, const char *filename, nr = parse_chunk(buffer + offset, size, patch); if (nr < 0) break; - if (reverse) + if (apply_in_reverse) reverse_patches(patch); if (use_patch(patch)) { patch_stats(patch); @@ -2278,7 +2275,6 @@ int cmd_apply(int argc, const char **argv, const char *prefix) { int i; int read_stdin = 1; - int reverse = 0; int inaccurate_eof = 0; const char *whitespace_option = NULL; @@ -2289,7 +2285,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) int fd; if (!strcmp(arg, "-")) { - apply_patch(0, "", reverse, inaccurate_eof); + apply_patch(0, "", inaccurate_eof); read_stdin = 0; continue; } @@ -2367,7 +2363,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "-R") || !strcmp(arg, "--reverse")) { - reverse = 1; + apply_in_reverse = 1; continue; } if (!strcmp(arg, "--inaccurate-eof")) { @@ -2390,12 +2386,12 @@ int cmd_apply(int argc, const char **argv, const char *prefix) usage(apply_usage); read_stdin = 0; set_default_whitespace_mode(whitespace_option); - apply_patch(fd, arg, reverse, inaccurate_eof); + apply_patch(fd, arg, inaccurate_eof); close(fd); } set_default_whitespace_mode(whitespace_option); if (read_stdin) - apply_patch(0, "", reverse, inaccurate_eof); + apply_patch(0, "", inaccurate_eof); if (whitespace_error) { if (squelch_whitespace_errors && squelch_whitespace_errors < whitespace_error) { -- cgit v1.2.3 From f4e14ca9e05876ec1e0d79d7c12b5b3a6b2e9d08 Mon Sep 17 00:00:00 2001 From: Dennis Stosberg Date: Tue, 15 Aug 2006 11:01:20 +0200 Subject: Solaris has strlcpy() at least since version 8 See http://docs.sun.com/app/docs/doc/816-3321/6m9k23sjk?a=view Signed-off-by: Dennis Stosberg Signed-off-by: Junio C Hamano --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index b363f50d48..b86de761ff 100644 --- a/Makefile +++ b/Makefile @@ -327,7 +327,6 @@ ifeq ($(uname_S),SunOS) NEEDS_NSL = YesPlease SHELL_PATH = /bin/bash NO_STRCASESTR = YesPlease - NO_STRLCPY = YesPlease ifeq ($(uname_R),5.8) NEEDS_LIBICONV = YesPlease NO_UNSETENV = YesPlease -- cgit v1.2.3 From ab5573ae9f4178a7b6f6920f04c73a65feb3c690 Mon Sep 17 00:00:00 2001 From: Dennis Stosberg Date: Tue, 15 Aug 2006 11:01:25 +0200 Subject: Look for sockaddr_storage in sys/socket.h On Solaris and the BSDs the definition of "struct sockaddr_storage" is not available from "netinet/in.h". On Solaris "sys/socket.h" is enough, at least OpenBSD needs "sys/types.h", too. Using "sys/types.h" and "sys/socket.h" seems to be a more portable way. Signed-off-by: Dennis Stosberg Signed-off-by: Junio C Hamano --- configure.ac | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index e890131c46..0321d437d9 100644 --- a/configure.ac +++ b/configure.ac @@ -181,8 +181,10 @@ AC_SUBST(NO_D_TYPE_IN_DIRENT) # sockaddr_storage. AC_CHECK_TYPE(struct sockaddr_storage, [NO_SOCKADDR_STORAGE=], -[NO_SOCKADDR_STORAGE=YesPlease], -[#include ]) +[NO_SOCKADDR_STORAGE=YesPlease],[ +#include +#include +]) AC_SUBST(NO_SOCKADDR_STORAGE) # # Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). -- cgit v1.2.3 From d1b9944db8f66131b259be407472e53db33718ea Mon Sep 17 00:00:00 2001 From: Dennis Stosberg Date: Tue, 15 Aug 2006 11:01:27 +0200 Subject: Fix detection of ipv6 on Solaris The configuration script detects whether linking with -lsocket is necessary but doesn't add -lsocket to LIBS. This lets the ipv6 test fail. Signed-off-by: Dennis Stosberg Signed-off-by: Junio C Hamano --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index 0321d437d9..36f9cd94d8 100644 --- a/configure.ac +++ b/configure.ac @@ -154,6 +154,7 @@ AC_CHECK_LIB([c], [socket], [NEEDS_SOCKET=], [NEEDS_SOCKET=YesPlease]) AC_SUBST(NEEDS_SOCKET) +test -n "$NEEDS_SOCKET" && LIBS="$LIBS -lsocket" ## Checks for header files. -- cgit v1.2.3 From 3cd4f5e8eb04ae01298ceaf46bb41a4277031916 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 15 Aug 2006 02:23:06 -0700 Subject: git-apply --binary: clean up and prepare for --reverse This cleans up the implementation of "git-apply --binary", and implements reverse application of binary patches (when git-diff is converted to emit reversible binary patches). Earlier, the types of encoding (either deflated literal or deflated delta) were stored in is_binary field in struct patch, which meant that we cannot store more than one fragment that differ in the encoding for a patch. This moves the information to a field in struct fragment that is otherwise unused for binary patches, and makes it possible to hang two (or more, but two is enough) hunks for a binary patch. The original "binary patch" output from git-diff is internally parsed into an "is_binary" patch with one fragment. Upcoming reversible binary patch output will have two fragments, the first one being the forward patch and the second one the reverse patch. Signed-off-by: Junio C Hamano --- builtin-apply.c | 206 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 141 insertions(+), 65 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 267aab0e84..4573c9abb2 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -109,6 +109,13 @@ static int max_change, max_len; */ static int linenr = 1; +/* + * This represents one "hunk" from a patch, starting with + * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The + * patch text is pointed at by patch, and its byte length + * is stored in size. leading and trailing are the number + * of context lines. + */ struct fragment { unsigned long leading, trailing; unsigned long oldpos, oldlines; @@ -118,12 +125,19 @@ struct fragment { struct fragment *next; }; +/* + * When dealing with a binary patch, we reuse "leading" field + * to store the type of the binary hunk, either deflated "delta" + * or deflated "literal". + */ +#define binary_patch_method leading +#define BINARY_DELTA_DEFLATED 1 +#define BINARY_LITERAL_DEFLATED 2 + struct patch { char *new_name, *old_name, *def_name; unsigned int old_mode, new_mode; int is_rename, is_copy, is_new, is_delete, is_binary; -#define BINARY_DELTA_DEFLATED 1 -#define BINARY_LITERAL_DEFLATED 2 unsigned long deflate_origlen; int lines_added, lines_deleted; int score; @@ -979,43 +993,70 @@ static inline int metadata_changes(struct patch *patch) patch->old_mode != patch->new_mode); } -static int parse_binary(char *buffer, unsigned long size, struct patch *patch) +static char *inflate_it(const void *data, unsigned long size, + unsigned long inflated_size) { - /* We have read "GIT binary patch\n"; what follows is a line - * that says the patch method (currently, either "deflated - * literal" or "deflated delta") and the length of data before - * deflating; a sequence of 'length-byte' followed by base-85 - * encoded data follows. + z_stream stream; + void *out; + int st; + + memset(&stream, 0, sizeof(stream)); + + stream.next_in = (unsigned char *)data; + stream.avail_in = size; + stream.next_out = out = xmalloc(inflated_size); + stream.avail_out = inflated_size; + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { + free(out); + return NULL; + } + return out; +} + +static struct fragment *parse_binary_hunk(char **buf_p, + unsigned long *sz_p, + int *status_p, + int *used_p) +{ + /* Expect a line that begins with binary patch method ("literal" + * or "delta"), followed by the length of data before deflating. + * a sequence of 'length-byte' followed by base-85 encoded data + * should follow, terminated by a newline. * * Each 5-byte sequence of base-85 encodes up to 4 bytes, * and we would limit the patch line to 66 characters, * so one line can fit up to 13 groups that would decode * to 52 bytes max. The length byte 'A'-'Z' corresponds * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes. - * The end of binary is signaled with an empty line. */ int llen, used; - struct fragment *fragment; + unsigned long size = *sz_p; + char *buffer = *buf_p; + int patch_method; + unsigned long origlen; char *data = NULL; + int hunk_size = 0; + struct fragment *frag; - patch->fragments = fragment = xcalloc(1, sizeof(*fragment)); - - /* Grab the type of patch */ llen = linelen(buffer, size); used = llen; - linenr++; + + *status_p = 0; if (!strncmp(buffer, "delta ", 6)) { - patch->is_binary = BINARY_DELTA_DEFLATED; - patch->deflate_origlen = strtoul(buffer + 6, NULL, 10); + patch_method = BINARY_DELTA_DEFLATED; + origlen = strtoul(buffer + 6, NULL, 10); } else if (!strncmp(buffer, "literal ", 8)) { - patch->is_binary = BINARY_LITERAL_DEFLATED; - patch->deflate_origlen = strtoul(buffer + 8, NULL, 10); + patch_method = BINARY_LITERAL_DEFLATED; + origlen = strtoul(buffer + 8, NULL, 10); } else - return error("unrecognized binary patch at line %d: %.*s", - linenr-1, llen-1, buffer); + return NULL; + + linenr++; buffer += llen; while (1) { int byte_length, max_byte_length, newsize; @@ -1044,21 +1085,79 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch) if (max_byte_length < byte_length || byte_length <= max_byte_length - 4) goto corrupt; - newsize = fragment->size + byte_length; + newsize = hunk_size + byte_length; data = xrealloc(data, newsize); - if (decode_85(data + fragment->size, - buffer + 1, - byte_length)) + if (decode_85(data + hunk_size, buffer + 1, byte_length)) goto corrupt; - fragment->size = newsize; + hunk_size = newsize; buffer += llen; size -= llen; } - fragment->patch = data; - return used; + + frag = xcalloc(1, sizeof(*frag)); + frag->patch = inflate_it(data, hunk_size, origlen); + if (!frag->patch) + goto corrupt; + free(data); + frag->size = origlen; + *buf_p = buffer; + *sz_p = size; + *used_p = used; + frag->binary_patch_method = patch_method; + return frag; + corrupt: - return error("corrupt binary patch at line %d: %.*s", - linenr-1, llen-1, buffer); + if (data) + free(data); + *status_p = -1; + error("corrupt binary patch at line %d: %.*s", + linenr-1, llen-1, buffer); + return NULL; +} + +static int parse_binary(char *buffer, unsigned long size, struct patch *patch) +{ + /* We have read "GIT binary patch\n"; what follows is a line + * that says the patch method (currently, either "literal" or + * "delta") and the length of data before deflating; a + * sequence of 'length-byte' followed by base-85 encoded data + * follows. + * + * When a binary patch is reversible, there is another binary + * hunk in the same format, starting with patch method (either + * "literal" or "delta") with the length of data, and a sequence + * of length-byte + base-85 encoded data, terminated with another + * empty line. This data, when applied to the postimage, produces + * the preimage. + */ + struct fragment *forward; + struct fragment *reverse; + int status; + int used, used_1; + + forward = parse_binary_hunk(&buffer, &size, &status, &used); + if (!forward && !status) + /* there has to be one hunk (forward hunk) */ + return error("unrecognized binary patch at line %d", linenr-1); + if (status) + /* otherwise we already gave an error message */ + return status; + + reverse = parse_binary_hunk(&buffer, &size, &status, &used_1); + if (reverse) + used += used_1; + else if (status) { + /* not having reverse hunk is not an error, but having + * a corrupt reverse hunk is. + */ + free((void*) forward->patch); + free(forward); + return status; + } + forward->next = reverse; + patch->fragments = forward; + patch->is_binary = 1; + return used; } static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) @@ -1505,28 +1604,6 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i return offset; } -static char *inflate_it(const void *data, unsigned long size, - unsigned long inflated_size) -{ - z_stream stream; - void *out; - int st; - - memset(&stream, 0, sizeof(stream)); - - stream.next_in = (unsigned char *)data; - stream.avail_in = size; - stream.next_out = out = xmalloc(inflated_size); - stream.avail_out = inflated_size; - inflateInit(&stream); - st = inflate(&stream, Z_FINISH); - if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { - free(out); - return NULL; - } - return out; -} - static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) { unsigned long dst_size; @@ -1534,30 +1611,29 @@ static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) void *data; void *result; - /* Binary patch is irreversible */ - if (apply_in_reverse) - return error("cannot reverse-apply a binary patch to '%s'", - patch->new_name - ? patch->new_name : patch->old_name); - - data = inflate_it(fragment->patch, fragment->size, - patch->deflate_origlen); - if (!data) - return error("corrupt patch data"); - switch (patch->is_binary) { + /* Binary patch is irreversible without the optional second hunk */ + if (apply_in_reverse) { + if (!fragment->next) + return error("cannot reverse-apply a binary patch " + "without the reverse hunk to '%s'", + patch->new_name + ? patch->new_name : patch->old_name); + fragment = fragment; + } + data = (void*) fragment->patch; + switch (fragment->binary_patch_method) { case BINARY_DELTA_DEFLATED: result = patch_delta(desc->buffer, desc->size, data, - patch->deflate_origlen, + fragment->size, &dst_size); free(desc->buffer); desc->buffer = result; - free(data); break; case BINARY_LITERAL_DEFLATED: free(desc->buffer); desc->buffer = data; - dst_size = patch->deflate_origlen; + dst_size = fragment->size; break; } if (!desc->buffer) -- cgit v1.2.3